WPF命令在模板内执行CanExecute验证

时间:2017-03-23 19:26:03

标签: wpf templates nested command canexecute

我有一个嵌套的数据网格,其中我有+和 - 按钮绑定到RelayCommands,它们分别添加新行或删除当前行。减号按钮命令的CanExecute逻辑应该禁用当前行的减号按钮,如果其类别中只剩下一个项目。

问题是由于其模板性质,它会禁用所有类别中的所有减号按钮。

Image

如何减轻这种情况?

这是代码。

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 个答案:

答案 0 :(得分:0)

您将两个命令绑定到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>

答案 1 :(得分:0)

我最终将按钮的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>