如何从 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?
我对 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?