【问题标题】:WPF: how to signal an event from ViewModel to View without code in codebehind? [duplicate]WPF:如何在没有代码隐藏代码的情况下从 ViewModel 向 View 发送事件信号? [复制]
【发布时间】:2010-12-26 01:04:46
【问题描述】:

我有一个很简单的(我希望:))问题:

在 MVVM 中,View 通常监听 ViewModel 属性的变化。但是,有时我想监听事件,例如,当 VM 发出信号时,View 可以启动动画或关闭窗口。

通过带有 NotifyPropertyChanged 的​​ bool 属性(并且仅当它从 false 变为 true 时才开始动画)是可能的,但感觉就像一个 hack,我更喜欢公开事件,因为它在语义上是正确的。

另外,我想在代码隐藏中没有代码的情况下这样做,因为这样做 viewModel.myEvent += handler 意味着我必须手动取消注册事件以允许 View 被 GC'd - WPF 视图已经能够'弱'地监听属性,我更喜欢仅在 View 中以声明方式编程。

标准的强事件订阅也不好,因为我需要为一个View切换多个ViewModel(因为每次创建View都占用了太多的CPU时间)。

感谢您的想法(如果有标准解决方案,指向 msdn 的链接就足够了)!

【问题讨论】:

  • Propertychanged 是一个事件。在触发器中监听 bool 属性在语义上是不正确的?
  • 我真的不明白为什么人们在视图中如此反对代码隐藏。视图响应属性的原因是代码隐藏中的代码,它只是隐藏在框架中。
  • adrianm:我尝试在 VM 中创建始终为 false 的 bool 属性,并在其上提高 OnPropertyChanged,但 View 没有反应 - 似乎 WPF 只有在实际值发生变化时才会执行某些操作。所以我需要打开和关闭那个 bool,MyProp = true; OnPropertyChanged("MyProp");我的道具=假; OnPropertyChanged("MyProp"); - 而不是 RaiseMyEvent()。看到了吗?
  • Cameron:因为在View中订阅事件是强耦合的,为了不造成内存泄漏而弱做这件事真的很棘手。此外,我们有时会在开发过程中使用多个视图(尝试使用 GUI 的替代方法),并且保持它们的代码隐藏同步将是不必要的维护负担。也可以通过为示例数据分配匿名类型来测试 View,这对于强类型代码隐藏会更加困难。
  • 我使用虚拟机通过 IView 接口感知视图的原理。我会在界面中使用启动动画的方法来解决它。

标签: wpf mvvm events view viewmodel


【解决方案1】:

这也是我纠结的事情……

与其他人所说的类似,但这里有一个带有一些代码 sn-ps 的示例...这个示例展示了如何使用 pub/sub 让 View 订阅 VM 触发的事件 - 在这种情况下,我做一个网格视图。重新绑定以确保 gv 与 VM 同步...

查看(子):

 using Microsoft.Practices.Composite.Events;
 using Microsoft.Practices.Composite.Presentation.Events;

 private SubscriptionToken getRequiresRebindToken = null;

    private void SubscribeToRequiresRebindEvents()
    {
        this.getRequiresRebindToken =
            EventBus.Current.GetEvent<RequiresRebindEvent>()
            .Subscribe(this.OnRequiresRebindEventReceived, 
                ThreadOption.PublisherThread, false,
                MemoryLeakHelper.DummyPredicate);
    }

    public void OnRequiresRebindEventReceived(RequiresRebindEventPayload payload)
    {
        if (payload != null)
        {
            if (payload.RequiresRebind)
            {
                using (this.gridView.DeferRefresh())
                {
                    this.gridView.Rebind();
                }
            }
        }
    }

    private void UnsubscribeFromRequiresRebindEvents()
    {
        if (this.getRequiresRebindToken != null)
        {
            EventBus.Current.GetEvent<RequiresRebindEvent>()
                .Unsubscribe(this.getRequiresRebindToken);
            this.getRequiresRebindToken = null;
        }
    }

从 close 方法调用 unsub 以防止内存泄漏。

ViewModel (Pub):

 private void PublishRequiresRebindEvent()
 {
      var payload = new RequiresRebindEventPayload();
      payload.SetRequiresRebind();
      EventBus.Current.GetEvent<RequiresRebindEvent>().Publish(payload);
 }

有效载荷类

using System;
using Microsoft.Practices.Composite.Presentation.Events;

public class RequiresRebindEvent 
    : CompositePresentationEvent<RequiresRebindEventPayload>
{

}

public class RequiresRebindEventPayload
{
    public RequiresRebindEventPayload()
    {
        this.RequiresRebind = false;
    }

    public bool RequiresRebind { get; private set; }

    public void SetRequiresRebind()
    {
        this.RequiresRebind = true;
    }
}

请注意,您还可以将构造函数设置为传入一个 Guid 或一些标识,可以在 Pub 上设置并在 sub 上检查以确保 pub/sub 同步。

【讨论】:

  • 这不是在视图中使用代码隐藏吗?哪个 OP 要求避免。
【解决方案2】:

恕我直言,分开

  1. 状态 - 能够在视图之间来回移动数据 vm
  2. 动作 - 能够调用视图模型函数/命令
  3. 通知 - 能够向视图发出信号表明发生了某些事情,并且您希望它采取可视化的操作,例如使元素发光、切换样式、更改布局、聚焦另一个元素等。

虽然您可以通过属性绑定来做到这一点,但正如 tomas 所提到的,它更像是一种 hack;我一直有这样的感觉。

我能够从视图模型(即通知)监听“事件”的解决方案是简单地监听数据上下文的变化,当它发生变化时,我验证类型是我正在寻找的 vm 并连接事件.粗略但简单。

我真正想要的是一种简单的方法来定义一些“视图模型事件”触发器,然后为其提供某种处理程序,该处理程序将在 xaml 中的所有事物的视图方面做出反应,并且只下降到后面的代码在 xaml 中无法实现的东西

【讨论】:

    【解决方案3】:

    一些cmets:

    • 您可以使用weak event pattern 来确保即使视图仍然附加到视图模型的事件,也可以进行 GC 处理
    • 如果您已经为一个视图切换了多个 VM,那不是附加/分离处理程序的理想位置吗?
    • 根据您的具体情况,您可以让 VM 公开一个状态属性,视图将其用作动画、过渡和其他视觉变化的触发器。 Visual state manager 非常适合这种事情。

    【讨论】:

    • 感谢您的链接,肯特。我知道弱事件模式,但我希望 WPF 有内置的东西(将一些动作绑定到 VM 事件)+ 弱事件在 c# 中真的很棘手:codeproject.com/KB/cs/WeakEvents.aspx。切换可能从几个地方发生,我不能说何时取消注册最后一个监听器(这将通过弱事件来解决)。状态是我不需要的——我实际上只有一个状态,我的事件不会在状态变化时发生。谢谢,但我会看看是否有人为我的情况提供答案。
    • 顺便说一句,我觉得奇怪的是,当绑定属性从 x 变为 y 时可以触发例如动画,但似乎不支持更自然的场景(事件发生时触发动画)。 .
    • 问题是你的模型只有一个状态。如果模型没有任何变化,为什么要启动动画?
    • 好吧,想象一下您正在构建一个 twitter 客户端,并且您想在新消息到达时闪烁一些图标。会是什么状态?消息到达的那一刻可以发出事件的信号,但它不会切换状态(如果您从“原始状态”切换到“新消息到达”,当另一批消息到达时您将如何发出信号?)。它的语义是事件信号,而不是状态切换,我希望 WPF 能够尊重这一点......
    • 弱事件模式的链接已失效?也许你可以用一些代码替换?
    【解决方案4】:

    一个更普遍的问题是:“我为什么要在我的 ViewModel 中处理这个事件?”

    如果答案与动画之类的仅视图有关,我认为 ViewModel 不需要知道它:后面的代码(适当时)、Data/Event/PropertyTriggers 和较新的 VisualStateManager 构造将提供你会更好,并保持 View 和 ViewModel 之间的清晰分离。

    如果某些事情需要“发生”作为事件的结果,那么您真正想要使用的是命令模式 - 通过使用 CommandManger,在代码中处理事件并在视图模型上调用命令,或者通过使用 System.Interactivity 库中的附加行为。

    无论哪种方式,您都希望您的 ViewModel 尽可能“纯粹”——如果您在其中看到任何特定于 View 的内容,那么您可能做错了。 :)

    【讨论】:

    • 我认为你没有很好地理解这个问题 - 假设我的模型是一个 twitter 客户端,它有事件“新消息到达”。 ViewModel 正在侦听 Model,并发出“NewMessageArrived”事件的信号(它不知道 View 如何对此事件作出反应)。现在,我希望 View 在事件发生时启动动画。没有状态改变(见上面我的 cmets),所以 DataTrigger、PropertyTrigger 和 VisualStateManager 不在游戏中,EventTrigger 监听 RoutedEvents,它只来自 UIElements(所以 ViewModel 不能引发它们!)。
    • 我完全同意这一点,如果您需要添加的代码仅与视图有关,即动画或更新视图,它应该只需要成为视图的一部分。这是因为如果您尝试创建另一个视图,它可能对动画或更新有不同的要求。
    【解决方案5】:

    就像 adrianm 所说,当您从 bool 属性触发动画时,您实际上是在响应一个事件。特别是 WPF 子系统的事件PropertyChanged。它旨在正确地附加/分离,以便您不会泄漏内存(您可能会在自己连接事件时忘记执行此操作,并通过将引用激活到应该被 GCed 的对象而导致内存泄漏) .

    这允许您将 ViewModel 公开为控件的DataContext,并通过数据绑定正确响应数据上下文上的属性更改。

    MVVM 是一种特别适用于 WPF 的模式,因为 WPF 为您提供了所有这些东西,并且触发属性更改实际上是使用整个 WPF 子系统来实现您的目标的一种优雅方式:)

    【讨论】:

    • 嗨 Jim,谢谢 - 我也喜欢 MVVM,但问题是当我的属性一直为假(或真)时,PropertyChanged("MyPropertyName") 无效 - 监听 wpf control 被调用,但它注意到没有真正改变参数,所以没有理由(例如)启动动画。我必须做一个丑陋的黑客,比如 MyProp = true; PropertyChanged("MyProp");我的道具=假; PropertyChanged("MyProp");完成这项工作,但感觉很笨重(并且也浪费了一些性能)。
    猜你喜欢
    • 2016-07-11
    • 2019-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-14
    • 2013-07-05
    • 1970-01-01
    相关资源
    最近更新 更多