【问题标题】:Can we raise an event from ViewModel to View?我们可以从 ViewModel 向 View 发起事件吗?
【发布时间】:2022-08-14 18:13:11
【问题描述】:

例如,我的ViewModel 做某事。在那之后,我想通知我的View,以便做一些只能在View 中完成的事情。

就像是;

public class MyViewModel : ViewModelBase{
  ...
  private void DoSomething(){
     //raise the event here and notify the View
  }
}

然后在视图中;

public MyView(){
  InitializeComponent();
  ...
}

private void ViewModelSaidDoSomething(){
 //The View Model raises an event, do something here about it...
}

在不破坏 MVVM 概念的情况下这可能吗?

  • 一般没有错。如果DoSomething 是公共方法,您可以考虑使用 async/await 代替事件。
  • \"在不破坏 MVVM 概念的情况下这可能吗?\"它不仅与 MVVM 概念兼容,而且完全正确的方式去做吧。
  • 如果您使用事件,那么您应该使用弱事件模式。这是内置于绑定工作方式的。所以你可以使用事件,但 imo 这是一个糟糕的选择。我更喜欢视图中的依赖属性和更改处理程序。将其绑定到视图模型中的属性。在视图模型中更改该属性,视图将执行您的事件处理操作。

标签: c# wpf


【解决方案1】:

是的,这是可能的。通常,您可以做的是从您的ViewModel 定义事件,然后让您的View 订阅该事件。

例如,在您的 ViewModel 中。

public class MyViewModel : ViewModelBase{
  ...
  //Define your event.
  public delegate void YourEventAction(string your_argument); 
  public event YourEventAction? YourEvent;
  
  private void DoSomething(){
     YourEvent?.Invoke(your_argument); //raise the event here, any subscriber will received this.
  }
}

然后您可以在您的视图中订阅该事件。

public MyView(){
  InitializeComponent();
  ...
  DataContextChanged += ViewModelSaidDoSomething; //subscribe to DataContextChanged.
}

private void ViewModelSaidDoSomething(){ 
    var viewModel = (MyViewModel)DataContext; //Get your ViewModel from your DataContext.
    viewModel.YourEvent += (your_argument) =>{  //Subscribe to the event from your ViewModel.
        //The View Model raises an event, do something here about it...
    };
}

希望有帮助。

【讨论】:

  • 不要忘记取消订阅旧的 DataContext 实例!!! (您必须显示正确的事件处理程序签名,并省略 lambda 表达式作为处理程序)
  • 订阅活动有很多缺点。潜在的内存泄漏。视图现在需要“了解”视图模型。何时添加对处理程序的订阅?它什么时候被删除?如果视图是一个用户控件并且“只是”继承了数据上下文怎么办?
  • @Andy你能详细建议一个更好的方法吗?
【解决方案2】:

我对任何视图模型 <-> 视图通信的主要候选人都是绑定的。 这是一个弱事件模式,后期绑定,视图不需要知道视图模型是什么类型。他们在实现绑定方面遇到了很多麻烦,而且 IMO 运行良好。无论如何,绑定意味着视图和视图模型在保持合理实用性的同时尽可能松散耦合。

这是一个例子。 目的是允许视图模型关闭窗口。

视图功能封装在控件中。 只有代码。

public class CloseMe : Control
{
    public bool? Yes
    {
        get
        {
            return (bool?)GetValue(YesProperty);
        }
        set
        {
            SetValue(YesProperty, value);
        }
    }
    public static readonly DependencyProperty YesProperty =
        DependencyProperty.Register("Yes",
                    typeof(bool?),
                    typeof(CloseMe),
                    new PropertyMetadata(null, new PropertyChangedCallback(YesChanged)));
    private static void YesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if ((bool?)e.NewValue != true)
        {
            return;
        }
        CloseMe me = (CloseMe)d;
        Window parent = Window.GetWindow(me) as Window;
        parent.Close();
    }
}

窗口或用户控件中的任何位置进入您要关闭的窗口:

   <local:CloseMe Yes="{Binding CloseYes, Mode=TwoWay}"/>

这绑定到视图模型中的布尔属性 CloseYes。设置为 true 并关闭窗口。

我们可以想象将一些数据封装在一个更复杂的视图模型对象中,而不仅仅是一个布尔值。但这不会那么优雅。 视图不应该处理数据,所以如果您要发送复杂数据,我会质疑您在做什么。

不过把这些疑虑放在一边。

如果您想通过松耦合将复杂类型从 A 转移到 B,那么 pub sub 模式是一个不错的选择。
mvvm 工具包是我的首选框架,其中有一个您可以使用的实现。信使: https://docs.microsoft.com/en-us/windows/communitytoolkit/mvvm/messenger

发送的消息是您定义的对象,您可以使用 Wea​​kReferenceMessenger。它做了它所暗示的并依赖于弱引用。从而降低内存泄漏的风险。 其他框架具有类似的机制,因此您可以查看您喜欢的框架中的内容。

【讨论】:

    猜你喜欢
    • 2012-07-17
    • 1970-01-01
    • 1970-01-01
    • 2013-09-05
    • 1970-01-01
    • 1970-01-01
    • 2011-12-14
    • 2019-02-19
    • 1970-01-01
    相关资源
    最近更新 更多