WPF 上下文菜单绑定到 right-click 上的 3 个列表框
WPF ContextMenu bound to 3 Listboxes on right-click
我有三个选项卡,每个选项卡都有一个包含不同类型文件的列表框。
当我 right-click 在列表框中的项目上时,我想要一个带有 "New, Edit and Delete" 作为项目 header 的上下文菜单。
我想我可以为每个列表框设置一个 ContextMenu,然后为每个列表框设置一个单独的方法 header,例如:
<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>
但真的,我希望有更好的方法。
我看到这个 post 显示 ContextMenu as Static Resource
这似乎是我想做的事情。
在同一个线程中,建议使用命令:
ContextMenu with Commands
因此我希望我可以获得被单击的 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>
然后是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 以显示现在是静态资源的上下文菜单?
因为在我的 xaml.cs 中我有这个:
private void ListBox_PreviewMouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// SelectItemOnRightClick(e);
NewEditDeleteContextMenu.PlacementTarget = sender as UIElement;
NewEditDeleteContextMenu.IsOpen = true;
}
但现在我有一个错误说:
The name 'NewEditDeleteContextMenu' does not exist in the current context.
因为最初我将上下文菜单作为列表框的一部分,例如:
<ListBox.ContextMenu>
...
但据我所知,这意味着每个 ListBox 都有一个单独的上下文菜单。
问题 #2
是使用命令的正确方法,假设 NewFileCommand 用于 ContextMenu 中的新项目 header(显示在 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 的 collection。ListBox 2 绑定到文件类型 C 的 collection。当我右键单击 ListBox 2 中的项目并选择新建时,我需要制作 C 类型的新文件。
问题 #3
这不是一个非常复杂的视图。我没有使用 MVVM 框架,因为到目前为止我还没有想到花时间学习一个框架是值得的,但考虑到这种情况,以及 ListBoxItems 上 double-click 的更简单的情况可以在其中一个代码块中看到,您会推荐使用框架吗?
您的方向是正确的,您的代码只需要更新一下。首先,不需要任何右键单击处理程序——如果控件设置了 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}"/>
PlacementTarget
是 ContextMenu
附加到的控件。将菜单的数据上下文显式绑定到 PlacementTarget
可确保每次调用时它都指向正确的 ListBox
。像 "Edit" 和 "Delete" 这样处理列表项的命令就很简单了:只需将 CommandParameter
(而不是 CommandTarget
绑定到 ListBox
' s SelectedItem
。然后,您要编辑或删除的项目将作为参数提供给命令。
因为您使用了 RelayCommand
我假设您使用了 GalaSoft 的 MVVM 框架。在这种情况下,您的 "Delete" 命令可能如下所示:
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...
}
"New" 命令会有点诡计,因为无论是否选择项目,您都希望能够创建一个新项目。所以我们将 CommandParameter
绑定到 ListBox
本身。不幸的是,没有一个好的方法来获取 ListBox
包含的项目类型。它可以包含多种类型的项目,或者根本没有项目。你可以给它一个 x:Name
然后在你的命令处理程序中查看名称,但我选择做的是把这个 ListBox
处理的项目类型作为 Tag
参数 ListBox
。 Tag
是一些额外的数据,您可以将其用于您喜欢的任何目的:
<ListBox Margin="10" ItemsSource="{Binding Files1}" ContextMenu="{StaticResource contextMenu}" Tag="{x:Type local:FileTypeA}"/>
现在我们可以像这样定义 "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...
}
至于这种情况是否需要 MVVM,您当然可以将三个文件列表放在一个 ViewModel 中,连同实际创建、编辑和删除文件的代码,并将您的命令放在 Window 调用 ViewModel 中的代码。不过,在情况变得更加复杂之前,我通常不会这样做。
我有三个选项卡,每个选项卡都有一个包含不同类型文件的列表框。
当我 right-click 在列表框中的项目上时,我想要一个带有 "New, Edit and Delete" 作为项目 header 的上下文菜单。
我想我可以为每个列表框设置一个 ContextMenu,然后为每个列表框设置一个单独的方法 header,例如:
<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>
但真的,我希望有更好的方法。
我看到这个 post 显示 ContextMenu as Static Resource
这似乎是我想做的事情。 在同一个线程中,建议使用命令: ContextMenu with Commands
因此我希望我可以获得被单击的 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>
然后是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 以显示现在是静态资源的上下文菜单? 因为在我的 xaml.cs 中我有这个:
private void ListBox_PreviewMouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// SelectItemOnRightClick(e);
NewEditDeleteContextMenu.PlacementTarget = sender as UIElement;
NewEditDeleteContextMenu.IsOpen = true;
}
但现在我有一个错误说:
The name 'NewEditDeleteContextMenu' does not exist in the current context.
因为最初我将上下文菜单作为列表框的一部分,例如:
<ListBox.ContextMenu>
...
但据我所知,这意味着每个 ListBox 都有一个单独的上下文菜单。
问题 #2
是使用命令的正确方法,假设 NewFileCommand 用于 ContextMenu 中的新项目 header(显示在 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 的 collection。ListBox 2 绑定到文件类型 C 的 collection。当我右键单击 ListBox 2 中的项目并选择新建时,我需要制作 C 类型的新文件。
问题 #3
这不是一个非常复杂的视图。我没有使用 MVVM 框架,因为到目前为止我还没有想到花时间学习一个框架是值得的,但考虑到这种情况,以及 ListBoxItems 上 double-click 的更简单的情况可以在其中一个代码块中看到,您会推荐使用框架吗?
您的方向是正确的,您的代码只需要更新一下。首先,不需要任何右键单击处理程序——如果控件设置了 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}"/>
PlacementTarget
是 ContextMenu
附加到的控件。将菜单的数据上下文显式绑定到 PlacementTarget
可确保每次调用时它都指向正确的 ListBox
。像 "Edit" 和 "Delete" 这样处理列表项的命令就很简单了:只需将 CommandParameter
(而不是 CommandTarget
绑定到 ListBox
' s SelectedItem
。然后,您要编辑或删除的项目将作为参数提供给命令。
因为您使用了 RelayCommand
我假设您使用了 GalaSoft 的 MVVM 框架。在这种情况下,您的 "Delete" 命令可能如下所示:
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...
}
"New" 命令会有点诡计,因为无论是否选择项目,您都希望能够创建一个新项目。所以我们将 CommandParameter
绑定到 ListBox
本身。不幸的是,没有一个好的方法来获取 ListBox
包含的项目类型。它可以包含多种类型的项目,或者根本没有项目。你可以给它一个 x:Name
然后在你的命令处理程序中查看名称,但我选择做的是把这个 ListBox
处理的项目类型作为 Tag
参数 ListBox
。 Tag
是一些额外的数据,您可以将其用于您喜欢的任何目的:
<ListBox Margin="10" ItemsSource="{Binding Files1}" ContextMenu="{StaticResource contextMenu}" Tag="{x:Type local:FileTypeA}"/>
现在我们可以像这样定义 "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...
}
至于这种情况是否需要 MVVM,您当然可以将三个文件列表放在一个 ViewModel 中,连同实际创建、编辑和删除文件的代码,并将您的命令放在 Window 调用 ViewModel 中的代码。不过,在情况变得更加复杂之前,我通常不会这样做。