里面的ItemsControl绑定上下文菜单? [英] Bind Context Menu inside ItemsControl?

查看:116
本文介绍了里面的ItemsControl绑定上下文菜单?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我如何添加一个文本菜单到的ItemsControl,其中:

How can I add a ContextMenu to a ItemsControl, where:


  • 中的ItemsControl的ItemsSource的是在该用户持有的视图模型ItemsControl的

  • 的上下文菜单的CommandParameter是视图模型绑定到ItemsControl中的项目。

我跟着这种方法。不过,我从绑定到我的ItemsControl中的ObservableCollection删除项目的命令。当这种情况发生的一个例外是RelayCommand内抛出。在我看来,作为文本菜单未隐藏,所以它试图评估CanExecute,其命令,但该项目已被删除它不能在RelayCommand的CanExecute法铸造的参数设置为T类。

I followed this approach. However, I have a Command that Removes Items from the ObservableCollection binded to my ItemsControl. When this happen an exception is thrown inside the RelayCommand. It seems to me as the ContextMenu is not "hiding", so it tries to evaluate the "CanExecute" for its commands, but as the item has been removed it can not cast the parameter to "T" in the CanExecute method of the RelayCommand Class.

我想知道是如何完成我需要正确的方法。

我实施至今:

MainViewModel

public class MainViewModel
{
    public ObservableCollection<MyContextMenuClass> ContextMenuItems{ get;set; }
    public ObservableCollection<MyItemClass> MyItems{ get;set; }

    public void AddItem(MyItemClass item)
    {
        MyItems.Add(item);
    }

    public void AddContextMenuItem(MyContextMenuClass item)
    {
        ContextMenuItems.Add(item);
    }

    public MainViewModel(IList<MyItemClass> myItems, IList<MyContextMenuClass> myContextualMenuItems)
    {
        MyItems.AddRange(myItems);
        ContextMenuItems.AddRange(myContextualMenuItems);
    }

    public MainViewModel()
    {}
}

MyItemClass

public class MyItemClass
{
    public string MyText{get;set;}
}

MyContextMenuClass

public class MyContextMenuClass 
{
    public RecentContextMenuItem()
    {}
    public string Caption{get;set;}

    public RelayCommand<MyItemClass> Command{get;set;}        
}

我的用户控件 (DataContext的= MainViewModel)

My UserControl (DataContext = MainViewModel)

<UserControl x:Class="MyNamespace.MyUserControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"                         
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>        
     <Style x:Key="CommandMenuItemStyle" TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
                    <Setter Property="MenuItem.Header" Value="{Binding Caption}" />
                    <Setter Property="MenuItem.Command" Value="{Binding Command}" />
                    <Setter Property="MenuItem.CommandParameter" Value="{Binding PlacementTarget.DataContext, 
                        RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />                        
                </Style>

                <ContextMenu x:Key="ItemContextMenu" ItemsSource="{Binding ContextMenuItems}" 
                 ItemContainerStyle="{StaticResource CommandMenuItemStyle}"
                 DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>

</UserControl.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>            
    </Grid.RowDefinitions>

    <TextBlock Grid.Row="0" Text="{Binding Title}" Margin="20,5,0,5" Foreground="#FF5D5858" FontFamily="Courier" FontSize="15" Grid.ColumnSpan="2" FontWeight="SemiBold"></TextBlock>

    <ScrollViewer VerticalScrollBarVisibility="Auto" Grid.Row="2" Padding="5,0,0,0">
        <ItemsControl x:Name="myItems" ItemsSource="{Binding MyItems}" >
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding MyText}" /> <!--Simplied this for the example-->                  
                </DataTemplate>                    
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemContainerStyle>
                <Style TargetType="{x:Type ContentPresenter}">
                    <EventSetter Event="ContextMenu.ContextMenuOpening" Handler="Item_ContextMenuOpening"></EventSetter>
                </Style>
            </ItemsControl.ItemContainerStyle>
        </ItemsControl>
    </ScrollViewer>
</Grid>



我的用户控件代码隐藏

public partial class MyUserControl : UserControl
{
    /// <summary>
    /// Initializes a new instance of the <see cref="RecentView"/> class.
    /// </summary>
    public MyUserControl()
    {
        InitializeComponent();
    }

    private void Item_ContextMenuOpening(object sender, ContextMenuEventArgs e)
    {
        var contentPresenter = sender as ContentPresenter;
        if (contentPresenter != null)
        {
            this.Dispatcher.BeginInvoke(new Action<ContentPresenter>(ShowItemContextMenu), new object[] { contentPresenter });
        }
    }

    private void ShowItemContextMenu(ContentPresenter sourceContentPresenter)
    {
        if (sourceContentPresenter != null)
        {
            var ctxMenu = (ContextMenu)this.FindResource("ItemContextMenu");
            ctxMenu.DataContext = this.DataContext;

            if (ctxMenu.Items.Count == 0)
            {
                sourceContentPresenter.ContextMenu = null;
            }
            else
            {
                ctxMenu.PlacementTarget = sourceContentPresenter;
                ctxMenu.IsOpen = true;
            }
        }
    }
}

< STRONG>的RemoveItemCommand我添加到MainViewModel

new RelayCommand<MyItemClass>(RemoveItem, (param) => true);

    private void RemoveItem(MyItemClassitemToRemove)
    {
        MyItems.Remove(itemToRemove);
    }



的RelayCommand的CanExecute方法

public bool CanExecute(object parameter)
{
    if (_canExecute == null)
    {
        return true;
    }

    if (parameter == null)
    {
        return _canExecute.Invoke(default(T));
    }

    T value;

    try
    {
        value = (T)parameter;                
    }
    catch(Exception exception)
    {
        Trace.TraceError(exception.ToString());
        return _canExecute.Invoke(default(T));
    }

    return _canExecute.Invoke(value);
}



我得到在值=(T)的参数错误; 行,因为参数的断开的,不能把它转换为 T

I get the error in the value = (T)parameter; line, because the parameter is Disconnected and can't cast it to T.

例外我得到:

MyProgram.vshost.exe错误:0:System.InvalidCastException:无法投对象类型'MS.Internal.NamedObject中键入MyItemClass。
在MyNamespace.RelayCommand`1.CanExecute(Object参数)在C:\MyPath\RelayCommand.cs:XXX行

MyProgram.vshost.exe Error: 0 : System.InvalidCastException: Unable to cast object of type 'MS.Internal.NamedObject' to type 'MyItemClass'. at MyNamespace.RelayCommand`1.CanExecute(Object parameter) in c:\MyPath\RelayCommand.cs:line xxx

如果我检查参数这是一个NamedObject:

If I inspect the parameter it is a NamedObject:


  • 参数{} DisconnectedItem {对象} MS.Internal.NamedObject

  • 非公共成员结果
    _name{} DisconnectedItem字符串

的问题是不是例外是,它到达该点与DisconnectedItem这一事实。这得到评估多次。它像文本菜单中的可视化树仍然是永远。

The problem is not the Exception, is the fact that it reaches this point with a DisconnectedItem. This get evaluated multiple times. It is like the ContextMenu remains "forever" in the Visual Tree.

推荐答案

首先,请查看您的参数值

Firstly, just check your parameter values for null:

return parameter == null ? false : _canExecuteMethod((T)parameter); 



其次,这是旧的 ContextMenu.DataContext 问题:文本菜单显示在不同的视觉树UI的其余部分。因此,它具有从主UI可视化树到的DataContext 进不去。正因为如此,我们必须使用一个小窍门,通过将它传递给其他视觉树。我们两个之间的连接是 ContextMenu.PlacementTarget 属性

Secondly, this is the old ContextMenu.DataContext problem: The ContextMenu is displayed in a different visual tree to the rest of the UI. It therefore has no access to a DataContext from the main UI visual tree. Because of this, we have to use a little trick to pass it through to the other visual tree. Our connection between the two is the ContextMenu.PlacementTarget property.

从链接的页面,这个属性。

From the linked page, this property

获取或的UIElement相对于它打开时,它的文本菜单的位置设置。

Gets or sets the UIElement relative to which the ContextMenu is positioned when it opens.

我们可以使用标签 ContextMenu.PlacementTarget 对象的属性通过的DataContext 。基本上,只要设置标签的对象属性,即设置了文本菜单上。尝试是这样的:

We can use the Tag property of the ContextMenu.PlacementTarget object to pass the DataContext. Basically, just set the Tag property on the object that you will set the ContextMenu on. Try something like this:

<ItemsControl x:Name="myItems" ItemsSource="{Binding MyItems}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding MyText}" Tag="{Binding DataContext, 
                RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" 
                ContextMenu="{StaticResource ItemContextMenu}" />
        </DataTemplate>                    
    </ItemsControl.ItemTemplate>
</ItemsControl>



...

...

<ContextMenu x:Key="ItemContextMenu" ItemsSource="{Binding ContextMenuItems}" 
    ItemContainerStyle="{StaticResource CommandMenuItemStyle}"
    DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}"/>

这就是它。现在,在声明的UI元素的文本菜单将有机会获得任何对象,你的数据绑定到标签属性。但绝对没有必要 EventSetter s到使用文本菜单 ...这是很简单的如果你知道怎么办。

That's it. Now the UI elements declared in the ContextMenu will have access to whatever object you data bind to the Tag property. There's absolutely no need for EventSetters to use a ContextMenu... it's quite simple if you know how.

这篇关于里面的ItemsControl绑定上下文菜单?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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