C# WPF Button - How to pass two itemsparameters into command? - Stack Overflow

I have a DataGrid within an ItemsControl, like shown below (Representing a collection within a collecti

I have a DataGrid within an ItemsControl, like shown below (Representing a collection within a collection).

When I place a button within a DataGrid cell, can I bind the command so that 2 parameters are passed into it? In this case I want to pass the instances of (Item,SubItem) that corresponds to the specific button.

How can I do this?

From what I have researched, I can make a helper class, which has the (Item,SubItem) set, and then pass that into the command. But I don't know how to go about actually doing it.

The code snippets below show what I am trying to do.

<ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplate>
    <DataTemplate>
        <StackPanel>
            <DataGrid ItemsSource="{Binding SubItems}">
                <DataGrid.Columns>

                    <DataGridTextColumn Binding="{Binding Name}" Width="60">
                        <DataGridTextColumn.ElementStyle>
                            <Style TargetType="TextBlock">
                                <Setter Property="HorizontalAlignment" Value="Center"/>
                            </Style>
                        </DataGridTextColumn.ElementStyle>
                    </DataGridTextColumn>
                    
                    <DataGridTemplateColumn>
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <Button Command="{Binding ???, Path=DataContext.Command}" 
                                        CommandParameter="{Binding ???}">
                                    <TextBlock Text="x"/>
                                </Button>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>

                </DataGrid.Columns>
            </DataGrid>
        </StackPanel>
    </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
class ViewModel
{
    public ViewModel()
    {
        Items = new ObservableCollection<Item>;
    }

    public ObservableCollection<Item> Items { get; set; }

    public RelayCommand Command => new RelayCommand(execute => SomeMethod((Item, SubItem) execute), canExecute => true);

    private void SomeMethod(Item item, SubItem subItem)
    {
        //Method Logic
    }

}
class Item
{
    public Item()
    {
        SubItems = new ObservableCollection<SubItem>;
    }

    public ObservableCollection<SubItem> SubItems { get; set; }

    public string Name;
    public double Property;
    etc...
}
class SubItem
{
    public SubItem()
    {
    }

    public string Name;
    public double Property;
    etc...
}

I have a DataGrid within an ItemsControl, like shown below (Representing a collection within a collection).

When I place a button within a DataGrid cell, can I bind the command so that 2 parameters are passed into it? In this case I want to pass the instances of (Item,SubItem) that corresponds to the specific button.

How can I do this?

From what I have researched, I can make a helper class, which has the (Item,SubItem) set, and then pass that into the command. But I don't know how to go about actually doing it.

The code snippets below show what I am trying to do.

<ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplate>
    <DataTemplate>
        <StackPanel>
            <DataGrid ItemsSource="{Binding SubItems}">
                <DataGrid.Columns>

                    <DataGridTextColumn Binding="{Binding Name}" Width="60">
                        <DataGridTextColumn.ElementStyle>
                            <Style TargetType="TextBlock">
                                <Setter Property="HorizontalAlignment" Value="Center"/>
                            </Style>
                        </DataGridTextColumn.ElementStyle>
                    </DataGridTextColumn>
                    
                    <DataGridTemplateColumn>
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <Button Command="{Binding ???, Path=DataContext.Command}" 
                                        CommandParameter="{Binding ???}">
                                    <TextBlock Text="x"/>
                                </Button>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>

                </DataGrid.Columns>
            </DataGrid>
        </StackPanel>
    </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
class ViewModel
{
    public ViewModel()
    {
        Items = new ObservableCollection<Item>;
    }

    public ObservableCollection<Item> Items { get; set; }

    public RelayCommand Command => new RelayCommand(execute => SomeMethod((Item, SubItem) execute), canExecute => true);

    private void SomeMethod(Item item, SubItem subItem)
    {
        //Method Logic
    }

}
class Item
{
    public Item()
    {
        SubItems = new ObservableCollection<SubItem>;
    }

    public ObservableCollection<SubItem> SubItems { get; set; }

    public string Name;
    public double Property;
    etc...
}
class SubItem
{
    public SubItem()
    {
    }

    public string Name;
    public double Property;
    etc...
}
Share Improve this question asked Feb 12 at 20:40 mmdnmmdn 536 bronze badges 3
  • This question is similar to: Passing two command parameters using a WPF binding. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. – emoacht Commented Feb 13 at 10:58
  • While I agree there are similarities to the linked answer, the specifics of this question are less about two properties (where a multibinding would be straightforward) and more about having the "instances of (Item,SubItem)" be able to easily interact with each other. This is the reason (in my mind) for setting up a good parenting hierarchy that maintains itself through any changes to the respective collections. – IV. Commented Feb 13 at 11:14
  • @mmdn I think emoacht has a point though. Your question might benefit from rewording perhaps because How to pass two parameters into command? would be more of a straight up duplicate whereas How to pass two objects/instances into command? might have a different flavor, even though it could strictly speaking be managed using multibinding. BTW let me know if multibinding solves your problem. Sometimes the minimal added complexity of the parenting would be worth it, other times not. I would also say that the UI is a little more decoupled, model more portable with the Parenting scheme. – IV. Commented Feb 13 at 11:23
Add a comment  | 

1 Answer 1

Reset to default 0

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;
        }
    }
}

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745203280a4616456.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信