0

I have a nested datagrid where I have + and - buttons that are bound to RelayCommands that add a new row or delete the current one respectively. The minus button command's CanExecute logic is supposed to disable the current row's minus button if only one item is left in its category.

The problem is that it disables all minus buttons in all categories because of its template nature.

Image

How can this be mitigated?

Here's the code.

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;
        }
    }
Disodium
  • 105
  • 1
  • 2
  • 7

2 Answers2

0

You're binding your two Commands to Window's Data Context, and it should bind to DataGrid's Data Context.

Change your xaml to:

<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>
Ricardo Serra
  • 374
  • 2
  • 11
  • Nope. Buttons stop executing commands this way completely. My problem is not about command binding, binding tells compiler where to find the command's code. It's fine as is. The problem is how CanExecute logic applies to the view. – Disodium Mar 24 '17 at 03:48
0

I have eventually set the button's CanExecute to always return true and styled the button with a custom trigger that disables it when Items.Count turns 1. Perhaps there are more elegant solutions but at least this one works for me.

<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>
Disodium
  • 105
  • 1
  • 2
  • 7