如何从 WPF 功能区按钮触发 ViewModel 中的方法

How to trigger a method in a ViewModel from a WPF Ribbon button

我对 WPF、Ribbon 菜单和 C# 以及 MVVM 模式相当缺乏经验。所以请原谅任何愚蠢。

我的问题是我有一个 WPF 应用程序,我试图拥有一个包含功能区组件的 MainView 以及一个显示当前视图的 ContentControl。 如何从 MainView 功能区触发当前视图中的方法?它似乎确实对视图有一些了解,因为它正在从中获取“名称”参数,但如果我尝试绑定到其他任何东西(因为那个东西不在我用来创建列表的界面中),则会抛出错误的观点)。

这是我希望足以让您了解我的问题的代码。

MainWindowView.xaml

<RibbonWindow x:Class="LIA.MainWindowView"
              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"              
              xmlns:local="clr-namespace:LIA" d:DataContext="{d:DesignInstance Type=local:MainWindowViewModel}"
              Title="LIA"
              Width="Auto" Height="480">

    <RibbonWindow.Resources>
        <!-- views and models for the app -->
        <DataTemplate DataType="{x:Type local:SampleViewModel}">
            <local:SampleView />
        </DataTemplate>
    </RibbonWindow.Resources>

    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Ribbon x:Name="Ribbon" Title="Ribbon Title" HorizontalAlignment="Center" Width="{Binding ElementName=LayoutRoot, Path=ActualWidth}">
            <Ribbon.HelpPaneContent>
                <RibbonButton SmallImageSource="Images/smallicon.png" />
            </Ribbon.HelpPaneContent>
            <Ribbon.QuickAccessToolBar>
                <RibbonQuickAccessToolBar>
                    <RibbonButton x:Name="QATButton1"
                                  SmallImageSource="Images/SmallIcon.png" />
                    <RibbonButton x:Name="QATButton2"
                                  SmallImageSource="Images/SmallIcon.png" />
                </RibbonQuickAccessToolBar>
            </Ribbon.QuickAccessToolBar>
            <Ribbon.ApplicationMenu>
                <RibbonApplicationMenu SmallImageSource="Images\SmallIcon.png">
                    <RibbonApplicationMenuItem Header="Hello _Ribbon"
                                               Width="Auto"
                                               x:Name="MenuItem1"
                                               ImageSource="Images\LargeIcon.png" />
                </RibbonApplicationMenu>
            </Ribbon.ApplicationMenu>
                        <RibbonTab x:Name="SampleTab"
                       Header="Aname">
                            <RibbonGroup x:Name="SampleTabGroup1"
                             Header="A header">
                                <RibbonButton x:Name="aLargeButton"
                                              
                                  LargeImageSource="Images\LargeIcon.png"
                                  Label="Button5"
                                  />
                            </RibbonGroup>
                        </RibbonTab>
                    
            <RibbonTab x:Name="tab"
                       Header="Sample Tab">
                <RibbonGroup x:Name="Group1"
                             Header="">
                    <ItemsControl ItemsSource="{Binding PageViewModels}">                        
                            <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <RibbonButton
                                    x:Name="Button1"                                    
                                  LargeImageSource="Images\LargeIcon.png"
                                  Label="{Binding Name}" />
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
                    
                </RibbonGroup>
            </RibbonTab>
                        
        </Ribbon>

        <ContentControl Content="{Binding CurrentPageViewModel}" Grid.Row="1" />

    </Grid>

</RibbonWindow>

MainWIndowView.xaml.cs:

using System.Windows.Controls.Ribbon;

namespace LIA
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindowView : RibbonWindow
    {
        public MainWindowView()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel();
        }

    }
}

MainWindowViewModel.cs:

using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;

namespace LIA
{
    class MainWindowViewModel : ObservableObject
    {
        #region MainWindowViewModel Fields

        private ICommand _changePageCommand;
        private IPageViewModel _currentPageViewModel;
        private List<IPageViewModel> _pageViewModels;

        #endregion MainWindowViewModel Fields

        public MainWindowViewModel()
        {
            //Add pages
            PageViewModels.Add(new SampleViewModel());
            CurrentPageViewModel = PageViewModels[0];
        }

        #region MainWindowViewModel Properties

        public List<IPageViewModel> PageViewModels
        {
            get
            {
                if (_pageViewModels == null)
                    _pageViewModels = new List<IPageViewModel>();

                return _pageViewModels;
            }
        }

        public IPageViewModel CurrentPageViewModel
        {
            get
            {
                return _currentPageViewModel;
            }
            set
            {
                if (_currentPageViewModel != value)
                {
                    _currentPageViewModel = value;
                    OnPropertyChanged("CurrentPageViewModel");
                }
            }
        }

        #endregion MainWindowViewModel Properties

        #region MainWindowViewModel Commands

        public ICommand ChangePageCommand
        {
            get
            {
                if (_changePageCommand == null)
                {
                    _changePageCommand = new RelayCommand(
                        p => ChangeViewModel((IPageViewModel)p),
                        p => p is IPageViewModel
                        );
                }
                System.Diagnostics.Debug.WriteLine("page change command");
                return _changePageCommand;
            }
        }



        #endregion MainWindowViewModel Commands


        #region MaiWindowViewModel Methods

        private void ChangeViewModel(IPageViewModel viewModel)
        {
            if (!PageViewModels.Contains(viewModel))
            {
                PageViewModels.Add(viewModel);

                CurrentPageViewModel = PageViewModels.FirstOrDefault(vm => vm == viewModel);
            }
        }

        #endregion MaiWindowViewModel Methods
    }
}

接口:

using System.Collections.Generic;
using System.Windows.Input;

namespace LIA
{
    public interface IPageViewModel
    {
        string Name { get; }
    }
}

SampleViewModel.cs:

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Collections.ObjectModel;
using System.Data.OleDb;
using System.Drawing;
using System.Drawing.Printing;
using System.Linq;
using System.Windows.Input;
using ZXing;
using ZXing.Datamatrix;

namespace LIA
{
    class SampleViewModel : ObservableObject, IPageViewModel
    {
        #region SampleViewModel Fields

        private ICommand _generateLabel;

        #endregion SampleViewModel Fields

        public SampleViewModel()
        {
            
        }


        #region SampleViewModel Properties

        public string Name
        {
            get { return "Sample"; }
        }

        #endregion SampleViewModel Properties

        #region SampleViewModel Commands

        public ICommand GenerateLabelCommand
        {
            get
            {
                if (_generateLabel == null)
                {
                    _generateLabel = new RelayCommand(
                        param => GenerateLabel(),                        
                        );
                }
                return _generateLabel;
            }
        }

        #endregion SampleViewModel Commands

        #region SampleViewModel Methods

        private void GenerateLabel()
        {
            //do the stuff I need to generate a label
        }

        #endregion SampleViewModel Methods
    }
}

我没有包含 SampleView.xaml 或 SampleView.xaml.cs,因为它似乎工作正常,当按钮位于该视图时我可以触发该方法。

这是输出: Menus with button showing correct name

一直在疯狂寻找解决方案,这可能意味着这里有一个基本概念我还不明白。

您尝试过使用 Dispatcher.BeginInvoke 吗?我没有任何代表可以发表评论抱歉。

如果我从你的代码中理解正确:在你的 RibbonGroup 命名为 Group1 中,你希望 PageViewModels 中的每个项目都有一个 Button,这将单击时从 IPageViewModel 项目执行一些代码,对吗?

在上面的示例中,将 Command="{Binding GenerateLabelCommand}" 添加到 Button1 应该 link 它到 SampleViewModel 中的命令。它所属的集合被定义为 List<IPageViewModel> 并不重要。如果这不起作用,则需要更多调试细节。

就是说,以上 SampleViewModel 有效,除非每个 IPageViewModel 都有一个名为 GenerateLabelCommand 的成员。如果您希望它适用于所有 IPageViewModel 实现,您应该向接口添加一个 ICommand 成员,然后显式绑定到接口实现,如本问题中所述: WPF databinding to interface and not actual object - casting possible?