简体   繁体   English

将父控件的命令绑定到 ContextMenu MenuItem

[英]Binding command of parent control to ContextMenu MenuItem

What I'm doing:我在做什么:

On my DataGrid I have a context menu to add Template items.在我的 DataGrid 上,我有一个上下文菜单来添加模板项。 The MenuItems are created dynamically using the ItemsSource property of the parent MenuItem. MenuItem 是使用父 MenuItem 的 ItemsSource 属性动态创建的。 The ItemsSource is an ObservableCollection of my template objects. ItemsSource 是我的模板对象的 ObservableCollection。 I want to get the Header of the dynamic MenuItems from the collection object properties, but execute a command from my main ViewModel.我想从集合对象属性中获取动态 MenuItems 的 Header,但从我的主 ViewModel 执行命令。 The main ViewModel is bound to the DataContext of the root Grid.主 ViewModel 绑定到根 Grid 的 DataContext。

My issue:我的问题:

The MenuItems are created properly and I can also bind the header to a property of an object in the collection. MenuItems 已正确创建,我还可以将标题绑定到集合中对象的属性。 But the Command binding does not work.但是命令绑定不起作用。 I can see an error in the output window: "System.Windows.Data Error: 4 : Cannot find source for binding with reference..."我可以在输出窗口中看到一个错误:“System.Windows.Data 错误:4:无法找到与引用绑定的源...”

Here is my code reduced to the issue (using MVVM light):这是我的代码简化为问题(使用 MVVM 灯):

MainWindow.xaml:主窗口.xaml:

<Window x:Class="TapTimesGui.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="1000">
    <!-- root grid -->
    <Grid x:Name="RootGrid" DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
        <!-- Jobs overview -->
        <DataGrid Grid.Column="0">
            <DataGrid.ContextMenu>
                <ContextMenu>
                    <!-- following works fine -->
                    <MenuItem Header="Basic command test" Command="{Binding AddTemplateJobCommand}" CommandParameter="Basic command test"/>
                    <MenuItem Header="Add template..." ItemsSource="{Binding JobTemplates}">
                        <MenuItem.ItemTemplate>
                            <DataTemplate>
                                <!-- following command does not work System.Windows.Data Error -->
                                <MenuItem Header="{Binding Name}" Command="{Binding ElementName=RootGrid, Path=DataContext.AddTemplateJobCommand}" CommandParameter="{Binding Name}"/>
                            </DataTemplate>
                        </MenuItem.ItemTemplate>
                        <!--<MenuItem.ItemContainerStyle> also does not work
                                <Style TargetType="MenuItem">
                                    <Setter Property="Header" Value="{Binding Name}"></Setter>
                                    <Setter Property="Command" Value="{Binding ElementName=RootGrid, Path=DataContext.SaveDayToNewFileCommand}"></Setter>
                                </Style>
                            </MenuItem.ItemContainerStyle>-->
                    </MenuItem>
                </ContextMenu>
            </DataGrid.ContextMenu>
        </DataGrid>
    </Grid>
</Window>

MainWindow.xaml.cs:主窗口.xaml.cs:

using System.Windows;

namespace TapTimesGui
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

MainViewModel.cs:主视图模型.cs:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using GalaSoft.MvvmLight.Messaging;
using System.Collections.ObjectModel;
using System.Windows.Input;
using TapTimesGui.Services;

namespace TapTimesGui.ViewModel
{
    /// <summary>
    /// ...
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        #region Properties and backing fields
        /// <summary>
        /// Templates for jobs
        /// </summary>
        public ObservableCollection<JobTemplate> JobTemplates
        {
            get
            {
                return _jobTemplates;
            }
        }
        private readonly ObservableCollection<JobTemplate> _jobTemplates = new ObservableCollection<JobTemplate>();
        #endregion Properties and backing fields

        #region ICommand properties
        public ICommand AddTemplateJobCommand { get; private set; }
        #endregion ICommand properties

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            // assign commands
            AddTemplateJobCommand = new RelayCommand<string>(AddTemplateJob);
            // populate data on start
            if (IsInDesignMode)
            {
                // Code runs in Blend --> create design time data.
            }
            else
            {
                //TODO test for templates
                JobTemplate tmpJobTemplate = new JobTemplate();
                tmpJobTemplate.Name = "Template 1";
                tmpJobTemplate.Template = "TestCustomer1 AG";
                JobTemplates.Add(tmpJobTemplate);
                tmpJobTemplate = new JobTemplate();
                tmpJobTemplate.Name = "Template 2";
                tmpJobTemplate.Template = "TestCustomer2 AG";
                JobTemplates.Add(tmpJobTemplate);
            }
        }
        #endregion Constructors

        #region Command methods
        private void AddTemplateJob(string name)
        {
            //TODO implement
            Messenger.Default.Send<NotificationMessage>(new NotificationMessage(name));
        }
        #endregion Command methods
    }
}

ViewModelLocator.cs: ViewModelLocator.cs:

/*
  In App.xaml:
  <Application.Resources>
      <vm:ViewModelLocator xmlns:vm="clr-namespace:TapTimesGui"
                           x:Key="Locator" />
  </Application.Resources>

  In the View:
  DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}"

  You can also use Blend to do all this with the tool's support.
  See http://www.galasoft.ch/mvvm
*/

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
//using Microsoft.Practices.ServiceLocation; TODO http://www.mvvmlight.net/std10 chapter known issues
using CommonServiceLocator;
using GalaSoft.MvvmLight.Messaging;
using System;
using System.Windows;

namespace TapTimesGui.ViewModel
{
    /// <summary>
    /// This class contains static references to all the view models in the
    /// application and provides an entry point for the bindings.
    /// </summary>
    public class ViewModelLocator
    {
        /// <summary>
        /// Initializes a new instance of the ViewModelLocator class.
        /// </summary>
        public ViewModelLocator()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            ////if (ViewModelBase.IsInDesignModeStatic)
            ////{
            ////    // Create design time view services and models
            ////    SimpleIoc.Default.Register<IDataService, DesignDataService>();
            ////}
            ////else
            ////{
            ////    // Create run time view services and models
            ////    SimpleIoc.Default.Register<IDataService, DataService>();
            ////}

            SimpleIoc.Default.Register<MainViewModel>();
            Messenger.Default.Register<NotificationMessage>(this, NotificationMessageHandler);
            Messenger.Default.Register<Exception>(this, ExceptionMessageHandler);
        }

        public MainViewModel Main
        {
            get
            {
                return ServiceLocator.Current.GetInstance<MainViewModel>();
            }
        }

        public static void Cleanup()
        {
            // TODO Clear the ViewModels
        }

        private void NotificationMessageHandler(NotificationMessage message)
        {
            //MessageBox.Show(message.Notification);
            System.Diagnostics.Debug.WriteLine(message.Notification);
            MessageBox.Show(message.Notification);
        }

        private void ExceptionMessageHandler(Exception ex)
        {
            MessageBox.Show(ex.ToString(), "Exception", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }
}

JobTemplate.cs:作业模板.cs:

using System.ComponentModel;

namespace TapTimesGui.Services
{
    /// <summary>
    /// Named TapTimesGui.Model.Job template
    /// </summary>
    /// <remarks>Can be used e.g. to save Templates of specific objects</remarks>
    public class JobTemplate : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
            }
        }
        private string _name = "";

        public string Template //Type was not string originally, it was if my Model object
        {
            get
            {
                //TODO proper cloning
                return _template;
            }
            set
            {
                //TODO proper cloning
                _template = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Template)));
            }
        }
        private string _template = "Template";
    }
}

Versions:版本:

  • Visual Studio Professional 2015 Visual Studio 专业版 2015
  • .NET Framework 4.5.2 .NET 框架 4.5.2
  • MvvmLight 5.4.1.1 MvvmLight 5.4.1.1
  • CommonServiceLocator 2.0.5 (required for MvvmLight) CommonServiceLocator 2.0.5(MvvmLight 需要)

I already tried to find a solution for some time.我已经尝试寻找解决方案有一段时间了。 Thanks in advance for your help.在此先感谢您的帮助。

You can access the ViewModelLocator inside the ItemTemplate as well.您也可以访问ItemTemplate内的ViewModelLocator

<DataGrid Grid.Column="0">
     <DataGrid.ContextMenu>
          <ContextMenu>
              <MenuItem Header="Add template..." ItemsSource="{Binding JobTemplates}">
                  <MenuItem.ItemTemplate>
                      <DataTemplate>
                          <MenuItem Header="{Binding Name}" Command="{Binding Source={StaticResource Locator}, Path=Main.AddTemplateJobCommand}" CommandParameter="{Binding Name}"/>
                       </DataTemplate>
                   </MenuItem.ItemTemplate>
              </MenuItem>
          </ContextMenu>
     </DataGrid.ContextMenu>
</DataGrid>

A more general approach without MVVM light could be using a binding proxy .没有 MVVM light 的更通用方法可能是使用绑定代理

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM