简体   繁体   English

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

[英]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. PlacementTargetContextMenu附加到的控件。 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 )绑定到ListBoxSelectedItem 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM