5

let's say that in my application, there is an user interface for representing a (geographic) map. It is integrated into the application as a UserControl and has its view model behind it.

Now, suppose I want to provide other parts of my application with a generic service interface to perform common tasks on the map (zoom, pan etc.) and not worry about the UI specifics. I could give away direct reference to the viewmodel, but I am pretty sure I would violate separation of concerns principle, not to mention it would be less testable.

So there are few questions:

  1. Does it make sense and is it good practice to implement such services (which act as an intermediate link to the UI) in the first place?
  2. Since the service operates directly on the map's viewmodel, should it be the viewmodel itself which implements the service interface?
  3. Is it appropriate for the service interface to provide events (e.g. besides providing a method to change the map scale, provide an event that the map scale was changed as well)? Or is it preferable to employ some kind of event broadcaster (aggregator) mechanism to push such notifications out of service interfaces?

Thanks in advance for your help.

petr k.
  • 8,040
  • 7
  • 41
  • 52

4 Answers4

3

Consider using the Messenger in the MVVM Light toolkit. See more in another SO answer:

https://stackoverflow.com/a/2700324/117625

Community
  • 1
  • 1
Ed Chapel
  • 6,842
  • 3
  • 30
  • 44
2

EventAggregator is the another mechanism which establish communication between disconnected view models. I believe all the other parts of your application will be using the same MVVm and will have viewmodel to do the operations. Publish event say Zoom with required arguments from other parts of the app and catch it in the map using the subscribe mechanism.

http://msdn.microsoft.com/en-us/magazine/dd943055.aspx#id0420209

Prism has a good implementation of Event Aggregator. you can use that part.

Joy George Kunjikkuru
  • 1,495
  • 13
  • 27
1

What if you had an aggregate Command object which specified the appropriate behaviour? I'm going to try to flesh your question out a little bit to specifics, correct me if I'm wrong:

Let's suppose that there are two relevant parts of your app - a map component, which can be zoomed and panned etc, and a set of controls, which present the user interface for zooming, panning and selecting between them - sort of a set of mode selectors. You don't want either of them to have a direct reference to the other, and the temptation is to have the map know directly about its set of controls, so that it can catch events from them and switch mode state appropriately.

One way to take care of this would be to have a set of CompositeCommands (available from the Prism Application Guidance) libraries inside an object injected into each of them. That way you get decoupling and a strong description of interface (you could also use events if you were that way inclined).

public class MapNavigationCommands{
  public static CompositeCommand startPanning = new CompositeCommand();
  public static CompositeCommand startZooming = new CompositeCommand();
  public static CompositeCommand setViewbox = new CompositeCommand();
}

Your mode controls, up in the Ribbon, register with your DI framework to have that injected (not wanting to introduce DI into this example, I've just referenced these static members directly).

public class ModeControls : UserControl{
  ...
  public void PanButtonSelected(object sender, RoutedEventArgs e){
    MapNavigationCommands.StartPanning.Execute(this); //It doesn't really care who sent it, it's just good event practice to specify the event/command source.
  }
}

Alternatively, in XAML:

...
  <Button Command={x:Static yourXmlns:MapNavigationCommands.StartPanning}>Start</Button>
...

Now, over on the map side:

public class PannableMapViewModel{
  public PannableMapViewModel(){
    MapNavigationCommands.StartPanning.RegisterCommand(new DelegateCommand<object>(StartPanning));
    MapNavigationCommands.SetViewbox.RegisterCommand(new DelegateCommand<Rectangle>(SetViewBox));

  }
  private void StartPanning(object sender){
    this.SetMode(Mode.Pan); //Or as appropriate to your application.  The View is bound to this mode state
  }
  private void SetViewbox(Rectangle newView){
    //Apply appropriate transforms.  The View is bound to your transform state.
  }
}

Now you have a decoupled, strongly specified interface between two controls, maintaining ViewModel separation, which can be mocked out for your tests.

Chris Hagan
  • 2,437
  • 3
  • 17
  • 16
  • Interesting approach. After studying Prism commands and composite commands, it never occurred to me they could be used in this way. Do you know of some open source projects (or articles) which make use of this approach? – petr k. Dec 18 '11 at 22:03
  • Hrm. Sorry, I don't think I do. It's the way we did it when we were incrementally adopting MVVM and Prism - just bringing in the commanding without any of the rest of the infrastructure (for the record, the rest of the infrastructure was totally worth it, when we got around to it, and next time I wouldn't incrementally adopt the same way, I'd just come across wholesale. Prism is very nice). – Chris Hagan Dec 22 '11 at 11:03
1

Whenever you need two view models to communicate (or something similar, such as a button that wants to invoke a command on a view model other than its own), the best practice I've found is use a messaging channel. (MVVM Light has the Messenger class; Prism and Caliburn.Micro both have an EventAggregator.) When the target of the command is instantiated (your map view model), it will register for specific commands on a messaging channel. When an invoker (such as a button) is instantiated, it can then send commands over that same channel. This keeps your components loosely coupled. Commands from the messaging channel can be easily mocked for unit testing. It also opens up other avenues to you, such as having multiple maps open at the same time (simply use a different messaging channel or some sort of token).

I'd skip the whole service interface in your case. When using an event aggregator, it doesn't really add much. Depending on the size and complexity of your code base, you might want to keep it around so it describes the commands available for a map, but that only makes sense if you have more than a handful of commands. In that case the service would register as the end point for commands on the messaging channel and would then have to forward those commands on to a map view model. (See? Doesn't add much and only seems to complicate things.)

Skip the events. They don't seem to add anything.

Mike Post
  • 6,355
  • 3
  • 38
  • 48
  • Thanks you very much. My issue with the event aggregator pattern is that it hides the interface (in the most general sense) a component exposes. Also, while I am okay with using the event aggregator for sending notifications to which other components may or may not react, I am for some reason not comfortable with the other scenario - when a component sends a message to other component which _is supposed_ to react. – petr k. Dec 18 '11 at 22:00
  • In other words, this patterns feels right for event-like behavior, not so much for cases when one component takes control over actions of some other component. – petr k. Dec 18 '11 at 22:01
  • It seems to me that it may be the naming which pushes me into certain way of thinking - while EventAggregator in Prism makes me think the way I just described, Messenger sounds much more like a general-purpose mechanism and more along the line of your suggestion, while they both do essentialy the same. – petr k. Dec 18 '11 at 22:06
  • I'm sure different library authors will tell you there are differences between EventAggregator and Messenger. However, there's no reason you can't treat them as the same thing. In practice, I name all the "events" I send through Prism's EventAggregator as commands (example: DoSomethingCommand). – Mike Post Dec 19 '11 at 01:57