WPF команды CanExecute проверки внутри шаблона

У меня есть вложенная сетка данных, где у меня есть кнопки + и -, которые связаны с RelayCommands, которые добавляют новую строку или удаляют текущую соответственно. Логика CanExecute команды "кнопка минуса" должна отключить кнопку "минус" текущей строки, если в ее категории остался только один элемент.

Проблема в том, что он отключает все минус-кнопки во всех категориях из-за своего шаблонного характера.

Образ

Как это можно смягчить?

Вот код

XAML

 <Grid>
        <DataGrid x:Name="dataGrid1" 
                  ItemsSource="{Binding DataCollection}"
                  SelectedItem="{Binding dataCollectionSelectedItem, Mode=TwoWay}"
                  AutoGenerateColumns="False" 
                  CanUserAddRows="false" >
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="Item/Price" Width="*">
                    <DataGridTemplateColumn.CellTemplate >
                        <DataTemplate>
                            <DataGrid x:Name="dataGridItem" 
                                      ItemsSource="{Binding Items}"
                                      SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.itemsSelectedItem, Mode=TwoWay}"
                                      Background="Transparent"
                                      HeadersVisibility="None"
                                      AutoGenerateColumns="False"
                                      CanUserAddRows="false" >
                                <DataGrid.Columns>
                                    <DataGridTextColumn Binding="{Binding Name}" Width="*"/>
                                    <DataGridTextColumn Binding="{Binding Price}" Width="50"/>
                                    <DataGridTemplateColumn Header="Button">
                                        <DataGridTemplateColumn.CellTemplate>
                                            <DataTemplate>
                                                <StackPanel Orientation="Horizontal">
                                                    <Button  Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.AddItem }" Width="20" Height="20">+</Button>
                                                    <Button  Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.DeleteItem }" Width="20" Height="20">-</Button>
                                                </StackPanel>
                                            </DataTemplate>
                                        </DataGridTemplateColumn.CellTemplate>
                                    </DataGridTemplateColumn>
                                </DataGrid.Columns>
                            </DataGrid>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn Header="Category" Binding="{Binding Category}" Width="Auto"/>
                <DataGridTemplateColumn Header="Buttons">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Button  Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.AddCategory}" Width="20" Height="20">+</Button>
                                <Button  Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.DeleteCategory}" Width="20" Height="20">-</Button>
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>

C#

 public class Item
    {
        public string Name { get; set; }
        public int Price { get; set; }
    }

    public class DataTable
    {
        public ObservableCollection<Item> Items { get; set; }
        public string Category { get; set; }
    }

    public class RelayCommand : ICommand
    {
        private Action<object> executeDelegate;
        readonly Predicate<object> canExecuteDelegate;

        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new NullReferenceException("execute");
            executeDelegate = execute;
            canExecuteDelegate = canExecute;
        }

        public RelayCommand(Action<object> execute) : this(execute, null) { }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter)
        {
            return canExecuteDelegate == null ? true : canExecuteDelegate(parameter);
        }

        public void Execute(object parameter)
        {
            executeDelegate.Invoke(parameter);
        }
    }

    public class ViewModel
    {
        public ObservableCollection<DataTable> DataCollection { get; set; }

        public DataTable dataCollectionSelectedItem { get; set; }
        public Item itemsSelectedItem { get; set; }

        public RelayCommand DeleteCategory { get; private set; }
        public RelayCommand AddCategory { get; private set; }
        public RelayCommand DeleteItem { get; private set; }
        public RelayCommand AddItem { get; private set; }

        public ViewModel()
        {
            DataCollection = new ObservableCollection<DataTable>
            {
                new DataTable() {
                    Items = new ObservableCollection<Item> {
                        new Item { Name = "Phone", Price = 220 },
                        new Item { Name = "Tablet", Price = 350 },
                    },
                    Category = "Electronic gadgets" },
                new DataTable() {
                    Items = new ObservableCollection<Item> {
                        new Item { Name = "Teddy Bear Deluxe", Price = 2200 },
                        new Item { Name = "Pokemon", Price = 100 },
                    },
                Category = "Toys" }
            };

            DeleteItem = new RelayCommand(innerDeleteItem, canUseDeleteItem);
            AddItem = new RelayCommand(innerAddItem, canUseAddItem);
        }

        public void innerDeleteItem(object parameter)
        {
            var collectionIndex = DataCollection.IndexOf(dataCollectionSelectedItem);
            if (DataCollection[collectionIndex].Items.Count != 1)
            {
                DataCollection[collectionIndex].Items.Remove(itemsSelectedItem);
                CollectionViewSource.GetDefaultView(DataCollection).Refresh();
            }

        }
        public bool canUseDeleteItem(object parameter)
        {
            var collectionIndex = DataCollection.IndexOf(dataCollectionSelectedItem);
            if ((dataCollectionSelectedItem != null) && (DataCollection[collectionIndex].Items.Count == 1))
            {
                return false;
            }
            else return true;
        }
        public void innerAddItem(object parameter)
        {
            var collectionIndex = DataCollection.IndexOf(dataCollectionSelectedItem);
            var itemIndex = DataCollection[collectionIndex].Items.IndexOf(itemsSelectedItem);
            Item newItem = new Item() { Name = "Item_Name", Price = 0 };
            DataCollection[collectionIndex].Items.Insert(itemIndex + 1, newItem);
            CollectionViewSource.GetDefaultView(DataCollection).Refresh();
        }
        public bool canUseAddItem(object parameter)
        {
            return true;
        }
    }

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            ViewModel newViewModel = new ViewModel();
            this.DataContext = newViewModel;
        }
    }

2 ответа

Вы связываете две свои команды с контекстом данных Window, и он должен связываться с контекстом данных DataGrid.

Измените свой xaml на:

<StackPanel Orientation="Horizontal">
     <Button  Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.AddItem }" Width="20" Height="20">+</Button>
     <Button  Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.DeleteItem }" Width="20" Height="20">-</Button>
</StackPanel>

В конце концов я установил CanExecute кнопки, чтобы она всегда возвращала true, и стилизовал кнопку с помощью пользовательского триггера, который отключает ее, когда Items.Count исполняется 1. Возможно, есть более элегантные решения, но по крайней мере это работает для меня.

<Button  Content="-"
         Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.DeleteItem }"
         Width="20" Height="20">
     <Button.Style>
         <Style TargetType="Button">
             <Setter Property="IsEnabled" Value="True" />
             <Style.Triggers>
                 <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=Items.Count }" Value="1">
                     <Setter Property="IsEnabled" Value="False" />
                 </DataTrigger>
              </Style.Triggers>
         </Style>
     </Button.Style>
</Button>
Другие вопросы по тегам