[英]How to implement commands to use ancestor methods in WPF?
我有這個上下文菜單資源:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ContextMenu x:Key="FooContextMenu">
<ContextMenu.CommandBindings>
<CommandBinding Command="Help" Executed="{Binding ElementName=MainTabs, Path=HelpExecuted}" />
</ContextMenu.CommandBindings>
<MenuItem Command="Help">
<MenuItem.Icon>
<Image Source="../Resources/Icons/Help.png" Stretch="None" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</ResourceDictionary>
我想在兩個地方重復使用它。 首先,我試圖將它放在DataGrid
:
<DataGrid ContextMenu="{DynamicResource FooContextMenu}">...
ContextMenu
本身工作正常,但使用Executed="..."
我現在打破了應用程序並拋出:
PresentationFramework.dll中出現“System.InvalidCastException”類型的第一次機會異常
附加信息:無法將類型為“System.Reflection.RuntimeEventInfo”的對象強制轉換為“System.Reflection.MethodInfo”。
如果我刪除整個Executed="..."
定義,那么代碼就可以工作(並且命令不會執行任何操作/灰顯)。 一旦我右鍵單擊網格/打開上下文菜單,就會拋出異常。
DataGrid
放在幾個元素下面,但最終它們都在TabControl
(稱為MainTabs
) MainTabs
,它將ItemsSource
設置為FooViewModel
的集合,並且在FooViewModel
我有一個我想要調用的方法HelpExecuted
。
讓我們想象一下:
ItemsSource=ObservableCollection<FooViewModel>
, x:Name=MainTabs
)
為什么我會收到此錯誤,如何使上下文菜單命令“定位” FooViewModel
的HelpExecuted
方法?
這有幫助嗎?
<ContextMenu>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=HelpExecuted}" />
</Style>
</ContextMenu.ItemContainerStyle>
<MenuItem Header="Help" />
</ContextMenu>
遺憾的是,您無法為ContextMenu
綁定Executed
,因為它是一個事件。 另一個問題是,在應用程序的其余部分存在的VisualTree
中, ContextMenu
不存在。 這兩個問題都有解決方案。
首先,您可以使用ContextMenu
的父控件的Tag
屬性來傳遞應用程序的DataContext
。 然后你可以使用DelegateCommand
為你的CommandBinding
,你去。 這是一個小樣本,顯示了您必須添加到項目中的View
, ViewModel
和DelegateCommand
實現。
DelegateCommand.cs
public class DelegateCommand : ICommand
{
private readonly Action<object> execute;
private readonly Predicate<object> canExecute;
public DelegateCommand(Action<object> execute)
: this(execute, null)
{ }
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
this.execute = execute;
this.canExecute = canExecute;
}
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return canExecute == null ? true : canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
execute(parameter);
}
#endregion
}
MainWindowView.xaml
<Window x:Class="Application.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindowView" Height="300" Width="300"
x:Name="MainWindow">
<Window.Resources>
<ResourceDictionary>
<ContextMenu x:Key="FooContextMenu">
<MenuItem Header="Help" Command="{Binding PlacementTarget.Tag.HelpExecuted, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</ResourceDictionary>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding FooViewModels}" x:Name="MainTabs">
<TabControl.ContentTemplate>
<DataTemplate>
<DataGrid ContextMenu="{DynamicResource FooContextMenu}" Tag="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
MainWindowView.xaml.cs
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
MainWindowViewModel.cs
public class MainWindowViewModel
{
public ObservableCollection<FooViewModel> FooViewModels { get; set; }
public MainWindowViewModel()
{
FooViewModels = new ObservableCollection<FooViewModel>();
}
}
FooViewModel.cs
public class FooViewModel
{
public ICommand HelpExecuted { get; set; }
public FooViewModel()
{
HelpExecuted = new DelegateCommand(ShowHelp);
}
private void ShowHelp(object obj)
{
// Yay!
}
}
我擔心MatthiasG會打敗我。 我的解決方案類似:
此處的“幫助”命令由選項卡項的視圖模型處理。 如果需要,將TestViewModel的引用傳遞給每個TestItemViewModel並讓ShowHelp回調到TestViewModel會很簡單。
public class TestViewModel
{
public TestViewModel()
{
Items = new List<TestItemViewModel>{
new TestItemViewModel(), new TestItemViewModel() };
}
public ICommand HelpCommand { get; private set; }
public IList<TestItemViewModel> Items { get; private set; }
}
public class TestItemViewModel
{
public TestItemViewModel()
{
// Expression Blend ActionCommand
HelpCommand = new ActionCommand(ShowHelp);
Header = "header";
}
public ICommand HelpCommand { get; private set; }
public string Header { get; private set; }
private void ShowHelp()
{
Debug.WriteLine("Help item");
}
}
xaml
<Window.Resources>
<ContextMenu x:Key="FooMenu">
<MenuItem Header="Help" Command="{Binding HelpCommand}"/>
</ContextMenu>
<DataTemplate x:Key="ItemTemplate">
<!-- context menu on header -->
<TextBlock Text="{Binding Header}" ContextMenu="{StaticResource FooMenu}"/>
</DataTemplate>
<DataTemplate x:Key="ContentTemplate">
<Grid Background="#FFE5E5E5">
<!-- context menu on data grid -->
<DataGrid ContextMenu="{StaticResource FooMenu}"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<WpfApplication2:TestViewModel/>
</Window.DataContext>
<Grid>
<TabControl
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource ItemTemplate}"
ContentTemplate="{StaticResource ContentTemplate}" />
</Grid>
備用視圖模型,以便將幫助命令定向到根視圖模型
public class TestViewModel
{
public TestViewModel()
{
var command = new ActionCommand(ShowHelp);
Items = new List<TestItemViewModel>
{
new TestItemViewModel(command),
new TestItemViewModel(command)
};
}
public IList<TestItemViewModel> Items { get; private set; }
private void ShowHelp()
{
Debug.WriteLine("Help root");
}
}
public class TestItemViewModel
{
public TestItemViewModel(ICommand helpCommand)
{
HelpCommand = helpCommand;
Header = "header";
}
public ICommand HelpCommand { get; private set; }
public string Header { get; private set; }
}
ActionCommand的一個非常簡單的實現
public class ActionCommand : ICommand
{
private readonly Action _action;
public ActionCommand(Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
_action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action();
}
// not used
public event EventHandler CanExecuteChanged;
}
您收到此錯誤,因為CommandBinding.Executed不是依賴項屬性,因此您無法綁定它。
相反,使用后面的ResourceDictionary代碼為CommandBinding.Executed事件指定事件處理程序,並在事件處理程序代碼中調用FooViewModel.HelpExecuted()方法,如下所示:
MainWindowResourceDictionary.xaml
<ResourceDictionary x:Class="WpfApplication.MainWindowResourceDictionary"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication">
<DataTemplate DataType="{x:Type local:FooViewModel}">
<Grid>
<DataGrid ContextMenu="{DynamicResource FooContextMenu}"/>
</Grid>
</DataTemplate>
<ContextMenu x:Key="FooContextMenu">
<ContextMenu.CommandBindings>
<CommandBinding Command="Help" Executed="HelpExecuted"/>
</ContextMenu.CommandBindings>
<MenuItem Command="Help"/>
</ContextMenu>
</ResourceDictionary>
MainWindowResourceDictionary.xaml.cs
public partial class MainWindowResourceDictionary : ResourceDictionary
{
public MainWindowResourceDictionary()
{
InitializeComponent();
}
private void HelpExecuted(object sender, ExecutedRoutedEventArgs e)
{
var fooViewModel = (FooViewModel)((FrameworkElement)e.Source).DataContext;
fooViewModel.HelpExecuted();
}
}
可以創建一個適配器類,可以將其配置為XAML中的資源,可以附加到Control以在那里創建CommandBindings,另一端可以綁定到ViewModel中的一個方法,該方法應該在命令由Button或MenuItem觸發。 在這種情況下,命令將是RoutedCommand,無論您是選擇其中一個預定義的WPF命令還是在應用程序中創建自定義RoutedCommand都無關緊要。
綁定到方法的技巧是
雖然這聽起來很復雜,但好處是,一旦你將框架的各個部分放在一起,你就可以只在XAML中重復使用它們,並且在ViewModel或后面的代碼中根本就沒有任何粘合代碼。
你可以想象,這需要一些基礎設施,代碼比我想發布的更多。 但是,我剛剛在我的博客上發表了一篇關於這個主題的文章, http://wpfglue.wordpress.com/2012/05/07/commanding-binding-controls-to-methods/ ,你可以通過博客下載完整的框架源代碼和VB.Net中的示例。
應用於您的問題,XAML將如下所示:
在ContextMenu的定義中:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ContextMenu x:Key="FooContextMenu">
<!-- No CommandBindings needed here -->
<MenuItem Command="Help">
<MenuItem.Icon>
<Image Source="../Resources/Icons/Help.png" Stretch="None" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</ResourceDictionary>
並在DataGrid的定義中
<DataGrid c:Commanding.CommandSet="{DynamicResource helpCommand}">
<DataGrid.Resources>
<f:ActionConverter x:Key="actionConverter"/>
<c:ActionCommand x:Key="helpCommand" Command="Help" ExecutedAction="{Binding Converter={StaticResource actionConverter}, ConverterParameter=HelpExecuted}"/>
<!-- DataGrid definition continued... -->
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.