Skip Navigation LinksHome / Articles / View Article

ModalDialogs, IEditableObject and MVVM in Silverlight 4

+ Add to SilverlightShow Favorites
25 comments   /   posted by Pencho Popadiyn on May 11, 2010
(20 votes)
Categories: Tutorials , Resources , Samples

1. Introduction

I recently found myself in the following situation. My boss came to me and asked me: “Dude, we have a big Silverlight project, performing a great number of CRUD operations. We need to find a unified way of showing Modal Dialogs in a MVVM manner, without using any code in the View. The logic of showing dialogs must reside in the ViewModel, not in the code-behind. Also do you remember our previous WinForms projects? There are several pretty useful classes, such as BindingSource and DataRowView, implementing BeginEdit, EndEdit and CancelEdit functionality out of the box. We need to bring this functionality in our Silverlight projects, however, this time on business objects level. It must be ready till tonight, otherwise you won’t get salary at the end of the month!!!

Knowing my boss, the last sentence must have been a joke, or at least I hope to be a joke :) After working a few hours on the problem, I found a solution, which makes me feel pretty happy. That’s why I decided to share it with you guys.

2. Modal Dialogs and MVVM

So here is the general idea. First, we need to find a way to show modal dialogs. Fortunately, Silverlight 3 and Silverlight 4 provide us with ChildWindow. Of course, with the same success you can use dialogs (windows) provided by any third-party component vendor. However, the most important question is how to use a Modal Dialog inside the ViewModel and in the same time to remain decoupled. One of the most important OOP principles – The Dependency Inversion Principle teaches us to depend on abstractions not on concrete implementations. When we have static dependencies in our code, we can break them by using either an interface or a base class. Additionally, according to the MVVM pattern, the ViewModel doesn’t have to know about the View. Definitely we need an interface. Here it is:

So we need a DialogResult to determine whether the OK or Cancel button is clicked. We need to have the possibility to Close and Show the dialog; to be notified when the dialog is closed, in order to perform the subsequent actions. And finally, let’s take a look at the Content property. It is a little bit more special. Why? The answer is that there are two possible solutions of the current problem. In the first one, we can exchange the Content property with DataContext property. Or in other words, we need to create a new modal dialog for each entity. After that we just set the target entity to the dialog’s DataContext, like on the image below.

The second solution requires some additional work. Instead of having a separate child window for each entity, you could use UserControls. After that, you just set the UserControl to the dialog’s Content property.

Personally I prefer the second one, and namely it is demonstrated in this demo. However, the first one is also “playable”. Here is the IModalView interface. It is a quite simple, and provides a way to set the DataContext property of the UserControl, as well as to be notified when OK or Cancel button is pressed.

Finally, we need to bring in another level of abstraction on the scene. The final piece is the glue between the ModalDialog, the ModalView, and the ViewModel. This is the IModalDialogWorker interface. It provides only one method for showing dialogs and completely hiding the details of the implementation.

As you can see, when you show modal dialogs, you have to pass implementations of IModalDialog and IModalView interfaces, a dataContext (this is the currently edited entity – e.g. Person, Customer, Employee, etc). Finally, you need to pass an action that is invoked when the dialog is closed. The last parameter is optional (e.g. you can use that method to show custom MessageBox dialogs). Via the IModalDialog’s DialogResult property, you can obtain the dialog result.

The ModalDialogWorker implementation is quite simple. It is just a matter of coding, here it is:

public class ModalDialogWorker : IModalDialogWorker
{
    public void ShowDialog<T>( IModalDialog modalDialog, IModalView modalView, T dataContext, Action<T> onClosed )
    {
        if ( modalDialog == null )
            throw new ArgumentNullException( "modalDialog", "Cannot be null" );
        if ( modalView == null )
            throw new ArgumentNullException( "modalView", "Cannot be null" );
 
        EventHandler onDialogClosedHandler = null;
        EventHandler<ModalViewEventArgs> onViewClosedHandler = null;
 
        if ( onClosed != null )
        {
            onDialogClosedHandler = ( s, a ) =>
            {
                modalDialog.Closed -= onDialogClosedHandler;
                onClosed( dataContext );
            };
 
            onViewClosedHandler = ( s, a ) =>
            {
                modalDialog.Closed -= onDialogClosedHandler;
                modalView.Closed -= onViewClosedHandler;
                modalDialog.DialogResult = a.DialogResult;
                //modalDialog.Close();
                onClosed( dataContext );
            };
 
            modalDialog.Closed += onDialogClosedHandler;
            modalView.Closed += onViewClosedHandler;
        }
 
        modalDialog.Content = modalView;
        modalView.DataContext = dataContext;
        modalDialog.ShowDialog();
    }
}

Now let’s see how to create IModalDialog and IModalView implementations. The ModalDialog is very simple. You just have to derive from ChildWindow and implement the IModalDialog interface.

public class ExtendedChildWindow : ChildWindow, IModalDialog
{
    public void ShowDialog()
    {
        this.Show();
    }
}

Note that the ExtendedChildWindow dialog is universal, you just have to set its content property with the concrete XXXModalView. For example, if you want to create a control for editing Person objects, you need to create a new UserControl and implement IModalView interface.

public partial class EditPersonControl : UserControl, IModalView
{
    public EditPersonControl()
    {
        InitializeComponent();
    }
 
    public event EventHandler<ModalViewEventArgs> Closed;
 
    protected virtual void OnClosed( ModalViewEventArgs e )
    {
        if ( this.Closed != null )
            this.Closed( this, e );
    }
 
    private void btnOkClick( object sender, RoutedEventArgs e )
    {
        this.OnClosed( new ModalViewEventArgs( true ) );
    }
 
    private void btnCancelClick( object sender, RoutedEventArgs e )
    {
        this.OnClosed( new ModalViewEventArgs( false ) );
    }
}

The only things you should do in the code-behind is to mark the control as IModalView implementation; to set the DialogResult to true, if user clicked OK, and to false if user clicked Cancel. Note that this is done in the code-behind because it is not part of the business logic, but of the UI logic.

The key moments in the ModalDialogWorker are these three lines of code:

modalDialog.Content = modalView;
modalView.DataContext = dataContext;
modalDialog.ShowDialog();

3. “M” for Mighty

The question remains how to inject the IModalDialog, IModalVIew and IModalDialogWorker implementations into the ViewModel.

Fortunately, we have MEF in our tool belt. You need to mark all composable parts with the Export attribute and to specify contracts.

On the other side of the channel, the ViewModel, you have to specify the Imports. After that, using the ModalDialogWorker is just a piece of cake.

private void OnEditPersonCommandExecute()
{
    this.ModalDialogWorker.ShowDialog<Person>(
        this.ModalDialog, this.EditPersonControl, this.SelectedPerson, p =>
        {
            if ( this.ModalDialog.DialogResult.HasValue &&
                this.ModalDialog.DialogResult.Value )
            {
                // OK
            }
            else
            {
                // Cancel
            }
        } );
}

Of course you could use any other discovering patterns.

4. Generic Implementation of IEditableObject

The next important bunch of questions is: What happens when the user edits objects? What should happen when he/she presses the OK or Cancel button? How to commit the changes? How to restore the original state of the object?

We need to commit or rollback changes through the BeginEdit, EndEdit and CancelEdit methods. That is easy. We can use IEditableObject interface. The most important problem is how to keep a snapshot of the object’s state. To address this bullet, we can use the Memento pattern. The function of the Memento pattern is to capture and externalize an object’s internal state so that the object can be restored to this state later, without violating encapsulation.

Well having this in mind, let’s create a new generic class named Memento<T>.

public class Memento<T>
{
    private Dictionary<PropertyInfo, object> storedProperties = new Dictionary<PropertyInfo, object>();
 
    public Memento( T originator )
    {
        this.InitializeMemento( originator );
    }
 
    public T Originator
    {
        get;
        protected set;
    }
 
    public void Restore( T originator )
    {
        foreach ( var pair in this.storedProperties )
        {
            pair.Key.SetValue( originator, pair.Value, null );
        }
    }
 
    private void InitializeMemento( T originator )
    {
        if ( originator == null )
            throw new ArgumentNullException( "Originator", "Originator cannot be null" );
 
        this.Originator = originator;
        IEnumerable<PropertyInfo> propertyInfos = typeof( T ).GetProperties( BindingFlags.Public | BindingFlags.Instance )
                                        .Where( p => p.CanRead && p.CanWrite );
 
        foreach ( PropertyInfo property in propertyInfos )
            this.storedProperties[ property ] = property.GetValue( originator, null );
    }
}

The code is quite simple. First you are passing the originator (this is the object we want to track). In the InitializeMemento method, you are taking the internal state. All properties and their values are stored in a simple Dictionary<string,object>.

this.Originator = originator;
IEnumerable<PropertyInfo> propertyInfos = typeof( T ).GetProperties( 
                                BindingFlags.Public | BindingFlags.Instance )
                                .Where( p => p.CanRead && p.CanWrite );
 
foreach ( PropertyInfo property in propertyInfos )
    this.storedProperties[ property ] = property.GetValue( originator, null );

Finally, in the RestoreState method, we are taking the original values and restoring the object’s initial state.

public void Restore( T originator )
{
    foreach ( var pair in this.storedProperties )
    {
        pair.Key.SetValue( originator, pair.Value, null );
    }
}

The last piece of the puzzle is the Caretaker. The Caretaker is a simple wrapper. It implements the IEditableObject interface and has a reference to the generic Memento<T>. This is the place where the magic happens. However, the code is even simpler than the code for Memento<T>.

public class Caretaker<T> : IEditableObject
{
    private Memento<T> memento;
    private T target;
 
    public T Target
    {
        get
        {
            return this.target;
        }
        protected set
        {
            if ( value == null )
            {
                throw new ArgumentNullException( "Target", "Target cannot be null" );
            }
 
            if ( Object.ReferenceEquals( this.Target, value ) )
                return;
 
            this.target = value;
        }
    }
 
    public Caretaker( T target )
    {
        this.Target = target;
    }
 
    public void BeginEdit()
    {
        if ( this.memento == null )
            this.memento = new Memento<T>( this.Target );
    }
 
    public void CancelEdit()
    {
        if ( this.memento == null )
            throw new ArgumentNullException( "Memento", "BeginEdit() is not invoked" );
 
        this.memento.Restore( Target );
        this.memento = null;
    }
 
    public void EndEdit()
    {
        if ( this.memento == null )
            throw new ArgumentNullException( "Memento", "BeginEdit() is not invoked" );
 
        this.memento = null;
    }
}

Now, you are free to use the Caretaker in the following manner:

Caretaker<Person> editableObject = new Caretaker<Person>( this.SelectedPerson );
editableObject.BeginEdit();
//.....
this.SelectedPerson.Name = "Pesho";
// Commit Changes
editableObject.EndEdit();
// -or CancelChanges
// editableObject.CancelEdit();

Let’s go back to our PersonViewModel, and update the OnEditPersonCommandExecute method:

private void OnEditPersonCommandExecute()
{
    Caretaker<Person> editableObject = new Caretaker<Person>( this.SelectedPerson );
    editableObject.BeginEdit();
 
    this.ModalDialogWorker.ShowDialog<Person>(
        this.ModalDialog, this.EditPersonControl, this.SelectedPerson, p =>
        {
            if ( this.ModalDialog.DialogResult.HasValue &&
                this.ModalDialog.DialogResult.Value )
            {
                editableObject.EndEdit();
            }
            else
            {
                editableObject.CancelEdit();
            }
        } );
}

5. Final Words

You can download the source code from here.

The other goodies: Note that the whole business logic is located in the ViewModel. No actions in the code-behind. Also pay attention to the fact that at each point the ViewModel deals with interfaces, no concrete implementations. This is important, because it is not recommended to have a reference in the ViewModel to ChildWindow (for example). I am a big fan of MEF, that’s why I am using it wherever this is possible. However, marking the concrete implementations with the Export attribute doesn’t obligate you to use MEF for parts discovering. You are free to use any other pattern (approach). Finally, using DelegateCommand – this is standard, no need of explanation.

And the online demo. Select a person from the datagrid. Press the Edit button, a new modal dialog appears. This is happening thanks to the three interfaces presented in the article. Make some changes to the current Person object. Click either the OK or Cancel button. The changes are committed, or respectively rejected. This is happening thanks to the generic implementations of the IEditableObject and the Memento pattern.

So, that’s it. I know the problem for performing CRUD operations in Silverlight/WPF is not so simple and I hope that the code here will be useful for you. Feel free to experiment with it. If you have any idea for further improvements or questions, I will be happy if you share it with us.

Share


Comments

Comments RSS RSS
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by WPFDeveloper on May 11, 2010 14:04
    Very nice article.  Great work.
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by s. singh on May 11, 2010 18:46
    Very nice. I've just started creating a bunch of SL4 ModalDialogs for a project..your article was timely.
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by Balaji on May 12, 2010 07:57
    Nice Article !!!!!
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by simple on May 12, 2010 17:08
    This is way too much code.  This is really over complicating things.  Keep it simple.
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by ppopadiyn on May 12, 2010 17:16

    Hi,

    If you mean the modal dialogs, i agree with you that using additional user controls makes the program more complex. Maybe using directly ChildWindows is better approach ;). Thanks for advice

  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by mike kidder on May 18, 2010 03:23
    Pencho, thanks for sharing this elegant solution, source code, and well written article....
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by Andre Baltieri on May 21, 2010 05:09

    Amazing solution. A little complex to me yet, but will continue my studies on MVVM and MEF with SL.
    This will realy help me a lot!

    Thanks man!

  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by aitran on May 21, 2010 10:22
    Im not using MEF for the whole solution, and I try to use IModalDialog, then it throws an exception of null in the ViewModel ModalDialogWorker
    Please help,
    Thanks.
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by ppopadiyn on May 21, 2010 10:27
    Hi aitran, probably, you need pass somehow to the ViewModel instances of the implementations (for example you can create a new constructor for the view model and pass instances of the ModalDialogWorker, ModalDialog, ModalView, etc). If you don't use MEF, then this is the nature of .Net, it will not create instances of these classes and when you try to use them NullReferenceException will be thrown. Let me know if this works for you.
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by Denis Vuyka on Jun 02, 2010 18:39

    This is a great solution and your samples help me a lot. However I've made some corrections to code:

    1. Some value comparison will work differently for reference and value types. So I've restricted Memento and Proxy with a constraint: "where T : class"

    2. Memento won't work properly for the class containing properties with non-public setters (at least for Silverlight 3). I've updated the Memento.InitializeMemento with additional check for public setter presence:

    foreach (var property in propertyInfos)
          {
            if (property.GetSetMethod() == null) continue;
            _storedProperties[property] = property.GetValue(originator, null);
          }

    Regards,

    Denis

  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by ppopadiyn on Jun 02, 2010 18:43
    Hi Denis, I am very happy you find the samples useful and thanks a lot for the improvements ;)
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by Alex on Jun 05, 2010 14:52

    Very good article. Nice and clean solution.

    I have one question, what to do with collections (collection of objects|)?

  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by Thanigainatahn on Jun 05, 2010 21:02

    Hi There,

    Very nice article.Good oppotunity to learn the Patterns.

    Thanks,

    Thani

  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by Calabonga on Jul 19, 2010 03:49

    Very good article! Thanks for your shared code.

    But what about MVVM + RIA Services modal dialog and IEditableObject?

  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by ppopadiyn on Jul 21, 2010 10:53

    Hello Calabonga,

    Fortunately, in WCF RIA all exposed business objects implements IEditableObject automatically. All changes are tracked in an EntityChangeSet object, and the changes are submitted collectively when you call SubmitChanges method of your domain context (respectively rejected, when you call the RejectChanges method). So you could use the same approach with the modal dialogs and mvvm. The only difference is that you should work directly with your domain context.

  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by Richard on Jul 21, 2010 11:25

    Hi

    I've converted your code to vb.net but am getting issues. I'm not an expert on OOP/classes...this is 2 examples errors (about 19 similiar)

    Class 'PersonViewModel' must implement 'Sub OnImportsSatisfied()' for interface 'System.ComponentModel.Composition.IPartImportsSatisfiedNotification'.  ..\...\Demo\ViewModels\PersonViewModel.vb

    Class 'ExtendedChildWindow' must implement 'Sub Close()' for interface 'Framework.IModalDialog'. Views\ExtendedChildWindow.vb


    My understanding is that as the ExtendedChildWindow implements Framework.IModalDialog
     it should have a Close() method - the C# version does not - likewise the OnImportsSatisfied sub exists in PersonViewModel
     but does not have the keyword 'Implements IPartImportsSatisfiedNotification' attached to it (C# version has this missing as well but no compilation error). If I add Implements IPartImportsSatisfiedNotification to the OnImportsSatisfied
     sub I still get compilation error - I am now confused.  Anyone one of you guru's shed some light/advice on this (obviouslt some difference in conversion from C# to Vb.net??)


    Great article.

    Richard





  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by ppopadiyn on Jul 21, 2010 11:37

    Hi Richard,

    Sorry but I am not an expert in VB.NET. As regards your question about the ExtendedChildWindow and IModalDialog. The ExtendedChildWindow class derives from ChildWindow and implements the IModalDialog interface. "it should have a Close() method - the C# version does not" - you are absolutely right there must be a Close method in the ExtendedChildWindow dialog, but it doesn't exists. However the base ChildWindow class has a Close method, or in other words the Close method is implemented in the base ChildWindow class, and the compiler obviously is pretty smart :) to recognize this. Just for a test you could rename the "Close" method in the C# code, for example to "Close2" and see the result, now you will have a compiler error.

  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by Richard on Jul 21, 2010 13:15

    ppopadyyn

    Thanks for the prompt reply, okay I will look into further and hopefully resolve.

    If anyone else has converted this code to Vb.net  please get back to me.

    Thanks




  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by dbis on Aug 02, 2010 00:21
    ppopadyyn, could you please clarify how you close modal dialog? Snippet
    modalDialog.Close() is commented out
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by ppopadiyn on Aug 02, 2010 08:01

    Hi dbis

    Setting the DialogResult property of the ChildWindow, automatically close it (that is the behavior of the ChildWindow). That happens in the ModalDialogWorker, line 40.

  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by dbis on Aug 02, 2010 20:26
    Thanks!
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by Calabonga on Aug 04, 2010 03:20
    Thank for your comment! That's what I realy need.
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by Brian on Aug 04, 2010 16:39

    There is a bug in your sample.


    1. Select a Person.
    2. Click Edit.
    3. Modify the Name, and leave the focus in the Name text box.
    4. Click the X in the upper right of the dialog to close it.
    5. The Name was updated when it shouldn't have been.


  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by stephan on Aug 26, 2010 15:17
    well done
  • RE: ModalDialogs, IEditableObject and MVVM in Silverlight 4  

    posted by jay on Sep 06, 2010 21:18

    Great post!  Your demo worked great. 

    I tried to use sections of it in my application. I keep getting the error message saying [Type of ......ModalDialog is not CLS_Compliant]. Any ideas ?


     [Import]

            public IModalDialog ModalDialog

            {

                get;

                set;

            }




Add Comment

 
 

   
  
  
   
Please add 4 and 1 and type the answer here:

Join the free SilverlightShow webcast 'Running Silverlight Outside the Browser and with Elevated Trust'. Sept 7th, 8 am - 9 am PDT.
In this live session Chris Anderson will cover configuring and debugging OOB mode, toast notifications, elevated trust, direct file access and much more.
Learn more | Register | See more webinars (hide this)