Search code examples
c#wpfxamlcontextmenulistboxitem

Click Event not firing on ListBoxItem ContextMenu


I'm trying to add a ContextMenu to ListBoxItem, but the Click event is not fired.

I tried ContextMenu.MenuItem.Click event. I also tried binding Command, but a binding error appears in the output window like:

"Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=NavigateCommand;"

Here is complete sample code.

XAML

<ListBox>
    <ListBoxItem>1</ListBoxItem>
    <ListBox.ItemContainerStyle>
        <Style TargetType="{x:Type ListBoxItem}"
               BasedOn="{StaticResource {x:Type ListBoxItem}}">
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu MenuItem.Click="ContextMenu_Click">
                        <MenuItem Header="Navigate"
                                  Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window, AncestorLevel=1}, Path=NavigateCommand}"
                                  Click="NavigateItemClick" />
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

CODE BEHIND

public MainWindow()
{
    InitializeComponent();
    NavigateCommand = new DelegateCommand(Navigate);
}

public DelegateCommand NavigateCommand { get; set; }


private void Navigate()
{
    MessageBox.Show("Command Worked");
}

private void NavigateItemClick(object sender, RoutedEventArgs e)
{
    MessageBox.Show("Item Click Worked");
}

private void ContextMenu_Click(object sender, RoutedEventArgs e)
{
    MessageBox.Show("Any Item click Worked");
}
        

Is there any way to invoke the Click event handler or bind the Command?


Solution

  • The ContextMenu is not part of the same visual tree as your ListBox, as it is displayed in a separate window. Therefore using a RelativeSource or ElementName binding does not work directly. However, you can work around this issue by binding the Window (where you define the NavigateCommand in code-behind) with a RelativeSource binding to the Tag property of ListBoxItem. This works, since they are part of the same visual tree. The Tag property is a general purpose property that you can assign anything to.

    Gets or sets an arbitrary object value that can be used to store custom information about this element.

    Then use the PlacementTarget property of the ContextMenu as indirection to access the Tag of the ListBoxItem that it was opened for.

    <Style TargetType="{x:Type ListBoxItem}"
           BasedOn="{StaticResource {x:Type ListBoxItem}}">
       <Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
       <Setter Property="ContextMenu">
          <Setter.Value>
             <ContextMenu>
                <MenuItem Header="Navigate"
                          Command="{Binding PlacementTarget.Tag.NavigateCommand, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
             </ContextMenu>
          </Setter.Value>
       </Setter>
    </Style>
    

    In essence, you bind the Window data context to the Tag of ListBoxItem which is the PlacementTarget of the ContextMenu, that can then bind the NavigateCommand through the Tag property.

    Adding a Click event handler is also possible, but in case the ContextMenu is defined in a Style, you have to add it differently, otherwise it will not work and you get this strange exception.

    Unable to cast object of type System.Windows.Controls.MenuItem to type System.Windows.Controls.Button.

    Add a MenuItem style with an event setter for Click, where you assign the event handler.

    <Style TargetType="{x:Type ListBoxItem}"
           BasedOn="{StaticResource {x:Type ListBoxItem}}">
       <Setter Property="ContextMenu">
          <Setter.Value>
             <ContextMenu>
                <MenuItem Header="Navigate">
                   <MenuItem.Style>
                      <Style TargetType="MenuItem">
                         <EventSetter Event="Click" Handler="MenuItem_OnClick"/>
                      </Style>
                   </MenuItem.Style>
                </MenuItem>
             </ContextMenu>
          </Setter.Value>
       </Setter>
    </Style>