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

    Question

  • Hello guys!

    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. Unfortunatly when I right click in the TextBox it's ContextMenu ItemsSource seem to be empty. However when I right click outside the TextBox in the DataGrid, the DataGrid's contextMenu is correctly binded.

    I was thinking that it might be a problem of Datcontext 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?
    Monday, January 17, 2011 9:55 AM

Answers

  • Hi Hass_NB,

    Welcome to our forum.

    Based on your description and the code snippet, I have reproduced your issue.

    Now I will do two things for you.

    1) Explain the reason why your Binding does not work.

    2) Provide you a solution to fix your issue.

    ----------------------------------------------------------------------------------------------------------

    I think you said right, the cause of your issue is:

    --> ContextMenu and Datagrid do not have the same Visual Tree

    So your Binding will not work as you want.

    On the other hand, you want to use RelativeSource to resolve your issue, it is a correct direction, however, your code:

    ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, Path=DataContext.listDataMenu}"

    is not correct, because, ContextMenu and DataGrid do not have the same Visual Tree, so this code snippet will not find the DataGridCell and DataContext.

    So you have to use another way to achieve your goal, I have come up a method demonstrates you how to complete your project you could refer to:

        <DataGrid ItemsSource="{Binding listDataPersons}" AutoGenerateColumns="False">

            <DataGrid.ContextMenu>

                <ContextMenu Name="test" ItemsSource="{Binding listDataMenu}"/>

            </DataGrid.ContextMenu>

            <DataGrid.Columns>

                <DataGridTemplateColumn IsReadOnly="True" Width="*">

                    <DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                                <TextBlock Text="{Binding Name}"  Width="80" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}, Path=DataContext}" >

                                <TextBlock.ContextMenu>

                                    <!--<ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, Path=DataContext.MyViewModel.listDataMenu}"/>-->

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

                                 </TextBlock.ContextMenu>

                            </TextBlock>

                        </DataTemplate>

                    </DataGridTemplateColumn.CellTemplate>

                </DataGridTemplateColumn>

            </DataGrid.Columns>

        </DataGrid>

    It works well on my side, if anything is unclear, please let me know.

     

    Best regards,


    Sheldon _Xiao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    • Marked as answer by Hass_NB Thursday, January 20, 2011 9:04 AM
    Thursday, January 20, 2011 5:10 AM
    Moderator

All replies

  • Hi Hass_NB,

    Welcome to our forum.

    Based on your description and the code snippet, I have reproduced your issue.

    Now I will do two things for you.

    1) Explain the reason why your Binding does not work.

    2) Provide you a solution to fix your issue.

    ----------------------------------------------------------------------------------------------------------

    I think you said right, the cause of your issue is:

    --> ContextMenu and Datagrid do not have the same Visual Tree

    So your Binding will not work as you want.

    On the other hand, you want to use RelativeSource to resolve your issue, it is a correct direction, however, your code:

    ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, Path=DataContext.listDataMenu}"

    is not correct, because, ContextMenu and DataGrid do not have the same Visual Tree, so this code snippet will not find the DataGridCell and DataContext.

    So you have to use another way to achieve your goal, I have come up a method demonstrates you how to complete your project you could refer to:

        <DataGrid ItemsSource="{Binding listDataPersons}" AutoGenerateColumns="False">

            <DataGrid.ContextMenu>

                <ContextMenu Name="test" ItemsSource="{Binding listDataMenu}"/>

            </DataGrid.ContextMenu>

            <DataGrid.Columns>

                <DataGridTemplateColumn IsReadOnly="True" Width="*">

                    <DataGridTemplateColumn.CellTemplate>

                        <DataTemplate>

                                <TextBlock Text="{Binding Name}"  Width="80" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}, Path=DataContext}" >

                                <TextBlock.ContextMenu>

                                    <!--<ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridCell}}, Path=DataContext.MyViewModel.listDataMenu}"/>-->

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

                                 </TextBlock.ContextMenu>

                            </TextBlock>

                        </DataTemplate>

                    </DataGridTemplateColumn.CellTemplate>

                </DataGridTemplateColumn>

            </DataGrid.Columns>

        </DataGrid>

    It works well on my side, if anything is unclear, please let me know.

     

    Best regards,


    Sheldon _Xiao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    • Marked as answer by Hass_NB Thursday, January 20, 2011 9:04 AM
    Thursday, January 20, 2011 5:10 AM
    Moderator
  • Thank very much Sheldon, that works great! Well done!
    Thursday, January 20, 2011 9:05 AM
  • Hi Sheldon _Xiao

    Its been a nice solution, i try to resolve the issue, but can't.

    Thanks for your help.

    Thanks,

    Rajnikant

    Thursday, January 20, 2011 10:08 AM
  • Hi Rajnikant Rajwadi,

     

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

    <UserControl x:Class="ContextMenuMVVM.MyView"      
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
           xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
           xmlns:MyNameSpace="clr-namespace:ContextMenuMVVM"
           mc:Ignorable="d" 
           >
    
      <UserControl.Resources>
        <HierarchicalDataTemplate DataType="{x:Type MyNameSpace: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>
    
    .......
    
    </UserControl>

    And sepecify the contextMenuItemStyle in the ContextMenu control as:

    <ContextMenu ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.Tag.listDataMenu}" ItemContainerStyle="{StaticResource ContextMenuItemStyle}"/>
    

    Hope this will make it work for you! cheers

     

    Friday, January 21, 2011 8:57 AM
  • Hi,

    Thanks for Sharing such views/ideas for the community.

    Thanks,

    Rajnikant

    Friday, January 21, 2011 9:02 AM
  •  Hi Sheldon ,

      How can I  bind commands to this dynamically created Menu Items  in MVVM ?

     

     

    Friday, June 10, 2011 5:12 AM