【问题标题】:How do I bind the MenuItem of a ContextMenu of a DataGrid's GroupItem to the DataContext for the window or control?如何将 DataGrid 的 GroupItem 的 ContextMenu 的 MenuItem 绑定到窗口或控件的 DataContext?
【发布时间】:2016-06-24 09:12:45
【问题描述】:

我正在处理的应用程序使用 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);
            }
        }
    }
}

【问题讨论】:

  • 同时尝试发布更清晰的图片。

标签: c# wpf mvvm data-binding


【解决方案1】:

这应该可以解决问题:

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 以启用正确的复制粘贴

【讨论】:

  • 嗨 lokusking,我已经尝试应用你的建议,但我的 DeleteGroupCommand 永远不会被调用。
  • 您是否像我在代码中那样创建了DeleteGroupCommand 属性?您有任何绑定问题吗?
  • 我已将您的 XAML 和 C# 代码直接粘贴到我的解决方案中的相应位上,并向 DeleteGroupCommand 添加了一个私有设置器,以便代码编译。我仍然没有调用 DeleteGroup() 方法。我在控制台中没有得到提及绑定问题的输出。
  • 我无法重现此内容。我在这条线上设置了一个断点_execute.Invoke(parameter);,一切对我来说都很好。如果您可以粘贴一些 Production-Xaml 可能会有所帮助
  • 我没有将其粘贴到生产代码中,而是将其粘贴到问题中包含的示例代码中。
【解决方案2】:

这里的问题是绑定

Command="{Binding DeleteGroupCommand}"

没有得到解决。一个简单的解决方案是将 DeleteGroupCommand 设为静态并按如下方式引用它:

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

这很好用。如需进一步改进,您可以查看相关主题:WPF: Bind to command from ControlTemplate

【讨论】:

  • 感谢您的回答,但是我不能使用静态命令,因为我们的工具允许该控件的多个实例同时可见,因此我需要绑定到 ViewModel 的命令实例。
  • 你能在多个窗口中有一个组吗? IE。你可以将一个组绑定到它的窗口吗?如果是这样,那么您可以在组中创建一个属性来获取窗口并引用它。或者干脆对组有一个命令。
  • 是的,同一个组(及其条目)可以出现在多个窗口中。条目(在实际代码中)显示本地化数据,因此每个窗口都可以配置为在不同的语言环境中显示相同的条目。我试图避免向组添加命令,因为它是模型的一部分。
【解决方案3】:

lokusking 的回答告诉我如何正确绑定命令,但我仍然需要将命令参数绑定到 Group 对象。

我认为我可以通过 CollectionViewGroupInternal 访问 Group 的 Name 属性,但这不是一个很好的解决方案,因为该类几乎无法访问。

我已经设法对 lokusking 的解决方案进行了更改,以将 Expander 的 Tag 绑定到 ContextMenu 的 Tag,而不是 Context 菜单的 DataContext。然后,我可以将 MenuItem 的 Command 绑定到 ContextMenu 的 Tag 上,为 CommandParameter 保留 MenuItem 的 DataContext。

这里是 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>

【讨论】:

    猜你喜欢
    • 2016-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多