如何将上下文菜单添加到 ItemsControl,其中:
- ItemsControl 的 ItemsSource 在持有 ItemsControl 的 UserControl 的 ViewModel 中
- 上下文菜单的 CommandParameter 是绑定(bind)到 ItemsControl 中的 Item 的 ViewModel。
我关注了this approach .但是,我有一个从绑定(bind)到我的 ItemsControl 的 ObservableCollection 中删除项目的命令。当发生这种情况时,在 RelayCommand 中会抛出一个异常。在我看来,ContextMenu 没有“隐藏”,因此它尝试为其命令评估“CanExecute”,但由于该项目已被删除,它无法在 RelayCommand 的 CanExecute 方法中将参数转换为“T”类。
我想知道完成我需要的正确方法是什么。
到目前为止我的实现:
主视图模型
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()
{}
}
我的元素类
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>
我的用户控件代码隐藏
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; 行中收到错误,因为参数断开连接并且无法将其转换为 T强>.
我得到的异常:
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
如果我检查参数,它是一个命名对象:
- parameter {DisconnectedItem} object {MS.Internal.NamedObject}
- Non-Public members
_name "{DisconnectedItem}" string
问题不是异常,而是它通过 DisconnectedItem 到达这一点的事实。这得到多次评估。这就像上下文菜单“永远”保留在可视化树中。
最佳答案
首先,只需检查您的参数值是否为 null
:
return parameter == null ? false : _canExecuteMethod((T)parameter);
其次,这是旧的 ContextMenu.DataContext
问题:ContextMenu
显示在与 UI 其余部分不同的可视化树中。因此,它无法从主 UI 可视化树访问 DataContext
。正因为如此,我们必须使用一个小技巧将它传递给另一个可视化树。我们两者之间的联系是 ContextMenu.PlacementTarget
property .
从链接页面,这个属性
Gets or sets the UIElement relative to which the ContextMenu is positioned when it opens.
我们可以使用 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 元素将可以访问您将数据绑定(bind)到 Tag
属性的任何对象。 EventSetter
绝对不需要使用 ContextMenu
...如果您知道怎么做,这很简单。
关于c# - 在 ItemsControl 中绑定(bind)上下文菜单?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21556561/