右键单击将WPF ContextMenu绑定到3个列表框 [英] WPF ContextMenu bound to 3 Listboxes on right-click

查看:67
本文介绍了右键单击将WPF ContextMenu绑定到3个列表框的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have three tabs and each has a listbox with different types of files.

当我右键单击列表框中的项目时,我希望使用带有新建,编辑和删除"作为项目标题的ContextMenu.

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:

               <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.

我看到了这篇帖子,其中显示了 ContextMenu作为静态资源

I saw this post which shows the ContextMenu as Static Resource

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

and this seems to be something I would like to do. In the same thread it is suggested to use commands: ContextMenu with Commands

并且我希望可以得到被单击的ListBoxItem的类型,因为我需要它.新文件类型B的处理方式必须与新文件类型C的处理方式不同,但我不希望使用大量的上下文菜单和新建/编辑/删除"方法.

and with that I'm hoping I can get the type of the ListBoxItem that was clicked, because I need that. 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.

所以,目前我在我的xaml文件中有这个更高的版本:

So, currently I have this higher up in my xaml file:

<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中添加一个列表框:

And then a listbox in the 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中,我有这个:

How do I get the rightclick of a ListBoxItem to show the ContextMenu, which is now a static resource? Because in my xaml.cs I had this:

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:

名称"NewEditDeleteContextMenu"在当前上下文中不存在.

The name 'NewEditDeleteContextMenu' does not exist in the current context.

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

because originally I had the contextmenu as part of the ListBox such as:

<ListBox.ContextMenu>
...

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

But as far as I could see that would mean a separate ContextMenu for each ListBox.

问题2

使用命令是正确的方法,例如,对于ContextMenu(显示在UserControl.Resources代码块中)中的New项标题,请使用NewFileCommand来执行以下操作:

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:

在我的ViewModel中:

In my ViewModel:

 public RelayCommand<string> NewFileCommand { get; private set; }

,然后在ViewModel的构造函数中:

and then in the ViewModel's constructor:

 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类型的新文件.

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. E.g.: 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.

问题#3

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

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?

推荐答案

您的发展方向正确,您的代码只需要一点点更新即可.首先,不需要任何右键单击处理程序-如果控件设置了ContextMenu,则右键单击将调用该ContextMenu.将ContextMenu作为StaticResource并将其附加到多个控件会造成一些问题,这是因为.NET中存在一个错误,该错误是ContextMenu在初始设置后不会更新其DataContext.这意味着,如果您首先在列表框#2上调用菜单,则将在该列表框中获得所选的项目...但是,如果您随后在列表框#3上调用它,则仍将在列表框#2中获得所选的项目.但这是可以解决的.

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. 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. 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. 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}"/>

PlacementTargetContextMenu所连接的控件.将菜单的数据上下文显式绑定到PlacementTarget可以确保每次调用菜单时都指向正确的ListBox.这样,处理列表项的编辑"和删除"之类的命令就很简单:只需将CommandParameter(而不是像您一样的CommandTarget)绑定到ListBoxSelectedItem.您要编辑或删除的项目将作为命令的参数给出.

PlacementTarget is the control the ContextMenu is attached to. Explicitly binding the menu's data context to PlacementTarget ensures it's pointing to the correct ListBox every time it's invoked. 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. The item you want to edit or delete will then be given as a parameter to the command.

由于您使用了RelayCommand,所以我假设您使用的是GalaSoft的MVVM框架.在这种情况下,删除"命令的外观如下:

Since you used RelayCommand I'm assuming you used GalaSoft's MVVM framework. 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...
}

新建"命令会有些麻烦,因为无论是否选中某个项目,您都希望能够创建一个新项目.因此,我们将CommandParameter绑定到ListBox本身.不幸的是,没有一种很好的方法来获取ListBox所包含的项目的类型.它可能包含多种类型的项目,或者根本不包含任何项目.您可以给它一个x:Name,然后在命令处理程序中查看它的名称,但是我选择做的是将此ListBox处理的项目类型作为ListBoxTag参数. Tag是一些额外的数据,您可以根据自己的喜好使用它们

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. Unfortunately there's not a good way to get the type of item the ListBox contains. 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. Tag is a bit of extra data you can use for whatever purpose you like:

<ListBox Margin="10" ItemsSource="{Binding Files1}" ContextMenu="{StaticResource contextMenu}" Tag="{x:Type local:FileTypeA}"/>

现在,我们可以像这样定义"New"命令处理程序:

Now we can define our "New" command handlers like this:

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中.不过,在情况变得更加复杂之前,我通常不会这样做.

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. I usually don't, though, until the scenario becomes more complicated.

这篇关于右键单击将WPF ContextMenu绑定到3个列表框的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆