簡體   English   中英

將父控件的命令綁定到 ContextMenu MenuItem

[英]Binding command of parent control to ContextMenu MenuItem

我在做什么:

在我的 DataGrid 上,我有一個上下文菜單來添加模板項。 MenuItem 是使用父 MenuItem 的 ItemsSource 屬性動態創建的。 ItemsSource 是我的模板對象的 ObservableCollection。 我想從集合對象屬性中獲取動態 MenuItems 的 Header,但從我的主 ViewModel 執行命令。 主 ViewModel 綁定到根 Grid 的 DataContext。

我的問題:

MenuItems 已正確創建,我還可以將標題綁定到集合中對象的屬性。 但是命令綁定不起作用。 我可以在輸出窗口中看到一個錯誤:“System.Windows.Data 錯誤:4:無法找到與引用綁定的源...”

這是我的代碼簡化為問題(使用 MVVM 燈):

主窗口.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>

主窗口.xaml.cs:

using System.Windows;

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

主視圖模型.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:

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

作業模板.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";
    }
}

版本:

  • Visual Studio 專業版 2015
  • .NET 框架 4.5.2
  • MvvmLight 5.4.1.1
  • CommonServiceLocator 2.0.5(MvvmLight 需要)

我已經嘗試尋找解決方案有一段時間了。 在此先感謝您的幫助。

您也可以訪問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>

沒有 MVVM light 的更通用方法可能是使用綁定代理

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM