单击事件未在 ListBoxItem 上下文菜单上触发
Click Event not firing on ListBoxItem ContextMenu
我正在尝试将 ContextMenu
添加到 ListBoxItem
,但未触发 Click
事件。
我尝试了 ContextMenu.MenuItem.Click
事件。
我也尝试绑定 Command
,但输出 window 中出现绑定错误,例如:
"Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=NavigateCommand;"
这是完整的示例代码。
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>
代码隐藏
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");
}
有什么方法可以调用 Click
事件处理程序或绑定 Command
?
ContextMenu
与您的 ListBox
不属于同一可视化树,因为它显示在单独的 window 中。因此,使用 RelativeSource
或 ElementName
绑定无法直接 。但是,您可以通过将 Window
(您在代码隐藏中定义 NavigateCommand
的位置)与 RelativeSource
绑定到 Tag
属性 的 ListBoxItem
。这是有效的,因为它们是同一棵可视化树的一部分。 Tag
属性 是通用的 属性,您可以将任何内容分配给它。
Gets or sets an arbitrary object value that can be used to store custom information about this element.
然后使用 ContextMenu
的 PlacementTarget
属性 作为间接访问它打开的 ListBoxItem
的 Tag
。
<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>
本质上,您将 Window
数据上下文绑定到 ListBoxItem
的 Tag
,即 ContextMenu
的 PlacementTarget
,然后可以通过 Tag
属性.
绑定 NavigateCommand
添加 Click
事件处理程序也是可能的,但是如果 ContextMenu
是在 Style
中定义的,您必须以不同的方式添加它,否则它将不起作用并且你得到这个奇怪的异常。
Unable to cast object of type System.Windows.Controls.MenuItem
to type System.Windows.Controls.Button
.
为 Click
添加带有事件 setter 的 MenuItem
样式,您可以在其中分配事件处理程序。
<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>
我认为没有必要编写“hackish”代码。我认为您应该改进您的代码。
上下文菜单只能在其当前 UI 上下文中运行,因此得名上下文菜单。当您右键单击您的代码编辑器时,您将找不到例如“打开文件”菜单项 - 这将脱离上下文。
菜单的上下文是实际项目。因此,应该在项目的数据模型中定义命令,因为上下文菜单应该只定义针对项目的命令。
请注意,ContextMenu
始终隐式托管在 Popup
中(如 Popup
的 Child
)。 Popup
的子元素永远不能成为可视化树的一部分(因为内容在专用的 Window
实例中呈现并且 Window
不能是另一个元素的子元素,Window
始终是它自己的可视化树的根),因此使用 Binding.RelativeSource
的树遍历是行不通的。
您可以引用 ContextMenu.PlacementTarget
(而不是 DataContext
)来获取实际的 ListBoxItem
(放置目标)。 ListBoxItem
的 DataContext
是数据模型(下例中的 WindowItem class)。
例如引用例如OpenWindowCommand
当前ListBoxItem
的底层数据模型从ContextMenu
里面使用:
"{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.DataContext.OpenWindowCommand}"
WindowItem.cs
为列表中的项目创建数据模型。此数据模型公开了对项目本身进行操作的所有命令。
class WindowItem
{
public WindowItem(string windowName) => this.Name = windowName;
public string Name { get; }
public ICommand OpenWindowCommand => new RelayCommand(ExecuteOpenWindow);
private void ExecuteOpenWindow(object commandParameter)
{
// TODO::Open the window associated with this instance
MessageBox.Show($"Window {this.Name} is open");
}
public override string ToString() => this.Name;
}
MainWindow.xaml.cs
使用数据绑定从 MainWindow 初始化 ListBox:
partial class MainWindow : Window
{
public ObservableCollection<WindowItem> WindowItems { get; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
var windowItems = new
this.WindowItems = new ObservableCollection<WindowItem>
{
new WindowItem { Name = "Window 1" },
new WindowItem { Name = "Window 2" }
}
}
}
MainWindow.xaml
<Window>
<Window.Resources>
<!-- Command version -->
<ContextMenu x:Key="ListBoxItemContextMenu">
<MenuItem Header="Open"
Command="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.DataContext.OpenWindowCommand}" />
</ContextMenu>
<!-- Optionally define a Click handler in code-behind (MainWindow.xaml.cs) -->
<ContextMenu x:Key="ListBoxItemContextMenu">
<MenuItem Header="Open"
Click="OpenWindow_OnMenuItemClick" />
</ContextMenu>
</Window.Resources>
<ListBox ItemsSource="{Binding WindowItems}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu" Value="{StaticResource ListBoxItemContextMenu}">
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Window>
我正在尝试将 ContextMenu
添加到 ListBoxItem
,但未触发 Click
事件。
我尝试了 ContextMenu.MenuItem.Click
事件。
我也尝试绑定 Command
,但输出 window 中出现绑定错误,例如:
"Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=NavigateCommand;"
这是完整的示例代码。
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>
代码隐藏
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");
}
有什么方法可以调用 Click
事件处理程序或绑定 Command
?
ContextMenu
与您的 ListBox
不属于同一可视化树,因为它显示在单独的 window 中。因此,使用 RelativeSource
或 ElementName
绑定无法直接 。但是,您可以通过将 Window
(您在代码隐藏中定义 NavigateCommand
的位置)与 RelativeSource
绑定到 Tag
属性 的 ListBoxItem
。这是有效的,因为它们是同一棵可视化树的一部分。 Tag
属性 是通用的 属性,您可以将任何内容分配给它。
Gets or sets an arbitrary object value that can be used to store custom information about this element.
然后使用 ContextMenu
的 PlacementTarget
属性 作为间接访问它打开的 ListBoxItem
的 Tag
。
<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>
本质上,您将 Window
数据上下文绑定到 ListBoxItem
的 Tag
,即 ContextMenu
的 PlacementTarget
,然后可以通过 Tag
属性.
NavigateCommand
添加 Click
事件处理程序也是可能的,但是如果 ContextMenu
是在 Style
中定义的,您必须以不同的方式添加它,否则它将不起作用并且你得到这个奇怪的异常。
Unable to cast object of type
System.Windows.Controls.MenuItem
to typeSystem.Windows.Controls.Button
.
为 Click
添加带有事件 setter 的 MenuItem
样式,您可以在其中分配事件处理程序。
<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>
我认为没有必要编写“hackish”代码。我认为您应该改进您的代码。
上下文菜单只能在其当前 UI 上下文中运行,因此得名上下文菜单。当您右键单击您的代码编辑器时,您将找不到例如“打开文件”菜单项 - 这将脱离上下文。
菜单的上下文是实际项目。因此,应该在项目的数据模型中定义命令,因为上下文菜单应该只定义针对项目的命令。
请注意,ContextMenu
始终隐式托管在 Popup
中(如 Popup
的 Child
)。 Popup
的子元素永远不能成为可视化树的一部分(因为内容在专用的 Window
实例中呈现并且 Window
不能是另一个元素的子元素,Window
始终是它自己的可视化树的根),因此使用 Binding.RelativeSource
的树遍历是行不通的。
您可以引用 ContextMenu.PlacementTarget
(而不是 DataContext
)来获取实际的 ListBoxItem
(放置目标)。 ListBoxItem
的 DataContext
是数据模型(下例中的 WindowItem class)。
例如引用例如OpenWindowCommand
当前ListBoxItem
的底层数据模型从ContextMenu
里面使用:
"{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.DataContext.OpenWindowCommand}"
WindowItem.cs
为列表中的项目创建数据模型。此数据模型公开了对项目本身进行操作的所有命令。
class WindowItem
{
public WindowItem(string windowName) => this.Name = windowName;
public string Name { get; }
public ICommand OpenWindowCommand => new RelayCommand(ExecuteOpenWindow);
private void ExecuteOpenWindow(object commandParameter)
{
// TODO::Open the window associated with this instance
MessageBox.Show($"Window {this.Name} is open");
}
public override string ToString() => this.Name;
}
MainWindow.xaml.cs
使用数据绑定从 MainWindow 初始化 ListBox:
partial class MainWindow : Window
{
public ObservableCollection<WindowItem> WindowItems { get; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
var windowItems = new
this.WindowItems = new ObservableCollection<WindowItem>
{
new WindowItem { Name = "Window 1" },
new WindowItem { Name = "Window 2" }
}
}
}
MainWindow.xaml
<Window>
<Window.Resources>
<!-- Command version -->
<ContextMenu x:Key="ListBoxItemContextMenu">
<MenuItem Header="Open"
Command="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.DataContext.OpenWindowCommand}" />
</ContextMenu>
<!-- Optionally define a Click handler in code-behind (MainWindow.xaml.cs) -->
<ContextMenu x:Key="ListBoxItemContextMenu">
<MenuItem Header="Open"
Click="OpenWindow_OnMenuItemClick" />
</ContextMenu>
</Window.Resources>
<ListBox ItemsSource="{Binding WindowItems}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContextMenu" Value="{StaticResource ListBoxItemContextMenu}">
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Window>