[英]WPF ContextMenu bound to 3 Listboxes on right-click
I have three tabs and each has a listbox with different types of files. 我有三个选项卡,每个选项卡都有一个包含不同类型文件的列表框。
When I right-click on an item in the listbox, I want a ContextMenu with "New, Edit and Delete" as Item headers. 当我右键单击列表框中的项目时,我需要一个带有“新建,编辑和删除”作为项目标题的ContextMenu。
I guess I could have a ContextMenu for each listbox, and then have a seperate method for each header, such as: 我想我可以为每个列表框都有一个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>
But really, I hope there is a better way. 但实际上,我希望有更好的方法。
I saw this post which shows the ContextMenu as Static Resource 我看到了这篇文章,其中将ContextMenu显示为静态资源
and this seems to be something I would like to do. 这似乎是我想做的事情。 In the same thread it is suggested to use commands: ContextMenu with Commands
建议在同一线程中使用命令: ContextMenu with Commands
and with that I'm hoping I can get the type of the ListBoxItem that was clicked, because I need that. 并且我希望可以得到被单击的ListBoxItem的类型,因为我需要它。 A new file type B must be handled differently than a new file type C, but I don't want a gazillion contextmenus and New/Edit/Delete methods.
新文件类型B的处理方式必须与新文件类型C的处理方式不同,但是我不希望使用大量的contextmenus和New / Edit / Delete方法。
So, currently I have this higher up in my xaml file: 因此,目前我的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>
And then a listbox in the tabItem: 然后在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>
Question #1 问题1
How do I get the rightclick of a ListBoxItem to show the ContextMenu, which is now a static resource? 如何获得ListBoxItem的右键单击以显示ContextMenu(现在是静态资源)? Because in my xaml.cs I had this:
因为在我的xaml.cs中,我有以下内容:
private void ListBox_PreviewMouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// SelectItemOnRightClick(e);
NewEditDeleteContextMenu.PlacementTarget = sender as UIElement;
NewEditDeleteContextMenu.IsOpen = true;
}
But now I have an error saying: 但是现在我有一个错误说:
The name 'NewEditDeleteContextMenu' does not exist in the current context.
名称“ NewEditDeleteContextMenu”在当前上下文中不存在。
because originally I had the contextmenu as part of the ListBox such as: 因为最初我将contextmenu作为ListBox的一部分,例如:
<ListBox.ContextMenu>
...
But as far as I could see that would mean a separate ContextMenu for each ListBox. 但据我所知,这意味着每个ListBox都有一个单独的ContextMenu。
Question #2 问题2
Is the correct way to use a command, let's say NewFileCommand for the New item header in the ContextMenu (shown in the UserControl.Resources block of code) to do the following: 是使用命令的正确方法,比如说对ContextMenu中的New item标头使用NewFileCommand(显示在UserControl.Resources代码块中)执行以下操作:
In my ViewModel: 在我的ViewModel中:
public RelayCommand<string> NewFileCommand { get; private set; }
and then in the ViewModel's constructor: 然后在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.
}
Basically, I want one ContextMenu bound to the different ListBox components, and this should pop up on a rightclick, and when for instance the New item is chosen on the ContextMenu, I want to determine the type of the file that has been bound to the ListBox. 基本上,我想将一个ContextMenu绑定到不同的ListBox组件,并且应该在右键单击时弹出,例如,当在ContextMenu上选择New项时,我想确定已绑定到该文件的文件的类型。列表框。 Eg: ListBox 1 is bound to a collection of file type B. ListBox 2 is bound to a collection of file type C. When I rightclick on an item in ListBox 2, and choose New, I need to make a new file of type C.
例如:ListBox 1绑定到文件类型B的集合。ListBox 2绑定到文件类型C的集合。当我右键单击ListBox 2中的一个项目并选择New时,我需要制作一个C类型的新文件。 。
Question #3 问题#3
This isn't a very intricate View. 这不是一个非常复杂的视图。 I haven't used a MVVM framework because so far I haven't thought that the time it would take me to learn one would be worth it, but considering this scenario, and a simpler case for a double-click on the ListBoxItems that can be seen in one of the blocks of code, would you recommend the use of a framework?
我没有使用MVVM框架,因为到目前为止,我还没有想到花时间去学习它是值得的,但是考虑到这种情况,还有一种更简单的双击ListBoxItems的情况,它可以在其中一个代码块中可以看到,您会建议使用框架吗?
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
. 首先,不需要任何右键单击处理程序-如果控件设置了
ContextMenu
,则右键单击将调用该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. 将
ContextMenu
作为StaticResource
并将其附加到多个控件会造成一些问题,这是因为.NET中存在一个错误,该错误是ContextMenu在初始设置后不会更新其DataContext
。 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. 这意味着,如果您首先在列表框#2上调用菜单,则将在该列表框中获得所选的项目...但是,如果随后在列表框#3上调用它,则仍将在列表框#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}"/>
PlacementTarget
is the control the ContextMenu
is attached to. PlacementTarget
是ContextMenu
附加到的控件。 Explicitly binding the menu's data context to PlacementTarget
ensures it's pointing to the correct ListBox
every time it's invoked. 将菜单的数据上下文明确绑定到
PlacementTarget
可以确保每次调用菜单时都指向正确的ListBox
。 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
. 这样,处理列表项的命令例如“ Edit”和“ Delete”就很简单:只需将
CommandParameter
(而不是像您那样的CommandTarget
)绑定到ListBox
的SelectedItem
。 The item you want to edit or delete will then be given as a parameter to the command. 您要编辑或删除的项目将作为命令的参数给出。
Since you used RelayCommand
I'm assuming you used GalaSoft's MVVM framework. 由于您使用了
RelayCommand
我假设您使用的是GalaSoft的MVVM框架。 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...
}
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. 因此,我们将
CommandParameter
绑定到ListBox
本身。 Unfortunately there's not a good way to get the type of item the ListBox
contains. 不幸的是,没有一种好的方法来获取
ListBox
包含的项目类型。 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
. 你可以给它一个
x:Name
,然后看看你的命令处理程序的名字,但我选择做的就是把这个项目的类型ListBox
处理作为Tag
的参数ListBox
。 Tag
is a bit of extra data you can use for whatever purpose you like: Tag
是一些额外的数据,您可以将其用于任何目的:
<ListBox Margin="10" ItemsSource="{Binding Files1}" ContextMenu="{StaticResource contextMenu}" Tag="{x:Type local:FileTypeA}"/>
Now we can define our "New" command handlers like this: 现在,我们可以像这样定义“ New”命令处理程序:
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...
}
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. 至于此方案是否需要MVVM,您当然可以将三个文件列表以及实际创建,编辑和删除文件的代码放入ViewModel中,并在Window中让您的命令在ViewModel中调用该代码。 I usually don't, though, until the scenario becomes more complicated.
不过,在情况变得更加复杂之前,我通常不会这样做。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.