MenuItem通过RelayCommand将所选项目传递给ViewModel ala MVVM-Light&头

时间:2014-02-05 18:45:52

标签: c# wpf xaml mvvm contextmenu

我一直在努力解决这个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

1 个答案:

答案 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”。