Skip to content

IVSoftware/command-with-multiple-args

Repository files navigation

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:

  1. Just call the command on the context you're actually holding which is SubItem
  2. The CommandParameter is the SubItem itself {Binding .}.
  3. 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;
        }
    }
}

Releases

No releases published

Packages

No packages published

Languages