简体   繁体   English

如何将DataGrid的GroupItem的ContextMenu的MenuItem绑定到窗口或控件的DataContext?

[英]How do I bind the MenuItem of a ContextMenu of a DataGrid's GroupItem to the DataContext for the window or control?

The application I'm working on uses a DataGrid to present entries to the user and these entries are grouped. 我正在处理的应用程序使用DataGrid向用户显示条目,并将这些条目分组。 The grouping is not tied to a single property of each entry, a single entry can be in multiple groups. 分组不依赖于每个条目的单个属性,单个条目可以分为多个组。 The user is able to create groups at will and add entries to those groups. 用户可以随意创建组并向这些组添加条目。

We want the user to be able to edit the entries and the groups directly from this view. 我们希望用户能够直接从该视图编辑条目和组。 To remove a group, we'd like the user to be able to right click on the group and select "Delete group" from the context menu. 要删除组,我们希望用户能够右键单击该组,然后从上下文菜单中选择“删除组”。

I've been able to give the GroupItem's Expander a context menu, but have no idea how to bind the Command or CommandParameter to the ViewModel. 我已经能够为GroupItem的Expander提供一个上下文菜单,但不知道如何将Command或CommandParameter绑定到ViewModel。

How do I achieve the results I seek? 我如何实现我寻求的结果? I appreciate that this might require "moving" the context menu to a different part of the control, but we want a different context menu for the entries to the group headers. 我很欣赏这可能需要将上下文菜单“移动”到控件的不同部分,但我们需要为组头的条目设置不同的上下文菜单。 This we have achieved in our live code, but is not represented in the example below. 这是我们在实时代码中实现的,但未在下面的示例中表示。

Here is a simplified example to represent what we are trying to achieve. 这是一个简化的例子来表示我们想要实现的目标。

XAML: 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>

Code behind: 代码背后:

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);
            }
        }
    }
}

This should do the Trick: 这应该做的伎俩:

XAML: 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>

Code-Behind: 代码隐藏:

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);
    }
  }

You probably have to edit the Binding of the CommandParameter so it will fit your needs 您可能需要编辑CommandParameter的Binding,以便它符合您的需求

EDIT: 编辑:

Fixed the Xaml to enable proper Copy-Paste 修复了Xaml以启用正确的复制粘贴

The problem here is that binding 这里的问题是绑定

Command="{Binding DeleteGroupCommand}"

does not get resolved. 没有得到解决。 A simple solution is to make DeleteGroupCommand static and to refer to it as follows: 一个简单的解决方案是使DeleteGroupCommand成为静态,并按如下方式引用它:

<MenuItem Header="Delete group" Command="{x:Static dataGridHeaderTest:MainWindow.DeleteGroupCommand}" CommandParameter="{Binding}" />

And this works fine. 这很好用。 For further improvements you can check out related topic: WPF: Bind to command from ControlTemplate 有关进一步的改进,您可以查看相关主题: WPF:从ControlTemplate绑定到命令

lokusking's answer showed me how to bind the command correctly, but I still needed to bind the command parameter to the Group object. lokusking的回答向我展示了如何正确绑定命令,但我仍然需要将命令参数绑定到Group对象。

I thought I could get access to the Group's Name property through the CollectionViewGroupInternal, but it would not have been a very good solution as the class is mostly inaccessible. 我以为我可以通过CollectionViewGroupInternal访问Group的Name属性,但它不是一个非常好的解决方案,因为该类几乎无法访问。

I have managed to make changes to lokusking's solution to bind the Tag of the Expander to the Tag of the ContextMenu rather than the DataContext of the Context menu. 我已设法更改lokusking的解决方案,将Expander的Tag绑定到ContextMenu的Tag,而不是Context菜单的DataContext。 I can then bind the Command of the MenuItem to the Tag of the ContextMenu leaving the DataContext of the MenuItem intact for the CommandParameter. 然后,我可以将MenuItem的Command绑定到ContextMenu的Tag,使MenuItem的DataContext保持完整,用于CommandParameter。

Here is the relevant section of XAML in case it is of use to anybody: 以下是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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM