How to make View react to event's raised in ViewModel?

Oct 27, 2011 at 4:53 AM

Just wondering if Cinch has any clever ways of solving my problem that I'm not aware of....

I have a usercontrol i've built, which acts as a View and has a ViewModel as its DataContext. The ViewModel has a 'Save' method which simply saves stuff to my db. Whenever this 'Save' method completes, I want it to notify the usercontrol so it can then run some internal logic.

So, basically I need to raise an event in the ViewModel and handle it in the View. The only ways I can think of doing this are:

  1. Have an 'IsSaved' boolean property in the ViewModel and switch it when the 'Save' method completes. Then create a dp in the usercontrol and bind it to the 'IsSaved' property in the ViewModel. When the dp changes, it means the 'Save' method has completed.
  2. Have a command in the ViewModel and use Cinch's CompletedAwareCommandTrigger on the usercontrol to detect when it has completed executing. When the 'Save' method has completed, execute the command causing the CompletedAwareCommandTrigger to execute an Action in the usercontrol.
  3. Add a 'SaveCompleted' event to the ViewModel and create an attached behaviour similar to Cinch's TextBoxFocusBehavior that wires up the 'SaveCompleted' event of the usercontrol's datacontext (ViewModel) to a handler in the usercontrol. When the event is raised in the ViewModel, the handler is fired in the usercontrol's code-behind.

All of these are valid solutions, yet they seem a bit clunky:

  • Soln 1 seems wrong to use a property for notifying something as the dp binding to it will change each time 'IsSaved' property is changed to either true or false which seems kind of silly.
  • Soln 2 seems wrong to use a command for no other reason other than to raise an event.
  • Soln 3 is close to what I want but its hard-coded purely for this scenario. If I have lots of other events that need to be handled in the same way, then I'd have to create separate behaviours for each of them.
Oct 27, 2011 at 2:09 PM

Not sure if its the best way, but I've stumbled across the DataEventTrigger from the Expression Blend Samples library which I'll use like this:

<UserControl x:Class="MyUserControl"
             x:Name="myUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
             xmlns:local="clr-namespace:DataEventTriggerTest"
             xmlns:meffed="http:\\www.codeplex.com\MEFedMVVM"
             meffed:ViewModelLocator.ViewModel="MyViewModel"             
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <i:Interaction.Triggers>
        <local:DataEventTrigger EventName="EventRaisedInMyViewModel" >
            <ei:CallMethodAction TargetObject="{Binding ElementName=myUserControl,Mode=OneWay}" MethodName="MethodToRunInUserControlCodeBehind" />
        </local:DataEventTrigger>
    </i:Interaction.Triggers>
</UserControl>


I'll probably need to make DataEventTrigger use Weak Events (i think) to avoid memory leaks. Other than that, it works well.

Coordinator
Oct 28, 2011 at 10:55 AM
Edited Oct 28, 2011 at 10:56 AM

Cinch does actually have a method of doing this directly.

So what you do is this

 

declare an interface such as this one

 

 

public ISomeInterface
{
   void SomeMethodOnView();
}

 

Which is implemented by view like this

 

public partial class SomeView : UserControl, ISomeInterface
{
   public void SomeMethodOnView()
   {
 	//Do your logic here
   }

}

 

 

Then in the the ViewModel for that view you can call that ISomeInterface method that the view implements, by using the IViewAwareStatus service as follows

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.Composition;

using MEFedMVVM.ViewModelLocator;
using Cinch;
using MEFedMVVM.Common;

namespace Demo
{

    [ExportViewModel("SomeViewModel")]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class SomeViewModel : ViewModelBase
    {
        private IViewAwareStatus viewAwareStatusService;


        [ImportingConstructor]
        public SomeViewModel(IViewAwareStatus viewAwareStatusService)
        {
            this.viewAwareStatusService = viewAwareStatusService;
            this.viewAwareStatusService.ViewLoaded += ViewAwareStatusService_ViewLoaded;
        }


        private void ViewAwareStatusService_ViewLoaded()
        {

            if (Designer.IsInDesignMode)
                return;

            ISomeInterface someInterface = viewAwareStatusService.View as ISomeInterface
            if (someInterface != null)
            {
		//Call the method on the view
                someInterface.SomeMethodOnView();
            }
        }
       
    }
}

 

 

Though your method works too. I prefer Cinchs native approach though 

 

 

 

 

Oct 28, 2011 at 12:39 PM

Ahh yeah, i knew there had to be something tucked away in Cinch for this, but didn't even think of using the View Aware service in this way. I'll definitely do it the Cinch way as it will not only avoid the need for modd'ing the DataEventTrigger to use weak events but is just plain nicer.

Thanks Sacha, appreciate your help once again.

Coordinator
Oct 28, 2011 at 2:07 PM

No worries. The ViewAwareStatus thing is plain cool in my opinion, allows you to use the View without even knowing you are using the view, and it all gets collected nicely thanks to MEF releasing the service.

Coordinator
Oct 28, 2011 at 2:09 PM

One word of warning though, if you hook to view events in this manner make sure they are Weak.