【问题标题】:WPF ContextMenu bound to 3 Listboxes on right-click右键单击时 WPF ContextMenu 绑定到 3 个列表框
【发布时间】:2017-01-04 13:40:03
【问题描述】:

我有三个选项卡,每个选项卡都有一个包含不同类型文件的列表框。

当我右键单击列表框中的项目时,我想要一个带有“新建、编辑和删除”作为项目标题的 ContextMenu。

我想我可以为每个列表框设置一个 ContextMenu,然后为每个标题设置一个单独的方法,例如:

               <ListBox.ContextMenu>
                    <ContextMenu x:Name="NewEditDeleteAdvCalcFileContextMenu">
                        <MenuItem Name="NewAdv" Header="New" Click="NewAdv_Click" />
                        <MenuItem Name="EditAdv" Header="Edit" Click="EditAdv_Click"/>
                        <MenuItem Name="DeleteAdv" Header="Delete" Click="DeleteAdv_Click"/>
                    </ContextMenu>
                </ListBox.ContextMenu>

但真的,我希望有更好的方法。

我看到这篇帖子显示ContextMenu as Static Resource

这似乎是我想做的事情。 在同一个线程中,建议使用命令: ContextMenu with Commands

我希望我能得到被点击的 ListBoxItem 的类型,因为我需要它。新文件类型 B 的处理方式必须与新文件类型 C 不同,但我不想要大量的上下文菜单和新建/编辑/删除方法。

所以,目前我的 xaml 文件中有更高的内容:

<UserControl.Resources>
    <ContextMenu x:Key="NewEditDeleteContextMenu">
        <MenuItem Header="New" 
                  Command="{Binding Path=NewFileCommand}"  
                  CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"/>
        <MenuItem Header="Edit" 
                  Command="{Binding Path=EditFileCommand}"  
                  CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"/>
        <MenuItem Header="Delete" 
                  Command="{Binding Path=DeleteFileCommand}"  
                  CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"/>
    </ContextMenu>
</UserControl.Resources>

然后是tabItem中的一个列表框:

<ListBox Name="CalcFilesListBox" 
                     Margin="20" ItemsSource="{Binding CalcFilesList}" 
                     PreviewMouseRightButtonUp="ListBox_PreviewMouseRightButtonUp" 
                     ContextMenu="{StaticResource NewEditDeleteContextMenu}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Path=Name}" />
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
                <ListBox.ItemContainerStyle>
                    <Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
                        <EventSetter Event="MouseDoubleClick" Handler="CalcFileListBox_MouseDoubleClick"/>
                    </Style>
                </ListBox.ItemContainerStyle>
            </ListBox>

问题 #1

如何右键单击 ListBoxItem 以显示 ContextMenu,它现在是静态资源? 因为在我的 xaml.cs 我有这个:

private void ListBox_PreviewMouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        // SelectItemOnRightClick(e);
        NewEditDeleteContextMenu.PlacementTarget = sender as UIElement;
        NewEditDeleteContextMenu.IsOpen = true;

    }

但现在我有一个错误说:

当前上下文中不存在名称“NewEditDeleteContextMenu”。

因为最初我将上下文菜单作为 ListBox 的一部分,例如:

<ListBox.ContextMenu>
...

但据我所知,这意味着每个 ListBox 都有一个单独的 ContextMenu。

问题 #2

是使用命令的正确方法,假设为 ContextMenu 中的 New 项标题的 NewFileCommand(显示在 UserControl.Resources 代码块中)执行以下操作:

在我的 ViewModel 中:

 public RelayCommand<string> NewFileCommand { get; private set; }

然后在 ViewModel 的构造函数中:

 public CalcViewModel()
    {
        NewFileCommand = new RelayCommand<object>(NewFile);
    }

 public void NewFile(object sender)
    {
         //Determine the type of file, based on the ListBoxItem's DataContext. 
That is, supposing the ListBoxItem is the object being passed as the sender.
    } 

基本上,我想要一个 ContextMenu 绑定到不同的 ListBox 组件,这应该会在右键单击时弹出,例如,当在 ContextMenu 上选择新项目时,我想确定文件的类型绑定到列表框。 例如:ListBox 1 绑定到文件类型 B 的集合。ListBox 2 绑定到文件类型 C 的集合。当我右键单击 ListBox 2 中的一个项目并选择新建时,我需要创建一个 C 类型的新文件.

问题 #3

这不是一个非常复杂的视图。我没有使用过 MVVM 框架,因为到目前为止,我认为花时间学习一个框架是值得的,但考虑到这种情况,以及双击 ListBoxItems 的更简单的情况在其中一个代码块中看到,您会推荐使用框架吗?

【问题讨论】:

    标签: wpf xaml mvvm listbox contextmenu


    【解决方案1】:

    您正朝着正确的方向前进,您的代码只需要一点更新。首先,不需要任何右键单击处理程序——如果控件设置了ContextMenu,则右键单击将调用该ContextMenu。将ContextMenu 作为StaticResource 并将其附加到多个控件会产生一些问题,因为.NET 中存在一个错误,即ContextMenu 在初始设置后不会更新其DataContext。这意味着如果您首先在列表框#2 上调用菜单,您将在该列表框中获得选定的项目……但是如果您随后在列表框#3 上调用它,您仍然会在列表框#2 中获得选定的项目。但是有办法解决这个问题。

    首先,让我们看一下上下文菜单以及它是如何绑定到列表框的:

    <ContextMenu x:Key="contextMenu" DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
        <MenuItem Header="New" Command="{Binding DataContext.NewFileCommand}" CommandParameter="{Binding}"/>
        <MenuItem Header="Delete" Command="{Binding DataContext.DeleteFileCommand}" CommandParameter="{Binding SelectedItem}"/>
    </ContextMenu>
    
    ...
    
    <ListBox Margin="10" ItemsSource="{Binding Files1}" ContextMenu="{StaticResource contextMenu}"/>
    

    PlacementTargetContextMenu 附加到的控件。将菜单的数据上下文显式绑定到PlacementTarget 可确保每次调用它时都指向正确的ListBox。处理列表项的诸如“编辑”和“删除”之类的命令很容易:只需将CommandParameter(而不是像您那样的CommandTarget)绑定到ListBoxSelectedItem。然后,您要编辑或删除的项目将作为参数提供给命令。

    由于您使用了RelayCommand,我假设您使用了 GalaSoft 的 MVVM 框架。在这种情况下,您的“删除”命令可能如下所示:

    public RelayCommand<object> DeleteFileCommand { get; } = new RelayCommand<object>( DeleteFile_Executed, DeleteFile_CanExecute );
    
    private static bool DeleteFile_CanExecute( object file )
    {
        return file != null;
    }
    
    private static void DeleteFile_Executed( object file )
    {
        var filetype = file.GetType();
        System.Diagnostics.Debug.WriteLine( string.Format( "Deleting file {0} of type {1}", file, file.GetType() ) );
    
        // if( filetype == typeof( FileTypeA ) ) DeleteFileTypeA( file as FileTypeA );
        // else if( filetype == typeof( FileTypeB ) ) DeleteFileTypeB( file as FileTypeB );
        // etc...
    }
    

    “新建”命令会有点棘手,因为无论是否选择了一个项目,您都希望能够创建一个新项目。所以我们将CommandParameter 绑定到ListBox 本身。不幸的是,没有很好的方法来获取 ListBox 包含的项目类型。它可以包含多种类型的项目,或者根本没有项目。您可以给它一个x:Name,然后查看命令处理程序中的名称,但我选择将这个ListBox 处理的项目类型作为ListBoxTag 参数。 Tag 是一些额外的数据,您可以将其用于任何您喜欢的目的:

    <ListBox Margin="10" ItemsSource="{Binding Files1}" ContextMenu="{StaticResource contextMenu}" Tag="{x:Type local:FileTypeA}"/>
    

    现在我们可以像这样定义我们的“新”命令处理程序:

    private static bool NewFile_CanExecute( ListBox listbox ) { return true; }
    
    private static void NewFile_Executed( ListBox listbox )
    {
        var filetype = listbox.Tag as Type;
    
        System.Diagnostics.Debug.WriteLine( string.Format( "Creating new file of type {0}", filetype ) );
    
        // if( filetype == typeof( FileTypeA ) ) CreateNewFileTypeA();
        // else if( filetype == typeof( FileTypeB ) ) CreateNewFileTypeB();
        // etc...
    }
    

    至于这种情况是否需要 MVVM,您当然可以将您的三个文件列表与实际创建、编辑和删除文件的代码一起放在 ViewModel 中,并让您在窗口中的命令调用代码在视图模型中。不过,在情况变得更复杂之前,我通常不会这样做。

    【讨论】:

    • 本周早些时候我很想感谢你,但我仍在更改一些内容并重构代码。测试完所有内容后,我会将其标记为答案。我在其中一个选项卡中也有一个树视图,但我认为我会首先正确设置列表框,因为它们可能更容易。我不使用 GalaSoft 的 MVVM 框架,但我正在尝试将您的答案转化为我所拥有的。所以,我可能会要求进一步澄清,但非常感谢你,你帮了我很大的忙!
    • 好的,那么是否也可以与树视图共享上下文菜单?我实际上有 2 个列表框和一个树视图。我预见到新命令会出现问题,我使用列表框上的标签来发送文件类型。如果我向命令发送一个对象(而不是列表框),那么我会丢失 Tag 属性,不是吗?否则我可以为我猜的树视图添加一个单独的上下文菜单。
    • 我想你可以。你试过了吗?假设您的树视图仅包含一种类型的项目,您仍然可以使用 Tag 方法——只需在 CanExecute 和 Execute 方法中使用超类 FrameworkElement 而不是 ListBox。
    猜你喜欢
    • 2011-07-20
    • 1970-01-01
    • 1970-01-01
    • 2011-06-16
    • 2019-09-13
    • 1970-01-01
    • 2018-06-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多