右键单击

时间:2017-01-04 13:40:03

标签: wpf xaml mvvm listbox contextmenu

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

当我右键单击列表框中的某个项目时,我想要一个ContextMenu,其中包含" New,Edit和Delete"作为项目标题。

我想我可以为每个列表框设置一个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;

    }

但现在我有一个错误说:

  

名称&#39; NewEditDeleteContextMenu&#39;在当前背景下不存在。

因为最初我将contextmenu作为ListBox的一部分,例如:

<ListBox.ContextMenu>
...

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

问题#2

使用命令的正确方法是,让我们在ContextMenu(显示在UserControl.Resources代码块中)的New item标题中使用NewFileCommand来执行以下操作:

在我的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中的项目,并选择New时,我需要创建一个C类型的新文件。

问题#3

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

1 个答案:

答案 0 :(得分:0)

你正朝着正确的方向前进,你的代码需要进行一些更新。首先,不需要任何右键单击处理程序 - 如果控件具有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)绑定到ListBox的{​​{1} }}。然后,您要编辑或删除的项目将作为命令的参数提供。

因为您使用了SelectedItem我假设您使用了GalaSoft的MVVM框架。在这种情况下,这是“删除”命令的外观:

RelayCommand

“新”命令会有点诡计,因为您希望能够创建新项目,无论是否选择了项目。因此,我们会将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参数{ {1}}。 Tag是您可以用于任何目的的额外数据:

ListBox

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

Tag

至于这个场景是否保证MVVM,您当然可以将三个文件列表放在ViewModel中,以及实际创建,编辑和删除文件的代码,并让您在Window中的命令调用代码在ViewModel中。但是,我通常不这样做,直到情景变得更加复杂。