Search code examples
wpfcontextmenudatatemplate

Assign event handler to ContextMenu item defined in custom style


I have created Context menu style with predefined MenuItems in it:

    <Style TargetType="{x:Type ContextMenu}" x:Key="DataGridColumnFilterContextMenu">                
                <Setter Property="ContextMenu.Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Border BorderBrush="#868686"
                                BorderThickness="1"
                                Background="#FAFAFA"> 
                                <Grid MaxHeight="500">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="*"/>
                                </Grid.RowDefinitions>
                                <StackPanel>
                                    <MenuItem Header="Filtrēt" StaysOpenOnClick="True" Name="DynSearch">
                                        <MenuItem.Icon>
                                            <Image   RenderOptions.BitmapScalingMode="NearestNeighbor"
                                                     RenderOptions.EdgeMode="Aliased" 
                                                     Source="/Furniture;component/Resources/search4.png" />
                                        </MenuItem.Icon>
                                    </MenuItem>
                                    <Separator  Margin="20 5 20 0"  Height="2" Width="Auto" />
                                    <MenuItem Margin="5 5 0 0" StaysOpenOnClick="True" >
                                        <MenuItem.Template>
                                            <ControlTemplate >
                                                <CheckBox Padding="15 0 0 0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >Iezīmēt/Atzīmēt visus</CheckBox>
                                            </ControlTemplate>
                                        </MenuItem.Template>
                                        <MenuItem.Icon>
                                            <Image   RenderOptions.BitmapScalingMode="NearestNeighbor"
                                                     RenderOptions.EdgeMode="Aliased" 
                                                     Source="/Furniture;component/Resources/search4.png" />
                                        </MenuItem.Icon>
                                    </MenuItem>
                                    <Separator Margin="20 5 20 0" Height="2" Width="Auto" />
                                    <RadioButton Margin="5 5 0 0" Padding="15 0 0 0" GroupName="Sorting" Content="Augoša secībā" IsChecked="True"/>
                                    <RadioButton Margin="5 5 0 0" Padding="15 0 0 0" GroupName="Sorting" Content="Dilstoša secībā" />
                                    <Separator Margin="20 5 20 5" Height="2" Width="Auto" />
                                </StackPanel>
                                <ScrollViewer Grid.Row="1" 
                                              Margin="1,0,1,0"                                            
                                              CanContentScroll="True"
                                              VerticalScrollBarVisibility="Auto"
                                              Grid.ColumnSpan="2">
                                        <ItemsPresenter KeyboardNavigation.DirectionalNavigation="Cycle" />
                                    </ScrollViewer>
                                </Grid>                              
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Now I assigned style to new ContextMenu and wanted to assign Click event handler to MenuItem defined in style with header "Filtrēt". I tried do it like following:

ContextMenu cm = new ContextMenu();
cm.Style = Application.Current.Resources["DataGridColumnFilterContextMenu"] as Style;
var controlList = ((((cm.Template.LoadContent() as Border).Child as Grid).Children)[0] as StackPanel).Children;
MenuItem filterItem = (controlList[0] as MenuItem);
filterItem.Click += MiFiltre_Click;

And when I clicked debugger It didn't come to MiFiltre_Click method. I tried different events like MouseDown and PreviewMouseDown. Also I tried to bound ICommand and this also didn't work.

Then I searched this question and realized that cm.Template.LoadContent() may actually create new instance of my ContextMenu template and I am trying to bound my event handler to different control instance. Then I tried to get ContextMenu node controls with VisualTreeHelper and LogicalTreeHelper and this also didn't work.

So here are questions:

  1. How to achieve Click event handler binding?
  2. If 1st question is too hard, then how to get ContextMenu childrens current instance defined in custom style?

Solution

  • I'm going to assume this menu Style is defined in a resource dictionary named "MyResources.xaml".

    1. Create a Class file in the same project folder, and call it "MyResources.xaml.cs".

    2. Make it a partial class like this:

      namespace WhateverNamespace
      {
          public partial class MyResources
          {
              private void FilterMenuItem_Click(object sender, RoutedEventArgs e)
              {
                  MenuItem mi = (MenuItem)sender;
      
                  //  Below, PlacementTarget is the control the user right-clicked on. 
                  //  Use that directly if you want that instead of its viewmodel. 
      
                  //  This works with the conventional ContextMenu defined below.
                  //ViewModel viewModel = ((FrameworkElement)((ContextMenu)mi.Parent).PlacementTarget).DataContext as ViewModel;
      
                  //  This works with your template design. The only difference is 
                  //  TemplatedParent instead of Parent. 
                  ViewModel viewModel = 
                      ((FrameworkElement)((ContextMenu)mi.TemplatedParent).PlacementTarget)
                      .DataContext as ViewModel;
      
                  MessageBox.Show("FilterMenuItem_Click");
              }
          }
      }
      
    3. Give the resource dictionary an x:Class attribute:

      <ResourceDictionary 
          x:Class="WhateverNamespace.MyResources"
      
    4. Wire up the handler:

      <MenuItem 
          Click="FilterMenuItem_Click"
          Header="Filtrēt" 
          StaysOpenOnClick="True" 
          Name="DynSearch">
      

    By the way, you don't need to mess with styles and templates to create a ContextMenu resource:

    <ContextMenu x:Key="DataGridColumnFilterContextMenu">
        <MenuItem 
            Click="FilterMenuItem_Click"
            Header="Filtrēt" 
            StaysOpenOnClick="True" 
            Name="DynSearch">
            <MenuItem.Icon>
                <Image
                    RenderOptions.BitmapScalingMode="NearestNeighbor"
                    RenderOptions.EdgeMode="Aliased" 
                    Source="/Furniture;component/Resources/search4.png"
                    />
            </MenuItem.Icon>
        </MenuItem>
        <Separator />
        <MenuItem StaysOpenOnClick="True">
            <MenuItem.Header>
                <CheckBox>Iezīmēt/Atzīmēt visus</CheckBox>
            </MenuItem.Header>
            <MenuItem.Icon>
                <Image   
                    RenderOptions.BitmapScalingMode="NearestNeighbor"
                    RenderOptions.EdgeMode="Aliased" 
                    Source="/Furniture;component/Resources/search4.png"
                    />
            </MenuItem.Icon>
        </MenuItem>
        <Separator />
        <MenuItem>
            <MenuItem.Header>
                <RadioButton GroupName="Sorting" Content="Augoša secībā" IsChecked="True"/>
            </MenuItem.Header>
        </MenuItem>
        <MenuItem>
            <MenuItem.Header>
                <RadioButton GroupName="Sorting" Content="Dilstoša secībā" IsChecked="True"/>
            </MenuItem.Header>
        </MenuItem>
    </ContextMenu>