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
?
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 typeSystem.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>