开发者

How to add a ContextMenu in the WPF DataGridColumn in MVVM?

I've got a tricky issue regarding ContextMenu in a WPF DataGridColumn. I don't know if someone have already face this issue but I will really appreciate if someone can help me!

Let's start by my classes

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

Now my 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;
    }}

Here are my View and it's code behind

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

code behind

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

        this.DataContext = new MyViewModel();
    }
}

What I wanted in this example is to have a dynamic ContextMenu in DataGridColumn. First I put a ContextMenu in the entire DataGrid and it works fine. But in my case I need a ContextMenu only on a right click in Cells not in the entire DataGrid. So I tried to edit DataGridColumn's DataTemplate with a TextBox which has a ContextMenu. Unfortunately when I right click in the TextBox it's ContextMenu's ItemsSource seem to be empty. However when I right click outside the TextBox in the DataGrid, the DataGrid's ContextMenu is correctly binded.

开发者_JS百科

I was thinking that it might be a problem of DataContext because ContextMenu and DataGrid do not have the same Visual Tree so I added RelativeSource in the ContextMenu's ItemsSource binding but no result!!!

Any idea?


First of all thank Rick for taking time to guide me on this issue.

I'd posted the problem in msdn Forum and I had an answer to solve it

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

The think to do is passing the UserControl's DataContext to theContextMenu through the TextBox's Tag

For those who want to make it work properly with my code you'll need to define UserControlRessoucre as :

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

this is the msdn forum link of the original answer: -->here<--

many thanks Sheldon Xiao for this answer


You are on the right track. You do need to use RelativeSource but using Self, and then use the PlacementTarget to swap visual trees to the TextBox from which you can get its DataContext which should have been inherited from the DataGridCell and finally be able to reach your menu property.

Untested example of what I mean:

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


Even though I need the same contextMenu for each row in Datagrid, I tried you suggestion and can't make it works:( May be I forgot somthing

Hear we are I modify my classes like this :

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

Then I change InitData function in my ViewModel like:

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

And finally the ContextMenu in my View

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

Feel sorry if I made a rookie mistake but it does work


Context menus do not work as easily as they could because, by default, they are in a different visual tree to everything else, so the DataContext cannot be found.

The key insight is to create a <Style> that defines a context menu, then attach that style to a target element, which hooks up the context menu. This shifts the context menu into a visual tree that is lined up with the default DataContext you want.

First, create the style:

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


Then, hook the context menu up anywhere you want, without running into issues caused by different visual trees.

Example 1:

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

Example 2:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜