Click here to Skip to main content
Click here to Skip to main content

MVVM for Multi Platforms

, 22 Mar 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
How to implement MVVM when developing a view model whose view implementation language is not certain

Introduction

Working with WPF for a while now, I've become a big fan of MVVM, the Model-View-ViewModel design pattern, which means the data is kept in the Model, the GUI is managed by the View and the ViewModel functions as a mediator between the Model and the View. The View uses the ViewModel as its DataContext. I've made it a mission for myself to build views which use no code in the XAML .cs files. This technique simplifies any changes which needs to be done by the View. However, the new task I was facing was a bit different. I've been told that the feature I was about to develop is planned to have UI which might be web based. My ViewModel couldn't have carried any objects which were Window specific, such as ICommand. Also the new UI was to be developed by a third party outside my company. That third party may want to use stubs to test their development. These requirements demanded new planning on my behalf.

Background

The first thing I did was making interfaces of all my future to be View models. The View was to know the interfaces without knowing the exact implementation. This makes the usage of stubs much easier. You can find the interfaces in the "Interfaces" project. The second thing was creating a class called InterfacesFactory which is the only class from the view model which the view is aware of. This class creates the Required View models. Dependency injection can come in handy as well.

public IEditableCityViewModel GetEditableCityViewModelForAdd(Guid countryId)
{
    return new EditableCityViewModel(new City(),countryId);
}

public IEditableCityViewModel GetEditableCityViewModelForEdit(Guid cityId)
{
    City city = DataManager.Instance.GetCity(cityId);
    return new EditableCityViewModel(city);
}

Having the view working with interfaces and not concrete classes posed a problem when using DataTemplate, which needs concrete types. I solved this problem by building my own DataTemplateSelector:

public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
    DataTemplate dTemplate = null;
    if (item != null)
    {
        Type[] types = item.GetType().GetInterfaces();
        if (types.Contains(typeof(IReadOnlyContinentViewModel)))
        {
            dTemplate = ContinentDataTemplate;
        }
        else if (types.Contains(typeof(IReadonlyCountryViewModel)))
        {
            dTemplate = CountryDataTemplate;
        }
        else if (types.Contains(typeof(IReadOnlyCityViewModel)))
        {
            dTemplate = CityDataTemplate;
        }
    }

    return dTemplate;
}
<local:ReadOnlyObjectsDataTemplateSelector
            x:Key="selector"
            ContinentDataTemplate="{StaticResource readOnlyContinentDataItem}"
            CountryDataTemplate="{StaticResource readOnlyCountryDataItem}"
            CityDataTemplate="{StaticResource readOnlyCityDataTemplate}"
/> 

Instead of ObservableCollection, I used IList. Instead on INotifyPropertyChanged, I used events which could be registered. On this scenario, the View registers itself to events and is responsible to update itself. It is important to remember that the data can only be updated from the Main thread.

/// <summary>
/// Example for data refresh
/// </summary>
private void GetCurrentTime(object state)
{
    System.Windows.Application.Current.Dispatcher.BeginInvoke
		(new SimpleOperationDelegate(this.CurrentTimeUpdated));
}

private void CurrentTimeUpdated()
{
    BindingExpression bindingExpression =
        BindingOperations.GetBindingExpression(txtCurrentTime, TextBlock.TextProperty);
    bindingExpression.UpdateTarget();
}

Instead of ICommand, I gave API calls on the View model. These APIs are called by asynchronous calls, due to the face that they might take time to commit.

private void onSave(object sender, ExecutedRoutedEventArgs args)
{
    this.Cursor = Cursors.Wait;
    SimpleOperationDelegate delg = new SimpleOperationDelegate(this.SaveOperation);
    delg.BeginInvoke(this.OperationCompleted, delg);
}

private void SaveOperation()
{
    m_DataContext.Save();
}

/// <summary>
/// The operation call back. Call the dispatcher to update the view from the main thread
/// </summary>
/// <param name="result"></param>
private void OperationCompleted(IAsyncResult result)
{
    System.Windows.Application.Current.Dispatcher.BeginInvoke
    	(new AsyncCallback(this.EndOperation), result);
}

/// <summary>
/// End the asynchronous call
/// </summary>
/// <param name="result"></param>
private void EndOperation(IAsyncResult result)
{
    this.Cursor = Cursors.Arrow;

    SimpleOperationDelegate delg = result.AsyncState as SimpleOperationDelegate;
    delg.EndInvoke(result);
    this.DialogResult = true;
    this.Close();            
}

Using the Code

The example solution is a TreeView presentation of Continents, countries and cities. There is an ability to add, edit or remove each one of them. The Model and the View are kept in the Lib project. The View is kept in the UILIB project. There is the Interfaces project, which holds all the interfaces the View works with and a TestsLib project which I used for testing my code. The data is kept in an XML file within the Lib project. The solution can easily be run after compiling it.

Points of Interest

My intention was that the View model would not be changed when the time comes. I hope it would be so.

History

  • 22nd March, 2010: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Izhar Lotem
Software Developer
Israel Israel
Software Developer in a promising Clean-Tech company

Comments and Discussions

 
GeneralMVVM database sample. Pinmemberguyet0526-Mar-10 13:05 
GeneralMy vote of 2 PinmvpDave Kreskowiak22-Mar-10 2:47 
QuestionRe: My vote of 2 PinmemberIzhar Lotem22-Mar-10 3:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 22 Mar 2010
Article Copyright 2010 by Izhar Lotem
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid