且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

右键单击将WPF ContextMenu绑定到3个列表框

更新时间:2022-05-03 02:42:46

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

You're going in the right direction, you code just needs a bit of updating. First, don't need any right-click handlers -- if a control has a ContextMenu set, right-clicking will invoke that ContextMenu. Having a ContextMenu as a StaticResource and attaching it to multiple controls creates a bit of a problem because of a bug in .NET where a ContextMenu doesn't update its DataContext after initially setting it. That means if you first invoke the menu on listbox #2, you'll get the selected item in that listbox... but if you then invoke it on listbox #3, you'll still get the selected item in listbox #2. But there's a way around this.

首先,让我们看一下上下文菜单及其与列表框的绑定方式:

First, let's look at the context menu and how it's bound to a list box:

<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.您要编辑或删除的项目将作为命令的参数给出.

PlacementTarget is the control the ContextMenu is attached to. Explicitly binding the menu's data context to PlacementTarget ensures it's pointing to the correct ListBox every time it's invoked. Commands like "Edit" and "Delete" that deal with list items are then easy: Just bind the CommandParameter (not the CommandTarget as you did) to the ListBox's SelectedItem. The item you want to edit or delete will then be given as a parameter to the command.

由于您使用了RelayCommand,所以我假设您使用的是GalaSoft的MVVM框架.在这种情况下,删除"命令的外观如下:

Since you used RelayCommand I'm assuming you used GalaSoft's MVVM framework. In that case here's how your "Delete" command might look:

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是一些额外的数据,您可以根据自己的喜好使用它们

The "New" command will be a bit tricker because you want to be able to create a new item whether an item is selected or not. So we'll bind the CommandParameter to the ListBox itself. Unfortunately there's not a good way to get the type of item the ListBox contains. It could contain multiple types of items, or no items at all. You could give it an x:Name then look at the name in your command handler, but what I choose to do is put the type of item this ListBox handles as the Tag parameter of the ListBox. Tag is a bit of extra data you can use for whatever purpose you like:

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

现在,我们可以像这样定义"New"命令处理程序:

Now we can define our "New" command handlers like this:

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中,并在Window中让您的命令调用该代码在ViewModel中.不过,在情况变得更加复杂之前,我通常不会这样做.

As for whether this scenario warrants an MVVM or not, you can certainly put your three file lists in a ViewModel, along with code that actually creates, edits, and deletes the files, and have your commands in the Window invoke the code in the ViewModel. I usually don't, though, until the scenario becomes more complicated.