在执行上下文菜单菜单项时,确定在 ListView 中单击了哪个 ListViewItem

4

我想在一个列表视图中使用上下文菜单来运行一些需要数据的代码,以确定它是从哪个项目启动的。

最初,我只是这样做的:

XAML:

    <ListView x:Name="lvResources" ScrollViewer.VerticalScrollBarVisibility="Visible">
      <ListView.Resources>
        <ContextMenu x:Key="resourceContextMenu">
            <MenuItem Header="Get Metadata" Name="cmMetadata" Click="cmMetadata_Click" />
        </ContextMenu>
      </ListView.Resources>
      <ListView.ItemContainerStyle>
          <Style TargetType="{x:Type ListViewItem}">
              <Setter Property="ContextMenu" Value="{StaticResource resourceContextMenu}" />
          </Style>
      </ListView.ItemContainerStyle>
 ...

C#:

    private void cmMetadata_Click(object sender, RoutedEventArgs e)
    {
      // code that needs item data here
    }

但我发现通过这种方式无法访问原始的列表项。
我阅读了一些关于如何解决这个问题的策略,比如拦截MouseDown事件并将私有字段设置为被单击的listviewitem,但是这种方式似乎有点不妥,因为它似乎是一种通过“传递数据”的hack方法。而且WPF应该很容易,对吧? :) 我阅读了这个SO问题和这个MSDN论坛问题,但我仍然不确定如何真正做到这一点,因为这两篇文章似乎都不能在我的情况下工作。是否有更好的方法将被单击的项传递给上下文菜单呢?
谢谢!
3个回答

4
与Charlie的回答类似,但不需要更改XAML。
private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
    MenuItem menu = sender as MenuItem;
    ListViewItem lvi = lvResources.ItemContainerGenerator.ContainerFromItem(menu.DataContext) as ListViewItem;
}

这是一种很好的简单方法,可以从上下文菜单中获取ListViewItem! - Chuck Savage

3
在cmMetadata_Click处理程序中,您只需查询lvResources.SelectedItem属性即可,因为lvResources将从位于单击处理程序的代码后台文件中访问。这不是很优雅,但它可以工作。
如果您想更加优雅一些,您可以更改设置ContextMenu的位置。例如,您可以尝试以下内容:
<ListView x:Name="lvResources" ScrollViewer.VerticalScrollBarVisibility="Visible">
 <ListView.Style>
  <Style TargetType="ListView">
   <Setter Property="ItemContainerStyle">
    <Setter.Value>
     <Style TargetType="{x:Type ListViewItem}">
      <Setter Property="Template">
       <Setter.Value>
        <ControlTemplate TargetType="{x:Type ListViewItem}">
         <TextBlock Text="{TemplateBinding Content}">
          <TextBlock.ContextMenu>
           <ContextMenu>
            <MenuItem Header="Get Metadata" Name="cmMetadata" Click="cmMetadata_Click" 
             DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"/>
           </ContextMenu>
          </TextBlock.ContextMenu>
         </TextBlock>
        </ControlTemplate>
       </Setter.Value>
      </Setter>
     </Style>
    </Setter.Value>
   </Setter>
  </Style>
 </ListView.Style>
 <ListViewItem>One Item</ListViewItem>
 <ListViewItem>Another item</ListViewItem>
</ListView>

这个操作是为你的ListViewItem插入一个模板,然后你可以使用方便的TemplatedParent快捷方式将ListViewItem分配给菜单项的数据上下文。

现在你的代码看起来像这样:

private void cmMetadata_Click(object sender, RoutedEventArgs e)
{
    MenuItem menu = sender as MenuItem;
    ListViewItem item = menu.DataContext as ListViewItem;
}

显然,缺点是现在您需要完成ListViewItem的模板,但我相信您可以很快找到一个适合您需求的。


我想指出你提到了仅使用listview选择来确定点击了什么。在用户选择了多个项目但仍希望仅对他们单击的项目执行代码的情况下,这种方法并不奏效,因为在这种情况下你无法检测到用户所单击的项目。 - patjbs

1

所以我决定尝试实现一个命令解决方案。现在它的工作效果相当不错。

首先,创建了我的命令:

public static class CustomCommands
{
    public static RoutedCommand DisplayMetadata = new RoutedCommand();
}

接下来在我的自定义列表视图控件中,我在构造函数中添加了一个新的命令绑定:

public SortableListView()
{
    CommandBindings.Add(new CommandBinding(CustomCommands.DisplayMetadata, DisplayMetadataExecuted, DisplayMetadataCanExecute));
}

同时,在那里添加了事件处理程序:

public void DisplayMetadataExecuted(object sender, ExecutedRoutedEventArgs e)
{
    var nbSelectedItem = (MyItem)e.Parameter;

    // do stuff with selected item
}

public void DisplayMetadataCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
    e.Handled = true;
}

我已经在使用样式选择器来动态地为列表视图项分配样式,因此我不必在XAML中这样做,而是必须在代码后台设置绑定。当然,你也可以在XAML中完成它:

public override Style SelectStyle(object item, DependencyObject container)
{
    ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(container);
    MyItem selectedItem = (MyItem)item;
    Style s = new Style();

    var listMenuItems = new List<MenuItem>();
    var mi = new MenuItem();
    mi.Header= "Get Metadata";
    mi.Name= "cmMetadata";
    mi.Command = CustomCommands.DisplayMetadata;
    mi.CommandParameter = selectedItem;
    listMenuItems.Add(mi);

    ContextMenu cm = new ContextMenu();
    cm.ItemsSource = listMenuItems;

    // Global styles
    s.Setters.Add(new Setter(Control.ContextMenuProperty, cm));

    // other style selection code

    return s;
}

我更喜欢这个解决方案的感觉,而不是尝试在鼠标单击时设置一个字段并尝试以那种方式访问所点击的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接