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>