Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/286.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何在ViewModel中处理WPF路由命令而无需代码隐藏?_C#_Wpf_Mvvm_Command_Routed Commands - Fatal编程技术网

C# 如何在ViewModel中处理WPF路由命令而无需代码隐藏?

C# 如何在ViewModel中处理WPF路由命令而无需代码隐藏?,c#,wpf,mvvm,command,routed-commands,C#,Wpf,Mvvm,Command,Routed Commands,根据我对MVVM的理解,直接在ViewModel中处理路由命令是一种很好的做法 当路由命令在ViewModel中定义为RelayCommand(或DelegateCommand)时,很容易直接绑定到命令,如下所示:command={Binding MyViewModelDefinedCommand} 实际上,对于在ViewModel之外定义的路由命令,我在视图的代码隐藏中处理这些命令,并将调用转发到ViewModel。但我觉得我不得不这么做很尴尬。这与推荐的MVVM良好实践背道而驰。我认为应该有

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

当路由命令在ViewModel中定义为RelayCommand(或DelegateCommand)时,很容易直接绑定到命令,如下所示:command={Binding MyViewModelDefinedCommand}

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

如何处理“System.Windows.Input.ApplicationCommand”或直接在Viewmodel中在Viewmodel外部定义的任何路由命令。 换句话说,对于在ViewModel之外定义的命令,如何直接处理ViewModel的CommandBinding回调“CommandExecute”和/或“CommandCanExecute”?
这可能吗?如果是,怎么做?如果没有,原因是什么?

这里有一个将命令绑定到按钮的简单示例:

MainWindow.xaml

<Window x:Class="csWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow">
    <Canvas>
        <Button Name="btnCommandBounded" Command="{Binding cmdExecuteSubmit}" Height="29" Width="68" Content="Submit"></Button>
    </Canvas>
</Window>
MainWindowViewModel.cs

class MainWindowViewModel
    {
        public ICommand cmdExecuteSubmit { get; set; }
        public MainWindowViewModel()
        {
            cmdExecuteSubmit = new RelayCommand(doSubmitStuff);
        }
        public void doSubmitStuff(object sender)
        {
            //Do your action here
        }
   }

我想将问题的措辞改为:

如何在ViewModel中处理WPF路由命令而无需代码隐藏

对此,我会回答:好问题

WPF并没有提供一种内置的方法来实现这一点,当您第一次启动WPF时,每个人都告诉您“代码隐藏是邪恶的”(它确实是邪恶的),这尤其令人恼火。所以你必须自己建造它

自己建造 那么,我们自己如何创建这样的功能呢?首先,我们需要一个等价的
命令绑定

/// <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;
  }
}
然后,它就像在元素上使用类一样简单:

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

交互。行为实现 了解上述情况后,您可能会问:

哇,那太好了,但是代码太多了。我已经在使用表达式行为了,有没有办法简化一下

对此,我会回答:好问题

如果您已经在使用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;
  }
}
//
///允许将路由命令与非普通命令关联。
/// 
公共类RoutedCommandBinding:行为
{
公共静态只读DependencyProperty CommandProperty=DependencyProperty.Register(
“命令”,
类型(ICommand),
类型(RoutedCommandBinding),
新属性元数据(默认值(ICommand));
///RoutedCommand激发时应执行的命令。
公共ICommand命令
{
获取{return(ICommand)GetValue(CommandProperty);}
set{SetValue(CommandProperty,value);}
}
///触发的命令。
公共ICommand RoutedCommand{get;set;}
受保护的覆盖无效附加()
{
base.onatached();
var binding=新命令绑定(RoutedCommand、HandleExecuted、HandleCanExecute);
AssociatedObject.CommandBindings.Add(绑定);
}
///当前命令的代理。CanExecute(对象)。
私有void HandleCanExecute(对象发送方,CanExecuteRoutedEventArgs e)
{
e、 CanExecute=命令?.CanExecute(e.参数)==真;
e、 已处理=正确;
}
///当前命令的代理。执行(对象)。
私有void HandleExecuted(对象发送方,ExecutedRoutedEventArgs e)
{
命令?.Execute(如参数);
e、 已处理=正确;
}
}
使用相应的XAML:

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

公认的答案很好,但OP似乎不太了解RoutedCommand是如何工作的,这造成了一些混乱。引述问题:

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

这是模棱两可的,但无论如何都是不正确的:

  • 或者-不能将RoutedCommand定义为中继/委派命令,因为RoutedCommand是ICommand接口的不同实现
  • 或者-如果虚拟机公开了一个实际的RoutedCommand,那么仍然会面临与在虚拟机之外定义的RoutedCommand相同的问题(因为RoutedCommand的工作方式)
  • RoutedCommand是ICommand的具体实现 RoutedCommand的Execute/CanExecute方法不包含我们的应用程序逻辑(实例化RoutedCommand时,不传递Execute/CanExecute委托)。它们引发路由事件,与其他路由事件一样,这些事件遍历元素树。这些事件(PreviewCanExecute、CanExecute、PreviewExecuted、Executed)正在查找对该RoutedCommand具有CommandBinding的元素。CommandBinding对象具有这些事件的事件处理程序,这就是我们的应用程序逻辑所在(现在很清楚为什么从VM公开RoutedCommand不能解决问题)

    xaml:

    
    ...
    //当命令被执行时,事件在元素树中向上移动
    //它在窗口上找到CommandBinding,并执行附加的处理程序
    ...
    
    命令绑定对象 CommandBinding类不从DependencyObject继承(它的Command属性不能绑定到VM上公开的命令)。您可以使用附加到CommandBinding的事件处理程序将调用(在代码隐藏中)转发到VM—那里没有重要的内容,没有逻辑(没有要测试的内容)。如果您不需要代码隐藏,那么接受的答案有很好的解决方案(为您转发)。

    如果没有很好地显示您所尝试的内容、命令的实现以及该实现与各种对象(视图、模型等)的关系,就不可能为您的问题提供具体的答案。很可能,您只想在一个UI对象(例如窗口本身)的
    CommandBindings
    集合中声明
    CommandBinding
    元素。但是有一个
    /// <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;
      }
    }
    
    <i:Interaction.Behaviors>
      <local:RoutedCommandBinding RoutedCommand="Help" Command="{Binding TheCommand}" />
    </i:Interaction.Behaviors>
    
    // The command could be declared as a resource in xaml, or it could be one 
    // of predefined ApplicationCommands
    public static class MyCommands {
        public static readonly RoutedCommand FooTheBar = new RoutedCommand();
    }
    
    <Window x:Class...
            xmlns:cmd="clr-namespace:MyCommands.Namespace">
        <Window.CommandBindings>
            <CommandBinding Command="{x:Static cmd:MyCommands.FooTheBar}"
                            Executed="BarFooing_Executed"/>
        </Window.CommandBindings>
    
    <Grid>
    ...
    // When command is executed, event goes up the element tree, and when
    // it finds CommandBinding on the Window, attached handler is executed
    <Button Command="{x:Static cmd:MyCommands.FooTheBar}"
            Content="MyButton"/>
    ...
    </Grid>
    </Window>