tags:

views:

80

answers:

3

I am trying to implement a simple WPF data analysis application using the MVVM design pattern, in which one can use several different methods to analyse some data (loaded from files).

In the first screen, the user should be able to choose the method he likes to employ. After he has done that, and loaded the data, the area previously occupied by the method selection screen should be replaced by the analysis results.

Currently, my MainWindowViewModel has a property "CurrentViewModel" of type object, that can be set to either the method selection viewmodel or on of the analysis results viewmodels, which are then rendered by using datatemplates.

The problem I am facing is, that I don't know how the different viewmodels should communicate.

  • The method selection screen needs a list of available methods.
  • The main screen needs to know what method was selected and choose the approriate viewmodel to display the results.
  • The data loaded somehow needs to get into the class doing the actual work, and the results viewmodel needs to know about this to know where to get its data from.

Everything I can think of leaves the MainWindowViewModel to do all the negotiating between the different viewmodel and model classes.

How would I optimally design this?

A: 

Could the data source of the selection be bound to the types of ViewModels you have and the path possibly be the name of each (be it a string property for a formatted name or the Type name explicitly). On Selection you then have the relevant object that has been selected.

Possibly each of the ViewModels references a common provider which as you say performs the results analysis and the two different Views simply display the same data in different ways.

REA_ANDREW
+2  A: 

For what it's worth, I helped implement a module for a similar Composite Application. So what follows is based on anecdotal experience,

  • To your first point, our application or "shell" needs an explicit enumeration of "available" methods. We can supply this any way we wish, either explicitly (via config) or implicitly (via reflection). As a preference, I favor the former as it is less "magical".

  • To your second point, in addition to an explicit enumeration our shell must maintain a map from choice to implementation. Again, this may be accomplished any number of ways, but typically our enumeration is a list of "Types", and when a type is selected we request an implementation of that type from a factory. The easiest way to implement this pattern is to leverage an Inversion of Control container, such as Castle Windsor, Unity, Ninject, etc. To be honest, I don't remember what we used internally.

For example, consider,

// a simple Plain Old C Object that describes business methods.
public class BusinessMethod
{
    // user-friendly name
    public string Name { get; set; }
    // type that actually implements
    public Type ImplementationType { get; set; }
}

// ... meanwhile, back on the ranch ...
public void OnBusinessMethodSelection ()
{
    // 1. if selected 
    if (BusinessMethodList.SelectedItem != null)
    {

        // 2. retrieve selected item
        BusinessMethod selected = 
            (BusinessMethod)(BusinessMethodList.SelectedItem);

        // 3. request implementation of selected item from
        // IoC container
        object implementation = 
            _container.Resolve (selected.ImplementationType);
    }
}
  • To your third point, we need a way for disparate parts to communicate. Unfortunately we cannot rely on design-time methods (ie Command and Data bindings), so we must implement our own Event Aggregation service. Basically a "singleton" (as in single instance not static class) that knows about subscribers and publishers, and preferably an implementation that offers strong typing of arguments. Fortunately for us, many a greater man have gone before us, and we may benefit from their experience. Check out Kent Boogaart's Event Hub.

Here is an example of how we would use an Event Aggregator

// an example of a strongly typed subject. notice how subject
// defines content. semanticly, when someone creates and publishes
// an instance of this subject, they are requesting someone show
// an analysis view based on data content,
public class AnalysisSubject
{
    // subject content, in this case a data result from
    // a business method
    public object Data { get; set; }
}

public class MainWindow : ISubscriber<AnalysisSubject> ...
{

    // use whatever implementation of an IoC container we like
    // here i assume we abstract from implementation and use a
    // custom interface IContainer that exposes functionality 
    // that we need
    private readonly IContainer _container = null;
    public class MainWindow ()
    {
        // we're teh r00tz! we create an instance of IoC
        // container for use throughout application
        IContainer _container = new CustomContainer ();

        // our container exposes both parameterized and
        // type-parameterized resolve methods
        IEventHub events = _container.Resolve<IEventHub> ();
        events.Subscribe<AnalysisSubject> (this);
    }

    #region ISubscriber<AnalysisSubject>

    // part of strongly typed subscriptions is that we
    // may now handle strongly typed publications! yay!
    public void Receive (AnalysisSubject subject)
    {
        // 1. request to display analysis of data
        Type analysisType = subject.Data.GetType ();

        // 2. get view control based on payload type
        // 
        // NOTE: implicit usage below is not consistent
        // with previous invocations, here we are submitting
        // a type of something we already have, and actually
        // want back something that knows how to handle it.
        // most IoC containers can provide this functionality
        // through "facilities" add ons that accept a 
        // parameter\discriminator like below, and produce 
        // something in return.
        Control control = (Control)(_container.Resolve (analysisType));

        // [alternatively] if the above is too "magical" where
        // IAnalysisFactory is an interface we define for this
        // express purpose
        //IAnalysisFactory factory = _container.Resolve<IAnalysisFactory> ();
        //Control control = factory.GetAnalysisControlFor (analysisType);

        // 3. assign subject data to control
        Control.DataContext = subject.Data;

        // 4. display control
    }

    #endregion

}

And an example of publication

public class SomeBusinessView
{

    private readonly IEventHub _events = null;

    // we cannot function without an event aggregator of
    // some kind, so we declare our dependency as a contructor
    // dependency
    public SomeBusinessView (IEventHub events)
    {
        _events = events;
    }

    public void DoMyThang ()
    {
        // 1. do some business
        MyBusinessData data = SomeBusinessFunction ();
        // 2. publish complete event
        AnalysisSubject subject = new AnalysisSubject () { Data = data, };
        _events.Publish (subject);
    }

}
johnny g
Thanks much! This helps a lot!
Jens
+1  A: 

Every View typically gets a viewmodel. If you are dealing with nested usercontrols inside one window, using multiple viewmodels (one per control) may be overkill.

If you have a viewmodel per control and are disconnected regarding communication between them then you can either have a common model that is universal to all the viewmodels OR have a global event provider that allows models to talk to eachother. (Something they all can reference for change notifications etc).

If you don't use a viewmodel per control then have the viewmodel bound to the main window in which all nested controls report to the main window which reports to the viewmodel for the main window.

Jeff Gwaltney