草庐IT

c# - 如何在没有代码隐藏的情况下处理我的 ViewModel 中的 WPF 路由命令?

coder 2024-06-01 原文

根据我对 MVVM 的理解,直接在 ViewModel 中处理路由命令是一种很好的做法。

当路由命令在 ViewModel 中定义为 RelayCommand(或 DelegateCommand)时,很容易像这样直接绑定(bind)到命令:Command={Binding MyViewModelDefinedCommand}。

实际上,对于在我的 ViewModel 外部定义的路由命令,我在 View 的代码隐藏中处理这些命令并将调用转发到 ViewModel。但我觉得我不得不这样做很尴尬。它违背了推荐的 MVVM 良好实践。我认为应该有一种更优雅的方式来完成这项工作。

如何直接在 ViewModel 中处理“System.Windows.Input.ApplicationCommands”或在 Viewmodel 外部定义的任何路由命令。 换句话说,对于在 ViewModel 外部定义的命令,我如何直接处理 ViewModel 的 CommandBinding 回调“CommandExecute”和/或“CommandCanExecute”? 那可能吗?如果是如何?如果不是,为什么?

最佳答案

我将问题改写为:

How can I handle WPF routed commands in my ViewModel without code-behind?

对此,我会回答:好问题!

WPF 不提供执行此操作的内置方法,这在您第一次启动 WPF 并且每个人都告诉您“代码隐藏是邪恶的”(它确实是)时尤其烦人。所以你必须自己构建它。

自己构建

那么,如何自己创建此类功能呢?好吧,首先我们需要一个 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) == true;
    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;
  }
}

然后我们需要一个类,它实际上将 RoutedCommandHandler 与特定元素相关联。为此,我们将收集 RoutedCommandHandler 作为附加属性,如下所示:

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

然后,就像在我们的元素上使用类一样简单:

<local:RoutedCommandHandlers.Commands>
  <local:RoutedCommandHandler RoutedCommand="Help" Command="{Binding TheCommand}" />
</local:RoutedCommandHandlers.Commands>

交互.行为实现

了解了以上内容,你可能会问:

Wow, that's great, but that's a lot of code. I'm using Expression Behaviors already, so is there a way to simplify this a bit?

对此,我会回答:好问题!

如果您已经在使用 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) == true;
    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;
  }
}

用对应的XAML:

<i:Interaction.Behaviors>
  <local:RoutedCommandBinding RoutedCommand="Help" Command="{Binding TheCommand}" />
</i:Interaction.Behaviors>

关于c# - 如何在没有代码隐藏的情况下处理我的 ViewModel 中的 WPF 路由命令?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36183366/

有关c# - 如何在没有代码隐藏的情况下处理我的 ViewModel 中的 WPF 路由命令?的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  4. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  5. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  6. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  7. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  8. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  9. ruby - 难道Lua没有和Ruby的method_missing相媲美的东西吗? - 2

    我好像记得Lua有类似Ruby的method_missing的东西。还是我记错了? 最佳答案 表的metatable的__index和__newindex可以用于与Ruby的method_missing相同的效果。 关于ruby-难道Lua没有和Ruby的method_missing相媲美的东西吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/7732154/

  10. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

随机推荐