43

I have quite simple (I hope :)) problem:

In MVVM, View usually listens on changes of ViewModel's properties. However, I would sometimes like to listen on event, so that, for example, View could start animation, or close window, when VM signals.

Doing it via bool property with NotifyPropertyChanged (and starting animation only when it changes from false to true) is possible, but it feels like a hack, I'd much prefer to expose event, as it is semantically correct.

Also, I'd like to do it without code in codebehind, as doing viewModel.myEvent += handler there would mean that I'd have manually unregister the event in order to allow View to be GC'd - WPF Views are already able to listen on properties 'weakly', and I'd much prefer to program only declaratively in View.

The standard strong event subscription is also bad, because I need to switch multiple ViewModels for one View (because creating View every time takes too much CPU time).

Thank you for ideas (if there is a standard solution, a link to msdn will suffice)!

Tomáš Kafka
  • 4,405
  • 6
  • 39
  • 52
  • 1
    Propertychanged is an event. What is not semanticly correct with listenig to a bool property in a trigger? – adrianm Dec 12 '09 at 19:19
  • 12
    I really don't get why people are so opposed to codebehind in the view. The reason views respond to properties is because of code in the codebehind, it's just hidden in the framework. – Cameron MacFarland Dec 13 '09 at 02:15
  • 2
    adrianm: I tried creating bool property in VM that is always false, and raising OnPropertyChanged on it, but View didn't react - it seems that WPF will do something only when the actual value changes. So I would need to toggle that bool on and off, MyProp = true; OnPropertyChanged("MyProp"); MyProp = false; OnPropertyChanged("MyProp"); - instead of RaiseMyEvent(). See? – Tomáš Kafka Dec 13 '09 at 05:15
  • 5
    Cameron: Because subscribing to event in View is strong coupling, and doing it weakly in order not to cause memory leaks is really tricky. Also, we are sometimes using several Views during development (to try alternative approaches to GUI), and keeping their codebehinds in sync would be unnecessary maintenance burden. It is also possible to test View by assigning an anonymous type with sample data, this would be much harder with strongly typed codebehind. – Tomáš Kafka Dec 13 '09 at 05:19
  • 1
    I use the principle where the VM is aware of the view via an IView interface. I would solve it with a method in the interface which starts the animation. – adrianm Dec 13 '09 at 15:49

5 Answers5

3

Some comments:

  • You can use the weak event pattern to ensure that the view can be GC'd even if it is still attached to the view model's event
  • If you're already switching multiple VMs in for the one view, wouldn't that be the ideal place to attach/detach the handler?
  • Depending on your exact scenario, you could just have the VM expose a state property which the view uses as a trigger for animations, transitions, and other visual changes. Visual state manager is great for this kind of thing.
Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • Thanks for the links, Kent. I know about weak event pattern, but I hoped that WPF has something built in (to bind some action to VM event) + weak events are really tricky in c#: http://www.codeproject.com/KB/cs/WeakEvents.aspx . Switching might occur from several places, and I can't say when to unregister the last listener (this would be solved by weak events). State is what I don't need - I practically have one state, my events don't occur on state changes. Thank you, but I'll see if someone provides answer for my case. – Tomáš Kafka Dec 12 '09 at 21:13
  • 1
    By the way, I find it strange that you can trigger for example animation when bound property changes from x to y, but more natural scenario (trigger animation when event occurs) seems to be not supported... – Tomáš Kafka Dec 12 '09 at 21:14
  • The problem is that your model only got one state. Why do you want to start an animation if nothing changes in the model? – adrianm Dec 13 '09 at 21:46
  • 5
    Well, just imagine that you are building a twitter client, and you want to flash some icon when new messages arrive. What would be the state? The moment when messages arrived can signal an event, but it doesn't toggle state (if you switched from 'original state' to 'new messages arrived', how would you signal when another batch of messages arrive?). The semantics of this are event signal, not a state toggle, and I hoped WPF would respect this... – Tomáš Kafka Jan 05 '10 at 22:40
  • link for weak event pattern is dead? Perhaps you could replace with some code? – JumpingJezza Jun 19 '15 at 00:48
  • what is GC'd pls – Galilo Galilo May 15 '19 at 02:12
  • GC = Garbage Collector – moon Feb 20 '22 at 16:41
  • Wayback link for broken Visual State Manager link: https://web.archive.org/web/20120914042512/http://windowsclient.net:80/wpf/wpf35/wpf-35sp1-toolkit-visual-state-manager-overview.aspx – moon Feb 20 '22 at 16:42
2

This is something that I wrestled with as well...

Similar to what others are saying, but here is an example with some code snippets... This example shows how to use pub/sub to have a View subscribe to an event fired by the VM - in this case I do a GridView. Rebind to ensure the gv is in sync with the VM...

View (Sub):

 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;
        }
    }

Call unsub from the close method to prevent memory leaks.

ViewModel (Pub):

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

Payload class

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;
    }
}

Note that you can also set the constructor up to pass in a Guid, or some identified in, which can be set on Pub and checked on sub to be sure pub/sub is in sync.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
1

imho yYand separated

  1. state - to be able to move data back/forth between view <-> vm
  2. actions - to be able to call onto view model functions/commands
  3. notifications - to be able to signal to the view that something has happened and you want it to take a viewy action like make an element glow, switch styles, change layout, focus another element etc.

while is true that you can do this with a property binding, its more of a hack as tomas mentioned; always has felt like this to me.

my solution to be able to listen for 'events' from a view model aka notifications is to simple listen for data-context changes and when it does change i verify the type is the vm i'm looking for and connect the events. crude but simple.

what i would really like is a simple way to define some 'view model event' triggers and then provide some kind of handler for it that would react on the view side of things all in the xaml and only drop to code behind for stuff thats not do-able in xaml

wattostudios
  • 8,666
  • 13
  • 43
  • 57
voidx
  • 11
  • 1
0

Like adrianm said, when you trigger your animation off a bool property you are actually responding to an event. Specifically the event PropertyChanged which the WPF subsystem. Which is designed to attach/detach correctly to/from so that you don't leak memory (you may forget to do this when wiring an event yourself and cause a memory leak by having a reference active to an object which otherwise should be GCed).

This allows you to expose your ViewModel as the DataContext for the control and respond correctly to the changing of properties on the datacontext through databinding.

MVVM is a pattern that works particularly well with WPF because of all these things that WPF gives you, and triggering off a property change is actually an elegant way to use the entire WPF subsystem to accomplish your goals :)

Jim Wallace
  • 1,006
  • 8
  • 21
  • 1
    Hi Jim, thanks - I love MVVM too, however the problem is that when my property is false (or true) all the time, the PropertyChanged("MyPropertyName") has no effect - the listening wpf control is called, but it notices that no parameter really changed, so there is no reason to (for example) start the animation. I'd have to make an ugly hack like MyProp = true; PropertyChanged("MyProp"); MyProp = false; PropertyChanged("MyProp"); to make this work, but feels to clunky (and wastes some performance as well). – Tomáš Kafka Dec 21 '09 at 11:36
0

A more general question to ask is: "Why am I trying to deal with this event in my ViewModel?"

If the answer has anything to do with view-only things like animations, I'd argue the ViewModel needs not know about it: code behind (when appropriate), Data/Event/PropertyTriggers, and the newer VisualStateManager constructs will serve you much better, and maintain the clean separation between View and ViewModel.

If something needs to "happen" as a result of the event, then what you really want to use is a Command pattern - either by using the CommandManger, handling the event in code behind and invoking the command on the view model, or by using attached behaviors in the System.Interactivity libs.

Either way, you want to keep your ViewModel as "pure" as you can - if you see anything View-specific in there, you're probably doing it wrong. :)

JerKimball
  • 16,584
  • 3
  • 43
  • 55
  • 9
    I don't think you understood the question well - let's say my Model is a twitter client that has event 'new message arrived'. ViewModel is listening on Model, and signals a 'NewMessageArrived' event as well (and it has no idea how does View react on this event). Now, I'd like the View to start an animation when the event occurs. There is no state change (see my comments above), so DataTrigger, PropertyTrigger and VisualStateManager are out of game, and EventTrigger listens on RoutedEvents, that originate only in UIElements (so the ViewModel cannot raise them!). – Tomáš Kafka Jan 05 '10 at 22:48
  • I totally agree with this, if the code you need to add is ONLY to do with the view, i.e. Animations or updating the view, it should only need to be part of the view. This is because if you try to create another view it may have different requirements for animations or updating. – Aaron Murgatroyd Jul 12 '12 at 02:22