Eri*_*let 11 c# wpf command routed-commands mvvm
根据我对MVVM的理解,直接在ViewModel中处理路由命令是一种很好的做法.
当路由命令在ViewModel中定义为RelayCommand(或DelegateCommand)时,很容易直接绑定到命令,如下所示:Command = {Binding MyViewModelDefinedCommand}.
实际上,对于在我的ViewModel之外定义的路由命令,我在View后面的代码中处理这些命令并将调用转发到ViewModel.但我发现我必须这样做很尴尬.它违背了推荐的MVVM良好做法.我认为应该有一种更优雅的方式来实现这项工作.
如何直接在ViewModel中处理"System.Windows.Input.ApplicationCommands"或在Viewmodel外部定义的任何路由命令.换句话说,对于在ViewModel之外定义的命令,我如何直接处理ViewBodel的CommandBinding回调"CommandExecute"和/或"CommandCanExecute"?这有可能吗?如果有,怎么样?如果不是,为什么?
Fri*_*Guy 26
我将这个问题改为:
如何在没有代码隐藏的情况下处理ViewModel中的WPF路由命令?
对此,我会回答:好问题!
WPF没有提供内置的方法来执行此操作,当您第一次启动WPF并且每个人都告诉您"Code-Behind is evil"(它确实是)时,这尤其令人讨厌.所以你必须自己构建它.
那么,如何自己创建这样的功能呢?好吧,首先我们需要一个等价的CommandBinding
:
/// <summary>
/// Allows associated a routed command with a non-routed command. Used by
/// <see cref="RoutedCommandHandlers"/>.
/// </summary>
public class RoutedCommandHandler : Freezable
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(RoutedCommandHandler),
new PropertyMetadata(default(ICommand)));
/// <summary> The command that should be executed when the RoutedCommand fires. </summary>
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
/// <summary> The command that triggers <see cref="ICommand"/>. </summary>
public ICommand RoutedCommand { get; set; }
/// <inheritdoc />
protected override Freezable CreateInstanceCore()
{
return new RoutedCommandHandler();
}
/// <summary>
/// Register this handler to respond to the registered RoutedCommand for the
/// given element.
/// </summary>
/// <param name="owner"> The element for which we should register the command
/// binding for the current routed command. </param>
internal void Register(FrameworkElement owner)
{
var binding = new CommandBinding(RoutedCommand, HandleExecuted, HandleCanExecute);
owner.CommandBindings.Add(binding);
}
/// <summary> Proxy to the current Command.CanExecute(object). </summary>
private void HandleCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = Command?.CanExecute(e.Parameter) != null;
e.Handled = true;
}
/// <summary> Proxy to the current Command.Execute(object). </summary>
private void HandleExecuted(object sender, ExecutedRoutedEventArgs e)
{
Command?.Execute(e.Parameter);
e.Handled = true;
}
}
Run Code Online (Sandbox Code Playgroud)
然后我们需要一个实际将RoutedCommandHandler与特定元素相关联的类.为此,我们将RoutedCommandHandler
s作为附加属性的集合,如下所示:
/// <summary>
/// Holds a collection of <see cref="RoutedCommandHandler"/> that should be
/// turned into CommandBindings.
/// </summary>
public class RoutedCommandHandlers : FreezableCollection<RoutedCommandHandler>
{
/// <summary>
/// Hide this from WPF so that it's forced to go through
/// <see cref="GetCommands"/> and we can auto-create the collection
/// if it doesn't already exist. This isn't strictly necessary but it makes
/// the XAML much nicer.
/// </summary>
private static readonly DependencyProperty CommandsProperty = DependencyProperty.RegisterAttached(
"CommandsPrivate",
typeof(RoutedCommandHandlers),
typeof(RoutedCommandHandlers),
new PropertyMetadata(default(RoutedCommandHandlers)));
/// <summary>
/// Gets the collection of RoutedCommandHandler for a given element, creating
/// it if it doesn't already exist.
/// </summary>
public static RoutedCommandHandlers GetCommands(FrameworkElement element)
{
RoutedCommandHandlers handlers = (RoutedCommandHandlers)element.GetValue(CommandsProperty);
if (handlers == null)
{
handlers = new RoutedCommandHandlers(element);
element.SetValue(CommandsProperty, handlers);
}
return handlers;
}
private readonly FrameworkElement _owner;
/// <summary> Each collection is tied to a specific element. </summary>
/// <param name="owner"> The element for which this collection is created. </param>
public RoutedCommandHandlers(FrameworkElement owner)
{
_owner = owner;
// because we auto-create the collection, we don't know when items will be
// added. So, we observe ourself for changes manually.
var self = (INotifyCollectionChanged)this;
self.CollectionChanged += (sender, args) =>
{
// note this does not handle deletions, that's left as an exercise for the
// reader, but most of the time, that's not needed!
((RoutedCommandHandlers)sender).HandleAdditions(args.NewItems);
};
}
/// <summary> Invoked when new items are added to the collection. </summary>
/// <param name="newItems"> The new items that were added. </param>
private void HandleAdditions(IList newItems)
{
if (newItems == null)
return;
foreach (RoutedCommandHandler routedHandler in newItems)
{
routedHandler.Register(_owner);
}
}
/// <inheritdoc />
protected override Freezable CreateInstanceCore()
{
return new RoutedCommandHandlers(_owner);
}
}
Run Code Online (Sandbox Code Playgroud)
然后,就像在元素上使用类一样简单:
<local:RoutedCommandHandlers.Commands>
<local:RoutedCommandHandler RoutedCommand="Help" Command="{Binding TheCommand}" />
</local:RoutedCommandHandlers.Commands>
Run Code Online (Sandbox Code Playgroud)
知道了上述内容,您可能会问:
哇,那很好,但那是很多代码.我已经在使用Expression Behaviors,所以有没有办法简化这一点?
对此,我会回答:好问题!
如果您已经在使用Interaction.Behaviors,那么您可以使用以下实现:
/// <summary>
/// Allows associated a routed command with a non-ordinary command.
/// </summary>
public class RoutedCommandBinding : Behavior<FrameworkElement>
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(RoutedCommandBinding),
new PropertyMetadata(default(ICommand)));
/// <summary> The command that should be executed when the RoutedCommand fires. </summary>
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
/// <summary> The command that triggers <see cref="ICommand"/>. </summary>
public ICommand RoutedCommand { get; set; }
protected override void OnAttached()
{
base.OnAttached();
var binding = new CommandBinding(RoutedCommand, HandleExecuted, HandleCanExecute);
AssociatedObject.CommandBindings.Add(binding);
}
/// <summary> Proxy to the current Command.CanExecute(object). </summary>
private void HandleCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = Command?.CanExecute(e.Parameter) != null;
e.Handled = true;
}
/// <summary> Proxy to the current Command.Execute(object). </summary>
private void HandleExecuted(object sender, ExecutedRoutedEventArgs e)
{
Command?.Execute(e.Parameter);
e.Handled = true;
}
}
Run Code Online (Sandbox Code Playgroud)
使用相应的XAML:
<i:Interaction.Behaviors>
<local:RoutedCommandBinding RoutedCommand="Help" Command="{Binding TheCommand}" />
</i:Interaction.Behaviors>
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
3460 次 |
最近记录: |