将事件传递给 ViewModel 的最佳方式是啥?

Posted

技术标签:

【中文标题】将事件传递给 ViewModel 的最佳方式是啥?【英文标题】:What's the best way to pass event to ViewModel?将事件传递给 ViewModel 的最佳方式是什么? 【发布时间】:2014-02-12 16:00:51 【问题描述】:

案例是:我有一个控件的事件,我希望我的 ViewModel 对其做出反应。目前我正在通过执行如下示例中的不可见按钮命令来执行此操作。

在 View.xaml 中:

<Control x:Name="SearchResultGrid" ... DataRefreshed="SearchResultRefreshed" />
<Button x:Name="SearchResultRefreshedButton" Visibility="Collapsed" Command="Binding SearchResultRefreshedCommand" />

在 View.xaml.cs 中:

private void SearchResultRefreshed(object sender, EventArgs e)

    if (SearchResultRefreshedButton.Command != null)
    
        SearchResultRefreshedButton.Command.Execute(SearchResultGrid.ResultRowCount);
    

这很好用,但对我来说似乎是一个 hack。我想知道是否有更好的(标准)方法来做到这一点?我找不到任何例子,这就是我自己“发明”的。

【问题讨论】:

Google 的交互触发器。 或者只是将视图的 DataContext 转换为 ViewModel 并做任何你想做的事情。ViewModel vm = this.DataContext as ViewModel;那么你可以做 vm.SomeAction @RohitVats 谢谢,我在一些例子中看到了它们的使用,但它看起来不像我需要的。现在我发现我错了。 【参考方案1】:

使用 MVVM,处理事件的一般方法是简单地将它们包装在 Attached Properties 中,或者使用 Attached Events。以下是在附加属性中使用 PreviewKeyDown 事件的示例:

public static DependencyProperty PreviewKeyDownProperty = DependencyProperty.RegisterAttached("PreviewKeyDown", typeof(KeyEventHandler), typeof(TextBoxProperties), new UIPropertyMetadata(null, OnPreviewKeyDownChanged));

public static KeyEventHandler GetPreviewKeyDown(DependencyObject dependencyObject)

    return (KeyEventHandler)dependencyObject.GetValue(PreviewKeyDownProperty);


public static void SetPreviewKeyDown(DependencyObject dependencyObject, KeyEventHandler value)

    dependencyObject.SetValue(PreviewKeyDownProperty, value);


public static void OnPreviewKeyDownChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)

    TextBox textBox = dependencyObject as TextBox;
    if (e.OldValue == null && e.NewValue != null) textBox.PreviewKeyDown += TextBox_PreviewKeyDown;
    else if (e.OldValue != null && e.NewValue == null) textBox.PreviewKeyDown -= TextBox_PreviewKeyDown;


private static void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)

    TextBox textBox = sender as TextBox;
    KeyEventHandler eventHandler = GetPreviewKeyDown(textBox);
    if (eventHandler != null) eventHandler(sender, e);

请注意,使用ICommand 代替不应该在视图模型中的实际KeyEventArgs 对象同样容易(而且更好)。只需创建一个 ICommand 类型的附加属性,然后从这个 TextBox_PreviewKeyDown 处理程序中调用它:

private static void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)

    TextBox textBox = sender as TextBox;
    ICommand command = PreviewKeyDownCommand(textBox);
    if (command != null && command.CanExecute(textBox)) command.Execute(textBox);

无论哪种方式,它都会像这样使用:

<TextBox TextBoxProperties.PreviewKeyDown="SomeKeyEventHandler" />

或者,如果您使用首选的ICommand 方法:

<TextBox TextBoxProperties.PreviewKeyDownCommand="Binding SomeCommand" />

【讨论】:

这看起来很有趣...我需要检查它是如何工作的。 这行得通,我也喜欢这个解决方案的简洁明了。我也被告知有关交互触发器的信息,但我认为这更好。谢谢! 你能展示附加事件的方式吗?我找不到如何使用附加事件在 VM 上注册事件处理程序,因为所有路由事件都不支持绑定,或者不是吗? @Uzivatel828,我的回答中有一个指向解释附加事件的页面的链接。如果这没有帮助,我建议您针对您的实际问题提出一个新问题。 @Sheridan:我知道如何实现附加事件。我不知道如何使用附加事件来处理非附加路由事件。也许我误解了你的回答。【参考方案2】:

就我个人而言,我从来不需要使用附加属性来处理控件的事件。在您的示例中,想要知道“SearchResultRefreshed”何时通过隐藏控件通知 ViewModel 的控件......为什么 ViewModel 不知道结果已被刷新?

如果结果首先来自 ViewModel,并且绑定用于在您的控件中显示它们,那么搜索结果已刷新的知识应该由您的 ViewModel 驱动,而不是由您的视图驱动。

在少数情况下,我发现需要摆脱 ICommand 和数据绑定。

【讨论】:

是的,数据来自 ViewModel,但是在 3dparty WinForms 控件内部进行过滤时会引发该事件。 哦,好的。然后我会使用交互命名空间,因为它比编写新的附加属性需要更少的代码:&lt;i:Interaction.Triggers&gt; &lt;i:EventTrigger EventName="DataRefreshed"&gt; &lt;i:InvokeCommandAction Command="Binding SearchResultRefreshedCommand, Mode=OneWay" CommandParameter="Binding Name, ElementName=SearchResultGrid"/&gt; &lt;/i:EventTrigger&gt; &lt;/i:Interaction.Triggers&gt;【参考方案3】:

你应该在你的控件中添加一个依赖属性DataRefreshed以便绑定它

这里有一个例子你怎么做

public static readonly DependencyProperty DataRefreshedProperty = DependencyProperty.Register(
  "DataRefreshed",
  typeof(bool),
  typeof("typeof yourcontrol here "),
  new FrameworkPropertyMetadata(null,
      FrameworkPropertyMetadataOptions.AffectsRender, 
      new PropertyChangedCallback(OnDataRefreshedChanged)
  )
);
public bool DataRefreshed

  get  return (bool)GetValue(DataRefreshedProperty); 
  set  SetValue(DataRefreshedProperty, value); 

然后,您可以像在 ViewModel 中定义的任何其他 WPF 属性一样操作您的属性,例如 SearchResultRefreshed

<Control x:Name="SearchResultGrid" ... DataRefreshed="Binding SearchResultRefreshed" />
<Button x:Name="SearchResultRefreshedButton" Visibility="Collapsed" Command="Binding SearchResultRefreshedCommand" />

看看下面的教程了解更多dependecyproperty and attachedproperty

【讨论】:

以上是关于将事件传递给 ViewModel 的最佳方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章

可以将 ViewModel 传递给 RecyclerView(无 DataBinding)吗?

SwiftUI 将 Int 值传递给 ViewModel

在线程之间传递信息的最佳方式是啥?

将 formGroup 传递给 mat-step 的最干净的方法是啥?

将数组从 php 传递到 MySQL 存储过程的最佳方法是啥?

将 PHP 设置传达给 Javascript 的最佳方式是啥?