【问题标题】:Click Event not firing on ListBoxItem ContextMenu单击事件未在 ListBoxItem ContextMenu 上触发
【发布时间】:2021-07-29 13:48:37
【问题描述】:

我正在尝试将ContextMenu 添加到ListBoxItem,但Click 事件未触发。

我尝试了ContextMenu.MenuItem.Click 事件。 我也尝试绑定Command,但是在输出窗口中出现绑定错误,如下:

“无法通过引用 '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?

【问题讨论】:

  • 我认为没有必要编写“hackish”代码。我认为你应该考虑改进你的代码。上下文菜单应该只在其上下文中起作用,因此命名为上下文菜单。当您右键单击代码编辑器时,您将找不到例如“打开文件”菜单项 - 这将脱离上下文。菜单的上下文是实际项目。因此,命令应该在项目的数据模型中定义(因为上下文菜单应该只定义以项目为目标的命令)。
  • @BionicCode 你能给我一个正确使用的例子吗?例如,我的列表框由我可以打开的窗口名称列表组成。当我右键单击并打开相关窗口时,它脱离了上下文。或者你能给我一个没有断章取义的例子吗
  • 我已经发布了一个例子。您可以看到解决方案简单直观。无需“滥用” Tag 属性或定义任何 MenuItem 样式来附加事件处理程序。

标签: c# wpf xaml contextmenu listboxitem


【解决方案1】:

ContextMenu 与您的ListBox 不属于同一可视树,因为它显示在单独的窗口中。因此使用RelativeSource 或ElementName 绑定不能直接工作。但是,您可以通过将Window(在代码隐藏中定义NavigateCommand)与RelativeSource 绑定到ListBoxItem 的Tag 属性来解决此问题。这是可行的,因为它们是同一可视树的一部分。 Tag 属性是一个通用属性,您可以为其分配任何内容。

获取或设置可用于存储有关此元素的自定义信息的任意对象值。

然后使用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 中,则必须以不同的方式添加它,否则它将不起作用,并且会出现这个奇怪的异常。

无法将 System.Windows.Controls.MenuItem 类型的对象转换为 System.Windows.Controls.Button 类型。

添加带有Click 的事件设置器的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>

【讨论】:

  • 感谢您的详细解释。我还有一个问题。假设我们不使用 DataContext,假设我们只是用代码隐藏编写代码,所以我想将窗口本身绑定到标记属性,而不是 DataContext。我该怎么做
  • @ErhanUrun 我已经编辑了代码。以前,绑定是{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type Window}}},它绑定了Window 的DataContext。现在是 {Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}} 绑定 Window 本身。
【解决方案2】:

我认为没有必要编写“hackish”代码。我认为你应该改进你的代码。

上下文菜单只能在其当前的 UI 上下文中操作,因此得名上下文菜单。当您右键单击代码编辑器时,您将找不到例如“打开文件”菜单项 - 这将脱离上下文。

菜单的上下文是实际的项目。因此,该命令应该在项目的数据模型中定义,因为上下文菜单应该只定义以项目为目标的命令。

请注意,ContextMenu 始终隐式托管在 Popup 内(如 Child 的 Popup)。 Popup 的子元素永远不能成为可视化树的一部分(因为内容在专用的 Window 实例中呈现,而 Window 不能是另一个元素的子元素,Window 始终是其根自己的可视化树),因此无法使用Binding.RelativeSource 进行树遍历。
您可以参考ContextMenu.PlacementTarget(而不是DataContext)来获取实际的ListBoxItem(放置目标)。 ListBoxItem 的DataContext 是数据模型(下例中的WindowItem 类)。

例如引用 e.g.当前ListBoxItem的底层数据模型OpenWindowCommand从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>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-11
    • 2018-01-30
    • 2020-08-10
    • 1970-01-01
    • 2014-01-14
    相关资源
    最近更新 更多