如何在MVVM中的WPF DataGridColumn中添加ContextMenu?

时间:2011-01-17 09:48:43

标签: wpf mvvm datagrid contextmenu

我对WPF ContextMenu中的DataGridColumn提出了一个棘手的问题。我不知道是否有人已经面对这个问题,但如果有人能帮助我,我将非常感激!

让我们从我的课程开始

public class Person
{
    public string Type { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public int Age { get; set; }
}

public class Menu
{
    public string Name { get; set; }
    public int Code { get; set; }
    public ObservableCollection<Menu> listMenu { get; set; }
}

现在我的ViewModel

 public class MyViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Person> DataPersons = new ObservableCollection<Person>();
    private ObservableCollection<Menu> DataMenu = new ObservableCollection<Menu>();

    public ObservableCollection<Person> listDataPersons { get; set; }
    public ObservableCollection<Menu> listDataMenu { get; set; }

    public MyViewModel()
    {
        //initialization
        InitData();
    }

    private void InitData()
    {
        listDataPersons = new ObservableCollection<Person>();
        listDataMenu = new ObservableCollection<Menu>();

        DataPersons.Add(new Person() { Type = "Friend", Name = "Doe", Surname = "John", Age = 42});
        DataPersons.Add(new Person() { Type = "Friend", Name = "Smith", Surname = "Jack", Age = 42});

        DataMenu.Add(new Menu() { Name = "Principal", Code = 1});
        DataMenu.Add(new Menu() { Name = "Secondary", Code = 2});
        DataMenu.Add(new Menu() { Name = "Associated", Code = 3});

        DataMenu[2].listMenu = new ObservableCollection<Menu>();
        DataMenu[2].listMenu.Add(new Menu() { Name = "Associated 1", Code = 31 });

        listDataPersons = DataPersons;
        listDataMenu = DataMenu;
    }}

以下是我的View及其背后的代码

<DataGrid ItemsSource="{Binding listDataPersons}" AutoGenerateColumns="False">
    <DataGrid.ContextMenu>
        <ContextMenu ItemsSource="{Binding listDataMenu}"/>
    </DataGrid.ContextMenu>
    <DataGrid.Columns>                
        <DataGridTemplateColumn IsReadOnly="True" Width="*">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" Width="80" >
                    <TextBlock.ContextMenu>
                        <ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, Path=DataContext.listDataMenu}"/>
                    </TextBlock.ContextMenu>
                    </TextBlock>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

背后的代码

public partial class MyView : UserControl
{
    public MyView()
    {
        InitializeComponent();

        this.DataContext = new MyViewModel();
    }
}

在此示例中,我想要的是在ContextMenu中拥有动态DataGridColumn。首先,我在整个ContextMenu中添加了DataGrid,它运行正常。但在我的情况下,我只需要在单元格中右键单击ContextMenu而不是整个DataGrid。因此,我尝试使用DataGridColumn编辑DataTemplate的{​​{1}} TextBox。不幸的是,当我右键点击ContextMenuTextBox ContextMenu ItemsSource似乎是空的。但是,当我在TextBox的{​​{1}}外右键单击时,DataGrid的{​​{1}}被正确绑定。

我认为这可能是DataGrid的问题,因为ContextMenuDataContext没有相同的可视树,所以我在{{1}添加了ContextMenu } DataGrid绑定但没有结果!!!

有什么想法吗?

4 个答案:

答案 0 :(得分:1)

首先感谢Rick花时间指导我解决这个问题。

我在msdn论坛上发布了这个问题,我得到了解决它的答案

<TextBlock Text="{Binding Name}" Width="80" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGrid}}, Path=DataContext}">
<TextBlock.ContextMenu>                                                                                                
    <ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.Tag.listDataMenu}" ItemContainerStyle="{StaticResource ContextMenuItemStyle}"/>
</TextBlock.ContextMenu>                                

要做的是通过TextBox的标签将UserControl的DataContext传递给TextMenu

对于那些想要使用我的代码使其正常工作的人,您需要将UserControlRessoucre定义为:

<UserControl.Resources>
    <HierarchicalDataTemplate DataType="{x:Type cmd:Menu}" ItemsSource="{Binding listMenu}">
        <TextBlock Text="{Binding Path=Name}"/>            
    </HierarchicalDataTemplate>

    <Style x:Key="ContextMenuItemStyle">
        <Setter Property="MenuItem.ItemsSource" Value="{Binding Path=listMenu}"/>             
    </Style>
</UserControl.Resources>

这是原始答案的msdn论坛链接:-->here<--

非常感谢Sheldon Xiao的回答

答案 1 :(得分:0)

你走在正确的轨道上。您需要使用RelativeSource,但需要使用Self,然后使用PlacementTarget将可视树交换到TextBox,您可以从DataContext获取其DataGridCell应该是从<ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext.listDataMenu}"/> 继承而来,最终能够到达您的菜单属性。

我的意思是未经测试的例子:

{{1}}

答案 2 :(得分:0)

即使我在Datagrid中的每一行都需要相同的contextMenu,我尝试了你的建议并且无法使其工作:(可能是我忘记了某事

听到我们这样修改我的课程:

    public class Person
{
    public string Type { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public int Age { get; set; }
    public Column Column { get; set; }
}

public class Menu
{
    public string Name { get; set; }
    public int Code { get; set; }
    public ObservableCollection<Menu> listMenu { get; set; }
}
public class Column
{
    public ObservableCollection<Menu> listDatatMenu { get; set; }
}

然后我在我的ViewModel中更改InitData函数,如:

private void InitData()
    {
        listDataPersons = new List<Person>();
        listDataMenu = new ObservableCollection<Menu>();

        DataPersons.Add(new Person() { Type = "Friend", Name = "Doe", Surname = "John", Age = 42});
        DataPersons.Add(new Person() { Type = "Friend", Name = "Smith", Surname = "Jack", Age = 42});

        DataMenu.Add(new Menu() { Name = "Principal", Code = 1});
        DataMenu.Add(new Menu() { Name = "Secondary", Code = 2});
        DataMenu.Add(new Menu() { Name = "Associated", Code = 3});

        DataMenu[2].listMenu = new ObservableCollection<Menu>();
        DataMenu[2].listMenu.Add(new Menu() { Name = "Associated 1", Code = 31 });

        DataPersons[0].Column = new Column();
        DataPersons[0].Column.listDatatMenu = DataMenu;

        DataPersons[1].Column = new Column();
        DataPersons[1].Column.listDatatMenu = DataMenu;

        listDataPersons = DataPersons;
        listDataMenu = DataMenu;
    }

最后是我视图中的ContextMenu

<ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext.Column.listDataMenu}"/>  

如果我犯了一个菜鸟错误但确实有效,请感到抱歉

答案 3 :(得分:0)

上下文菜单不能尽可能轻松地工作,因为默认情况下,它们与其他所有内容都在不同的可视树中,因此无法找到DataContext

  

关键的见解是创建一个定义上下文菜单的<Style>,   然后将该样式附加到目标元素,该元素将连接上下文   菜单。 这会将上下文菜单转换为与您想要的默认DataContext对齐的可视树。

首先,创建样式:

<UserControl.Resources>                                                                                                                        
    <ResourceDictionary>

        <!-- For the context menu to work, we must shift it into a style, which means that the context menu is now in a
        visual tree that is more closely related to the current data context. All we have to do then is set the style, 
        which hooks up the context menu. -->
        <Style x:Key="ContextMenuStyle" TargetType="{x:Type StackPanel}">
            <Setter Property="ContextMenu" Value="{DynamicResource TreeViewContextMenu}"/>
        </Style>
        <ContextMenu x:Key="TreeViewContextMenu">
            <MenuItem Header="Test" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.CmdDisplayDetailsGraph}"/>
        </ContextMenu>


然后,将上下文菜单挂钩到任何您想要的位置,而不会遇到由不同可视树引起的问题。

示例1:

<HierarchicalDataTemplate DataType="{x:Type snapshot:Details}" ItemsSource="{Binding DetailsList}">
    <StackPanel Orientation="Vertical" Style="{StaticResource ContextMenuStyle}">
        <ContentPresenter Content="{Binding}" ContentTemplate="{Binding View.DefaultDataRowTemplate}" />
</StackPanel>

示例2:

<DataTemplate DataType="{x:Type snapshot:InstrumentDetails}">
  <StackPanel Orientation="Vertical" Style="{StaticResource ContextMenuStyle}">                 
      <Grid HorizontalAlignment="Stretch" VerticalAlignment="Center">