Getting access to the ViewModel from the view

Nov 9, 2010 at 11:09 PM

I'm brand new to Caliburn Micro, and fairly green on Silverlight too, so hopefully this isn't too dumb a question

I've wired up CM (which I'm loving, btw) using a view-first approach. I've wired in the Bind.Model attached property, and everything is wiring up just great.

The problem is that I want the Silverlight control to listen to some events on the ViewModel and re-broadcast them as events from the control itself. That means the control needs a viewmodel instance so it can hook up some event handlers to it.

It seems silly, but I don't know the best way to figure out *when* the view model has been set, so I can attach those event handlers. The DataContext property isn't virtual, so I can't override it. There doesn't seem to be a DataContextChanged event like there is in WPF. AFAICT CM doesn't provide any sort of notification mechanism that will tell me. Am I missing something obvious here?

Coordinator
Nov 10, 2010 at 2:02 AM

Hmm. I'm not sure what the best approach to recommend would be. Can you provide any more details? Why does the View need to "re-broadcast" events from the View-Model? Could you use the EventAggregator from your ViewModel for the same purpose? Are you switching different ViewModel instances out against the same View? or do you just need to capture a single VM instance when it is first set for the view? What is the base class for your ViewModel?

Nov 10, 2010 at 2:47 AM

The View in this case is a Silverlight control that raises events for Javascript or a Silverlight app to handle. In this specific example, it contains a ListBox, and raises its own special selection event. I think of the viewmodel/view communication as private, so currently the view code catches events from the viewmodel and converts them to "public" events. If there's a smarter way to do this, I'm certainly willing to consider it.

I'm not switching out VM's against the same view - I just need to capture the VM when it's first set on the view. The base class for the ViewModel is PropertyChangedBase.

Coordinator
Nov 10, 2010 at 1:19 PM

You situation sounds different than ones I have encountered in the past. I'm not sure I can offer an alternative implementation idea based on the info, but I do think I can help you solve your problem :) Implement IViewAware on your ViewModel (or alternatively, inherit your VM from Screen). Once you implement this interface, Micro's binding mechanism will call your AttachView method during the binding process, after the DataContext/Action.Target is set. You can use this to grab the view and tell it to wire any events, etc. You may wish to implement an interface on your view for this purpose, but that's up to you. This sort of explicit synchronization veers away from MVVM and towards a more SupervisingController style. There's nothing wrong with that....just wanted to point it out. Let me know if that works.

Nov 10, 2010 at 6:28 PM

Thanks Rob. I was thinking along the same lines - I'll give it a try.

Am I really doing something unusual here? If you were writing a fairly complex control that used MVVM internally but needed to raise regular events, how would you do it?

Another approach I was considering was using MEF to do the composition, then using the IPartImportsSatisfiedNotification stuff. But I don't think I grok CM well enough to know if that's possible. Any thoughts?

Nov 10, 2010 at 8:49 PM
Edited Nov 10, 2010 at 8:59 PM

I happened to attempt building a reusable control (within the scope of a single app) using Caliburn+MVVM internally and regular events for the outside, but I found that working at VM level is a better strategy on the long run. It's a lot easier to test and refactor mainly at VM side; this, however, come at the price of having very decomposed Views with a lot of "holes" where to push content at runtime.
It may be different, however, if you plan to package the component as a full featured SL control: in this scenario you may take advantage of MVVM leaving your user the freedom to choose a Xaml-only approach, if they will.

For the javascript communication purposes, I would try to leverage the EventBroker: the View can subscribe to a set of messages broadcasted by its corresponding VM (or by any other child VM, even deeply nested in the VMs tree), and translate them to CLR or Routed events.
Does it make sense for you?

(EDIT: I cannot advise you about MEF, sorry... I'm still trying hard to find THE use-case for it...)

Nov 10, 2010 at 10:02 PM
Edited Nov 10, 2010 at 10:04 PM

I'd go with the approach Rob suggested. Maybe something like this:

public class ViewModel : Screen
{
	IView _view;

	public override void AttachView(object view, object context)
	{
		_view = (IView) view;
	}

	public void MyAction()
	{
		_view.DoSomething();
	}
}

public partial class View : IView
{
	public void DoSomething()
	{
		SomethingHappened(this, EventArgs.Empty);
	}

	public event EventHandler SomethingHappened = delegate { };
}

public interface IView
{
	void DoSomething();
}

And I agree with the EvenBroker for javascript interopt. Mostyly, because I'd want to consolidate all that logic into some sort of gateway class. I'd avoid MEF until there was a specific need.