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



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

当我右键单击列表框中的项目时,我想要一个以"新建、编辑和删除"作为项目标题的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显示为静态资源

这似乎是我想做的事情。在同一线程中,建议使用以下命令:带命令的上下文菜单

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

所以,目前我在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>

然后是选项卡中的列表框项目:

<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上选择New项时,我希望确定绑定到ListBox的文件的类型。例如:ListBox 1绑定到一个文件类型为B的集合。ListBox 2绑定到一种文件类型为C的集合。当我右键单击ListBox 2中的一个项目并选择"新建"时,我需要制作一个类型为C.的新文件。

问题#3

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

您正朝着正确的方向前进,您的代码只需要一点更新。首先,不需要任何右键单击处理程序——如果控件设置了ContextMenu,则右键单击将调用该ContextMenu。将ContextMenu作为StaticResource并将其附加到多个控件会产生一些问题,因为.NET中有一个错误,ContextMenu在初始设置后不会更新其DataContext。这意味着,如果您首先调用列表框#2上的菜单,您将在该列表框中获得所选项。。。但是,如果您在listbox#3上调用它,您仍然会在listbox#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。像"Edit"one_answers"Delete"这样处理列表项的命令就很容易了:只需将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中,并让您在窗口中的命令调用ViewModel中的代码。不过,在情况变得更加复杂之前,我通常不会这么做。

最新更新