【问题标题】:Bind Context Menu inside ItemsControl?在 ItemsControl 中绑定上下文菜单?
【发布时间】:2014-02-04 15:30:28
【问题描述】:

如何将 ContextMenu 添加到 ItemsControl,其中:

  • ItemsControl 的 ItemsSource 位于持有 ItemsControl 的 UserControl 的 ViewModel 中
  • 上下文菜单的 CommandParameter 是绑定到 ItemsControl 中的项目的 ViewModel。

我关注了this approach。但是,我有一个从绑定到我的 ItemsControl 的 ObservableCollection 中删除项目的命令。发生这种情况时,RelayCommand 内会引发异常。在我看来,ContextMenu 没有“隐藏”,因此它尝试评估其命令的“CanExecute”,但由于该项目已被删除,它无法在 RelayCommand 的 CanExecute 方法中将参数转换为“T”类。

我想知道完成我需要的正确方法是什么。


到目前为止我的实现:

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)

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

我的 UserControl 代码隐藏

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;
            }
        }
    }
}

我添加到 MainViewModel 的 RemoveItemCommand

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);
}

我在 value = (T)parameter; 行中得到错误,因为该参数是 Disconnected 并且无法将其转换为 T强>。

我得到的例外

MyProgram.vshost.exe 错误:0:System.InvalidCastException:无法将“MS.Internal.NamedObject”类型的对象转换为“MyItemClass”类型。 在 c:\MyPath\RelayCommand.cs:line xxx 中的 MyNamespace.RelayCommand`1.CanExecute(Object 参数)

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

  • 参数 {DisconnectedItem} 对象 {MS.Internal.NamedObject}
  • 非公共成员
    _name "{DisconnectedItem}" 字符串

问题不在于异常,而是它通过 DisconnectedItem 到达这一点。这会被多次评估。就像 ContextMenu 在可视树中“永远”保持不变。

【问题讨论】:

    标签: c# wpf contextmenu


    【解决方案1】:

    首先,只需检查null 的参数值:

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

    其次,这是旧的ContextMenu.DataContext 问题:ContextMenu 显示在与 UI 的其余部分不同的可视化树中。因此,它无法从主 UI 视觉树中访问 DataContext。正因为如此,我们必须使用一个小技巧将它传递给另一个可视化树。我们两者之间的联系是ContextMenu.PlacementTarget property

    从链接的页面,这个属性

    获取或设置 ContextMenu 打开时相对于其定位的 UIElement。

    我们可以使用ContextMenu.PlacementTarget 对象的Tag 属性来传递DataContext。基本上,只需在您将设置ContextMenu 的对象上设置Tag 属性。试试这样的:

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

    就是这样。现在,ContextMenu 中声明的 UI 元素将可以访问您数据绑定到 Tag 属性的任何对象。 EventSetters 完全没有必要使用ContextMenu...这很简单如果你知道怎么做。

    【讨论】:

    • 谢里登,感谢您的回答。该参数不为空,我在问题的该部分添加了更多详细信息,以便您查看实际的异常。我还实现了您的更改,我有一个 StackPanel 围绕着我的 DataTemplate 中的所有控件,所以我把你建议的标签放在那里。 ContextMenu 被添加,但我仍然得到异常。然而,有一个改进,因为它在 ContextMenu 中的每个命令只发生一次(我有 5 个命令,所以每次删除一个项目时我都会得到 5 个异常)然后它停止,不像在我的实现中我不断收到异常。
    • 漂亮地使用 Tag 将 UserControl 级别的数据上下文传播到 ContextMenus 的备用树中。因为我必须将 MenuItem 的 Visibility 属性绑定到 UserControl 的数据上下文,所以我使用了您的技术两次,但我仍然需要访问在 ListBox.ItemTemplate 中定义的本地 DataContext,以便我可以拉出我想要操作的特定项目来自 MenuItem.Click。因此,我在 DataTemplate 中的 Image 元素上放置了一个标签,然后将该标签传递给 ContextMenu 的标签,然后将 Visibility 属性绑定到 ContextMenu 的标签。效果很好!
    【解决方案2】:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-07-14
      • 2014-07-20
      • 1970-01-01
      • 1970-01-01
      • 2016-06-04
      • 1970-01-01
      • 1970-01-01
      • 2017-11-05
      相关资源
      最近更新 更多