43

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

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

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

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

标准的强事件订阅也很糟糕,因为我需要为一个 View 切换多个 ViewModel(因为每次创建 View 都会占用过多的 CPU 时间)。

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

4

5 回答 5

3

一些评论:

  • 您可以使用弱事件模式来确保视图可以被 GC,即使它仍然附加到视图模型的事件
  • 如果您已经为一个视图切换了多个 VM,那不是附加/分离处理程序的理想位置吗?
  • 根据您的具体情况,您可以让 VM 公开一个状态属性,视图将其用作动画、过渡和其他视觉变化的触发器。视觉状态管理器非常适合这种事情。
于 2009-12-12T20:09:32.780 回答
2

这也是我纠结的事情……

与其他人所说的类似,但这里有一个带有一些代码片段的示例......这个示例展示了如何使用 pub/sub 让 View 订阅 VM 触发的事件 - 在这种情况下,我执行 GridView。重新绑定以确保 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 以防止内存泄漏。

视图模型(酒吧):

 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 同步。

于 2012-04-11T15:44:19.710 回答
1

imho yYand 分开

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

虽然您可以通过属性绑定来做到这一点,但正如托马斯提到的那样,它更像是一种黑客行为;我一直有这样的感觉。

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

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

于 2011-09-14T10:37:51.563 回答
1

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

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

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

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

于 2009-12-21T19:32:45.910 回答
0

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

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

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

于 2009-12-21T02:22:06.553 回答