我一直在努力解决这个Q / A以及其他一些人试图解决这个问题,但我必须遗漏一些简单的事情: Bind Items to MenuItem -> use Command
我创建了这个小测试应用程序来尝试理解上下文菜单,并了解如何将Click事件连接到ViewModel中的中继命令,并从上下文菜单中访问当前选定的项目。
这是XAML:
<Window x:Class="ContextMenuTest_01.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="105" Width="525"
WindowStartupLocation="CenterScreen"
xmlns:local="clr-namespace:ContextMenuTest_01.ViewModels"
DataContext="MainWindowViewModel">
<Window.Resources>
<ObjectDataProvider x:Key="MainWindowViewModel" ObjectType="{x:Type local:MainWindowViewModel}" IsAsynchronous="True"/>
</Window.Resources>
<!-- CONTEXT MENU -->
<Window.ContextMenu>
<ContextMenu DataContext="MainWindowViewModel" Name="MainWindowContextMenu" PresentationTraceSources.TraceLevel="High">
<MenuItem Header="Skins" ItemsSource="{Binding Source={StaticResource MainWindowViewModel}, Path=Skins}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Source={StaticResource MainWindowViewModel}, Path=Skins.SkinName}"/>
<Setter Property="Command" Value="{Binding Source={StaticResource MainWindowViewModel}, Path=ContextMenuClickCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=SkinName}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
</Window.ContextMenu>
</Window>
视图模型:
using ContextMenuTest_01.Models;
using System.Collections.ObjectModel;
using ContextMenuTest_01.CommandBase;
using System.Windows;
namespace ContextMenuTest_01.ViewModels
{
/// <summary>Main Window View Model</summary>
class MainWindowViewModel
{
#region Class Variables
/// <summary>The skins</summary>
private ObservableCollection<SkinItem> skins = new ObservableCollection<SkinItem>();
#endregion Class Variables
public RelayCommand<object> ContextMenuClickCommand{ get; private set; }
#region Properties
/// <summary>Gets the skins.</summary>
/// <value>The skins.</value>
public ObservableCollection<SkinItem> Skins
{
get { return this.skins; }
private set { this.skins = value; }
}
#endregion Properties
/// <summary>Initializes a new instance of the <see cref="MainWindowViewModel"/> class.</summary>
public MainWindowViewModel()
{
ContextMenuClickCommand = new RelayCommand<object>((e) => OnMenuItemClick(e));
skins.Add(new SkinItem("Skin Item 1"));
skins.Add(new SkinItem("Skin Item 2"));
skins.Add(new SkinItem("Skin Item 3"));
}
/// <summary>Called when [menu item click].</summary>
public void OnMenuItemClick(object selected)
{
MessageBox.Show("Got to the ViewModel! YAY!!!");
}
}
}
因此,如果在ViewModel中更改以下三行,则会在调试器中触发OnMenuItemClick函数并显示消息框:
从中继命令定义中删除:
public RelayCommand ContextMenuClickCommand{ get; private set; }
从创建RelayCommand的地方删除和(e):
ContextMenuClickCommand = new RelayCommand(() => OnMenuItemClick());
从OnMenuItemClick公共函数中删除(选定的对象):
public void OnMenuItemClick()
然后一切正常但当然我没有当前选中的项目。 那么我在XAML中缺少什么来命令参数将参数SkinName传递给RelayCommand?
如果我离开这条线:
<Setter Property="Header" Value="{Binding Source={StaticResource MainWindowViewModel}, Path=Skins.SkinName}"/>
然后我在上下文菜单中得到以下内容:
Skins -> ContextMenuTest_01.Models.SkinItem
ContextMenuTest_01.Models.SkinItem
ContextMenuTest_01.Models.SkinItem
这告诉我绑定工作正常,它只是没有正确显示,这就是为什么我试图插入
<Setter Property="Header"....
但当然这并不像我期望的那样有效。
感谢您的时间!任何想法都会有所帮助!
我后面的代码中没有任何内容,就像跟踪MVVM时的方式一样。 这是我的skinItem类,没什么可说的,但我想我会在有人问起之前展示它:
using System.Windows.Input;
using System.Windows.Media;
namespace ContextMenuTest_01.Models
{
/// <summary>A small data structure to hold a single skin item.</summary>
public class SkinItem
{
#region Class Variables
/// <summary>The skin name</summary>
private string skinName;
/// <summary>The base skin name</summary>
private string baseSkinName;
/// <summary>The skin path</summary>
private string skinPath;
/// <summary>The action to be taken when switching skins.</summary>
private ICommand action;
/// <summary>The icon of the skin.</summary>
private Brush icon;
#endregion Class Variables
#region Constructors
/// <summary>Initializes a new instance of the <see cref="SkinItem"/> class.</summary>
public SkinItem() { }
/// <summary>Initializes a new instance of the <see cref="SkinItem" /> class.</summary>
/// <param name="newSkinName">The name of the new skin.</param>
/// <param name="baseSkinName">Name of the base skin.</param>
/// <param name="newSkinPath">Optional Parameter: The new skin path.</param>
/// <param name="newSkinAction">Optional Parameter: The new skin action to be taken when switching to the new skin.</param>
/// <param name="newSkinIcon">Optional Parameter: The new skin icon.</param>
public SkinItem(string newSkinName, string baseSkinName = "", string newSkinPath = "", ICommand newSkinAction = null, Brush newSkinIcon = null)
{
if (newSkinName != "")
this.skinName = newSkinName;
if (baseSkinName != "")
this.baseSkinName = baseSkinName;
if (newSkinPath != "")
this.skinPath = newSkinPath;
if (newSkinAction != null)
this.action = newSkinAction;
if (newSkinIcon != null)
this.icon = newSkinIcon;
}
#endregion Constructors
#region Properties
/// <summary>Gets or sets the name of the skin.</summary>
/// <value>The name of the skin.</value>
public string SkinName
{
get { return this.skinName; }
set
{
if (this.skinName != value)
{
this.skinName = value;
//OnPropertyChanged(() => this.SkinName);
}
}
}
/// <summary>Gets or sets the name of the base skin.</summary>
/// <value>The name of the base skin.</value>
public string BaseSkinName
{
get { return this.baseSkinName; }
set
{
if (this.baseSkinName != value)
{
this.baseSkinName = value;
//OnPropertyChanged(() => this.BaseSkinName);
}
}
}
/// <summary>Gets or sets the skin path.</summary>
/// <value>The skin path.</value>
public string SkinPath
{
get { return this.skinPath; }
set
{
if (this.skinPath != value)
{
this.skinPath = value;
//OnPropertyChanged(() => this.SkinPath);
}
}
}
/// <summary>Gets or sets the action.</summary>
/// <value>The action.</value>
public ICommand Action
{
get { return this.action; }
set
{
if (this.action != value)
{
this.action = value;
//OnPropertyChanged(() => this.Action);
}
}
}
/// <summary>Gets or sets the icon.</summary>
/// <value>The icon.</value>
public Brush Icon
{
get { return this.icon; }
set
{
if (this.icon != value)
{
this.icon = value;
//OnPropertyChanged(() => this.Icon);
}
}
}
#endregion Properties
}
}
哦,我正在使用Galasoft MVVM-Light通用RelayCommand,它应该带参数,可以在这里找到: http://mvvmlight.codeplex.com/SourceControl/latest#GalaSoft.MvvmLight/GalaSoft.MvvmLight%20%28NET35%29/Command/RelayCommandGeneric.cs
答案 0 :(得分:1)
我很难理解你正在寻找什么。但是在运行代码时,我发现上下文菜单中没有显示Skins的名称。如果您删除了源代码,那么标题的设置如下所示:
<!-- CONTEXT MENU -->
<Window.ContextMenu>
<ContextMenu DataContext="MainWindowViewModel" Name="MainWindowContextMenu" PresentationTraceSources.TraceLevel="High">
<MenuItem Header="Skins" ItemsSource="{Binding Source={StaticResource MainWindowViewModel}, Path=Skins}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=SkinName}"/>
<Setter Property="Command" Value="{Binding Source={StaticResource MainWindowViewModel}, Path=ContextMenuClickCommand}"/>
<Setter Property="CommandParameter" Value="{Binding Path=SkinName}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
</Window.ContextMenu>
这将解决您的问题。由于您在MenuItem上设置源,因此您可以更改其中项目的datacontext。所以你不需要再次指定源。
编辑:
我还将路径从Skins.SkinName更改为SkinName
现在我看到菜单中项目的文本,当我点击“Skin Item 1”时,OnMenuItemClick中选择的值为“Skin Item 1”。