DataGridやListBox内でクリックされたら自身の行を削除するButton
概要
DataGridやListBoxで複数のアイテムを表示しているときに、アイテム自身に削除ボタンをつけたいことがあります。
MVVMでやっている場合は、アイテムごとViewModelに削除Commandを用意して、、、となります。
なしの場合はコードビハインドで、削除ボタンが押されたItemを検索して、、、となります。
どちらにしても、手間ですし他のコードで使い回せません。
そこで、添付プロパティを使って、Buttonに自身が所属しているItemsControlから削除する機能を追加します。
Viewだけで完結しているので、ViewModel側には追加作業は必要ありません。
MVVMを使用していない場合も、コードビハインドから呼び出して使えます。
ItemsControlを継承しているコントロールなら使えるので、ItemsControl、DataGrid、ListBox、ListView、ComboBoxでも使えます。
方法
添付プロパティ
まず、指定されたオブジェクトを含む行を親のItemsControlから削除するメソッドを定義します。
コードビハインドを使用する場合は、このメソッドをButtonのクリックイベントから呼び出しても使えます。
/// <summary>
/// 指定されたオブジェクトを含む行を親のItemsControlから削除する
/// </summary>
public static void RemoveItemFromParent(DependencyObject elementInItem)
{
DependencyObject parent = elementInItem;
var parentTree = new List<DependencyObject> { parent };
//指定されたオブジェクトのVisualTree上の親を順番に探索し、ItemsControlを探す。
//ただし、DataGridは中間にいるDataGridCellsPresenterは無視する
while (parent != null && !(parent is ItemsControl) || parent is DataGridCellsPresenter)
{
parent = VisualTreeHelper.GetParent(parent);
parentTree.Add(parent);
}
if (!(parent is ItemsControl itemsControl))
return;
//ItemsControlの行にあたるオブジェクトを探索履歴の後ろから検索
var item = parentTree
.LastOrDefault(x => itemsControl.IsItemItsOwnContainer(x));
int? removeIndex = itemsControl.ItemContainerGenerator?.IndexFromContainer(item);
if (removeIndex == null || removeIndex < 0)
return;
//Bindingしていた場合はItemsSource、違うならItemsから削除する
IEnumerable targetList = (itemsControl.ItemsSource ?? itemsControl.Items);
switch (targetList)
{
case IList il:
il.RemoveAt(index);
return;
case IEditableCollectionView iECV:
iECV.RemoveAt(index);
return;
}
}
そして、Buttonのクリックイベントでこのメソッドを呼ぶ添付プロパティを用意します。
#region RemoveItem添付プロパティ
public static bool GetRemoveItem(DependencyObject obj) => (bool)obj.GetValue(RemoveItemProperty);
public static void SetRemoveItem(DependencyObject obj, bool value) => obj.SetValue(RemoveItemProperty, value);
public static readonly DependencyProperty RemoveItemProperty =
DependencyProperty.RegisterAttached("RemoveItem", typeof(bool), typeof(MyExt),
new PropertyMetadata(default(bool), OnRemoveItemChanged));
private static void OnRemoveItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ButtonBase button))
return;
if (!(e.NewValue is bool b))
return;
if (b)
button.Click += RemoveItem;
else
button.Click -= RemoveItem;
}
private static void RemoveItem(object sender, RoutedEventArgs e) => RemoveItemFromParent(sender as DependencyObject);
#endregion
使用方法
ViewModel側にこんなプロパティがあるとします。
public ObservableCollection<string> Names { get; } = new ObservableCollection<string>(new[] { "TARO", "JIRO", "SABRO" });
それに対して、ViewではDataGridで上記のNames
プロパティにBindingしています。
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding Names}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<Button local:MyExt.RemoveItem="True" Content="✖" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
ListBoxの場合は以下です。
<ListBox
ItemsSource="{Binding Names}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width="100" Text="{Binding}" />
<Button local:MyExt.RemoveItem="True" Content="✖" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
MVVMを使用しない場合は、以下のようになります
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Text}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Click="XButton_Click" Content="X" />
<!--<Button Content="X" local:MyExt.RemoveItem="True">-->
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<TextBlock Text="AAA" />
<TextBlock Text="BBB" />
<TextBlock Text="CCC" />
</DataGrid>
添付プロパティを使用せず、クリックイベントのコードビハインドから呼び出す場合は、上記Xamlのコメント部分を解除した上で、コードビハインドに以下を追加します。
private void XButton_Click(object sender, RoutedEventArgs e)
{
if (sender is DependencyObject dObj)
MyExt.RemoveItemFromParent(dObj);
}
注意点
DataGridなどで並び替えしていると、正しく動きません。削除するときのIndex算出は並び替え後のIndexですが、削除時はデフォルトの並びでのIndexを指定する必要があるためです。
View側での変更をViewModelに伝えるため、ItemsSourceのBindingはTwo-Wayにする必要があります。
つまり、ReadOnlyなコレクションがBindingされていた場合は使えません。
環境
VisualStudio2019
.NET Core 3.1
C#8
Author And Source
この問題について(DataGridやListBox内でクリックされたら自身の行を削除するButton), 我々は、より多くの情報をここで見つけました https://qiita.com/soi/items/94b652850e5ad5394330著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .