One we get into these complex hierarchies it can be more of an art than a science and there may not be a "right" way. Here's one approach. So, you said:
I want to pass the instances of (Item,SubItem) that corresponds to the specific button.
In my "personal" view the path of least resistance to give you those two objects is:
- Just call the command on the context you're actually holding which is
SubItem - The
CommandParameteris theSubItemitself{Binding .}. - SubItems and Items are Parented in the respective models. They can easily traverse in the models without having to do gyrations in the XAML.
XAML Command Binding
<Window.DataContext>
<local:ViewModel></local:ViewModel>
</Window.DataContext>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<DataGrid
ItemsSource="{Binding SubItems}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Padding" Value="5,1"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Command="{Binding ButtonXCommand}"
CommandParameter="{Binding .}">
<TextBlock Text="x"/>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>Command Target is SubItem
When the [x] button is clicked the command target is in the Subitem class. To react, tell the SubItem.Parent to "do something" like in this example where we say we say "remove me".
partial class SubItem : ObservableObject
{
[ObservableProperty]
string? _name;
[ObservableProperty]
double _property;
public Item? Parent { get; internal set; }
[RelayCommand]
private void ButtonX(SubItem subItem)
{
Parent?.SubItems.Remove(subItem);
}
}But you might ask, how does SubItem come to have a Parent Item?
Item Model
In this approach to things, SubItem has a Parent because Item reacts to additions to the SubItems list by setting itself as the Parent. In this example, Item is listening to removals of SubItems. When the count goes to zero, it tells its Parent.Items to "remove me".
partial class Item : ObservableObject
{
public ViewModel? Parent { get; internal set; }
[ObservableProperty]
ObservableCollection<SubItem> _subItems = new();
[ObservableProperty]
string? _name;
[ObservableProperty]
double _property;
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.PropertyName == nameof(SubItems))
{
if (SubItems != null)
{
SubItems.ToList().ForEach(_ => _.Parent = this);
SubItems.CollectionChanged += CollectionChangedListener;
}
}
}
private void CollectionChangedListener(object? sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
e.NewItems?.OfType<SubItem>().ToList().ForEach(_ => _.Parent = this);
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldItems?.OfType<SubItem>().FirstOrDefault()?.Parent is Item item)
{
if (!item.SubItems.Any()) Parent?.Items.Remove(item);
}
break;
}
}
}
View Model
Like SubItem, Item has a Parent because ViewModel reacts to additions to the Items list by setting itself as the Parent.
partial class ViewModel : ObservableObject
{
[ObservableProperty]
ObservableCollection<Item> _items = new();
[RelayCommand]
private void ButtonX(object item)
{
if (item != null)
{
//Items.Remove(item); // Example: Removes the clicked item
}
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if(e.PropertyName == nameof(Items))
{
if(Items != null)
{
Items.ToList().ForEach(_ => _.Parent = this);
Items.CollectionChanged += CollectionChangedListener;
}
}
}
private void CollectionChangedListener(object? sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
e.NewItems?.OfType<Item>().ToList().ForEach(_ => _.Parent = this);
break;
}
}
}