我正在处理的应用程序使用DataGrid向用户显示条目,并将这些条目分组。分组不依赖于每个条目的单个属性,单个条目可以分为多个组。用户可以随意创建组并向这些组添加条目。
我们希望用户能够直接从此视图编辑条目和组。要删除组,我们希望用户能够右键单击该组,然后从上下文菜单中选择“删除组”。
我已经能够为GroupItem的Expander提供一个上下文菜单,但不知道如何将Command或CommandParameter绑定到ViewModel。
我如何实现我寻求的结果?我很欣赏这可能需要将上下文菜单“移动”到控件的不同部分,但我们需要为组头的条目设置不同的上下文菜单。这是我们在实时代码中实现的,但未在下面的示例中表示。
这是一个简化的例子来表示我们想要实现的目标。
XAML:
<Window x:Class="DataGridHeaderTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource x:Key="GroupedEntriesSource" Source="{Binding Entries}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Key"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="GroupContainerStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="#414040">
<Expander.ContextMenu>
<ContextMenu>
<!-- How do I bind Command and CommandParameter? -->
<MenuItem Header="Delete group" Command="{Binding DeleteGroupCommand}" CommandParameter="{Binding}" />
</ContextMenu>
</Expander.ContextMenu>
<Expander.Header>
<Grid>
<TextBlock Text="{Binding Path=Items[0].Key.Name}"/>
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Source={StaticResource GroupedEntriesSource}}" AutoGenerateColumns="False">
<DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupContainerStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Value.Name, Mode=OneWay}"/>
<DataGridTextColumn Header="Data" Binding="{Binding Value.Val, UpdateSourceTrigger=LostFocus}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
代码背后:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace DataGridHeaderTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
CreateData();
DeleteGroupCommand = new TestCommand(DeleteGroup);
DataContext = this;
InitializeComponent();
}
void CreateData()
{
Entries = new ObservableCollection<KeyValuePair<Group, Entry>>();
Group group1 = new Group() { Name = "Group1" };
Group group2 = new Group() { Name = "Group2" };
Entry entry1 = new Entry() { Name = "Entry1", Val = "Val1" };
Entry entry2 = new Entry() { Name = "Entry2", Val = "Val2" };
Entry entry3 = new Entry() { Name = "Entry3", Val = "Val3" };
Entries.Add(new KeyValuePair<Group, Entry>(group1, entry1));
Entries.Add(new KeyValuePair<Group, Entry>(group1, entry3));
Entries.Add(new KeyValuePair<Group, Entry>(group2, entry2));
Entries.Add(new KeyValuePair<Group, Entry>(group2, entry3));
}
void DeleteGroup(object group)
{
// I want to run this when "Delete group" is selected from the context menu of the Group Expander.
// I want the Group object associated with the Group Expander passed as the parameter
}
public ObservableCollection<KeyValuePair<Group, Entry>> Entries { get; set; }
public ICommand DeleteGroupCommand { get; set; }
public class Group
{
public string Name { get; set; }
}
public class Entry
{
public string Name { get; set; }
public string Val { get; set; }
}
public class TestCommand : ICommand
{
public delegate void ICommandOnExecute(object parameter);
private ICommandOnExecute _execute;
public TestCommand(ICommandOnExecute onExecuteMethod)
{
_execute = onExecuteMethod;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_execute.Invoke(parameter);
}
}
}
}
答案 0 :(得分:3)
这应该做的伎俩:
<强> XAML:强>
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" x:Name="root">
<Window.Resources>
<CollectionViewSource x:Key="GroupedEntriesSource" Source="{Binding Entries}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Key"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="GroupContainerStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="#414040" Tag="{Binding ElementName=root, Path=DataContext}">
<Expander.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Delete group" Command="{Binding DeleteGroupCommand}" CommandParameter="{TemplateBinding DataContext}" />
</ContextMenu>
</Expander.ContextMenu>
<Expander.Header>
<Grid>
<TextBlock Text="{Binding Path=Items[0].Key.Name}"/>
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Source={StaticResource GroupedEntriesSource}}" AutoGenerateColumns="False">
<DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupContainerStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Value.Name, Mode=OneWay}"/>
<DataGridTextColumn Header="Data" Binding="{Binding Value.Val, UpdateSourceTrigger=LostFocus}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
<强>代码隐藏:强>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
CreateData();
DeleteGroupCommand = new TestCommand(DeleteGroup);
DataContext = this;
}
void CreateData() {
Entries = new ObservableCollection<KeyValuePair<Group, Entry>>();
Group group1 = new Group() { Name = "Group1" };
Group group2 = new Group() { Name = "Group2" };
Entry entry1 = new Entry() { Name = "Entry1", Val = "Val1" };
Entry entry2 = new Entry() { Name = "Entry2", Val = "Val2" };
Entry entry3 = new Entry() { Name = "Entry3", Val = "Val3" };
Entries.Add(new KeyValuePair<Group, Entry>(group1, entry1));
Entries.Add(new KeyValuePair<Group, Entry>(group1, entry3));
Entries.Add(new KeyValuePair<Group, Entry>(group2, entry2));
Entries.Add(new KeyValuePair<Group, Entry>(group2, entry3));
}
void DeleteGroup(object group) {
// I want to run this when "Delete group" is selected from the context menu of the Group Expander.
// I want the Group object associated with the Group Expander passed as the parameter
}
public ObservableCollection<KeyValuePair<Group, Entry>> Entries {
get; set;
}
public ICommand DeleteGroupCommand { get; }
public class Group {
public string Name {
get; set;
}
}
public class Entry {
public string Name {
get; set;
}
public string Val {
get; set;
}
}
}
public class TestCommand : ICommand {
public delegate void ICommandOnExecute(object parameter);
private ICommandOnExecute _execute;
public TestCommand(ICommandOnExecute onExecuteMethod) {
_execute = onExecuteMethod;
}
public event EventHandler CanExecuteChanged {
add {
CommandManager.RequerySuggested += value;
}
remove {
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter) {
return true;
}
public void Execute(object parameter) {
_execute.Invoke(parameter);
}
}
您可能需要编辑CommandParameter的Binding以使其符合您的需求
修改强>
修复了Xaml以启用正确的复制粘贴
答案 1 :(得分:0)
这里的问题是绑定
Command="{Binding DeleteGroupCommand}"
无法解决。一个简单的解决方案是使DeleteGroupCommand成为静态,并按如下方式引用它:
<MenuItem Header="Delete group" Command="{x:Static dataGridHeaderTest:MainWindow.DeleteGroupCommand}" CommandParameter="{Binding}" />
这很好用。有关进一步改进,请查看相关主题:WPF: Bind to command from ControlTemplate
答案 2 :(得分:0)
lokusking的回答向我展示了如何正确绑定命令,但我仍然需要将命令参数绑定到Group对象。
我以为我可以通过CollectionViewGroupInternal访问Group的Name属性,但由于该类几乎无法访问,所以它不是一个非常好的解决方案。
我已经设法更改了lokusking的解决方案,将Expander的Tag绑定到ContextMenu的Tag,而不是Context菜单的DataContext。然后,我可以将MenuItem的Command绑定到ContextMenu的Tag,使MenuItem的DataContext保持完整,用于CommandParameter。
以下是XAML的相关部分,以防任何人使用:
<Style x:Key="GroupContainerStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="#414040" Tag="{Binding ElementName=root, Path=DataContext}">
<Expander.ContextMenu>
<ContextMenu Tag="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Delete group"
Command="{Binding Tag.DeleteGroupCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
CommandParameter="{Binding Items[0].Key}" />
</ContextMenu>
</Expander.ContextMenu>
<Expander.Header>
<Grid>
<TextBlock Text="{Binding Path=Items[0].Key.Name}"/>
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>