Tuesday, October 4, 2011

Dialogs in MVVM (Silverlight)

Several days ago I was facing a problem, when was trying to open/show a dialog from in my MVVM application.
The actual problem is that I want that dialog also to be bound to a ViewModel instance - controlling it.
There are many approaches to handle this. I will present one of them - which I am satisfied with.
So, let's start. What I want is to have a DialogViewModel instance in my main ViewModel object, which is the DataContext of the current page, and which from from I'm going to open the dialog.
When thinking of MVVM itself you think that is much be something like setting a property and a dialog must show up - THAT IS CORRECT ! And that is what I'm going to achieve.
So just to give a small example of code, how the structure should be:

public class PageViewModel
{
    private DialogViewModel dialogVM = new DialogViewModel();

    public DialogViewModel DialogVM
    {
        get
        {
            return this.dialogVM;
        }
    }
}

public class DialogViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private bool show = false;

    private void NotifyPropertyChange(string argPropertyName)
    {
        PropertyChangedEventHandler tmpEH = this.PropertyChanged;
        if(tmpEH != null)
            tmpEH(this, new PropertyChangeEventArgs(argPropertyName));
    }

    // Here comes a lot of logic like dialog commands, ... which I will not include in this article

    public bool Show
    {
        get
        {
            return this.show;
        }
        set
        {
            if(value != this.show)
            {
                this.show = value;
                this.NotifyPropertyChange("Show");
            }
        }
    }
}

This is quiete enough for the ViewModel layer. So this is really what I've described enough. You will now ask - how should this boolean change (althought it supposed to be such thing) affect the dialog to appear - keep reading.
The simple approach (which won't work with ChildWindow derieved dialogs) is to add a dialog definition into the XAML and bind its Visibility property to the DialogVM.Show property, using a BoolToVisibilityConverter (am sure you'll be able to write something like this yourself). But when you'll try to do this, the dialog will behabe strange - it will really be not what you wanted - and you'll notice a lot of issues with it. Maybe in further versions of Silverlight this way of showing dialogs also will be possible - but not yet.
So, the second approach, I'll keep with, is to create a helper class, derieved from Control class. This will be the actual bridge between the DialogVM and the dialog we're going to show.
Here what the helper control will look like:

public class DialogHelper : Control
{
    //...
}

But an empty class is not going to be any helpfull for us. And here the core of the solution is: we're going to add a DependencyProperty to this control - to enable it to be bound to anything, bind it with the DialogVM.Show property and finally handle the change of that dependency property.

public class DialogHelper : Control
{
    private static readonly DependencyProperty ShowProperty = DependencyProperty.Register("Show", typeof(bool), typeof(DialogHelper), new PropertyMetadata(false, new PropertyChangedCallback(OnShowChanged)));

    private static void OnShowChanged(DependencyObject argSender, DependencyPropertyChangedEventArgs argEA)
    {
        ConfirmationDialogHelper tmpSender = argSender as ConfirmationDialogHelper;
        if(tmpSender.Show)
        {
            DialogToShow tmpDialog = new DialogToShow();
            //The following line makes sure that the dialog also will work based on MVVM
            tmpDialog.DataContext = (this.DataContext as PageViewModel).DialogVM;
            tmpDialog.Show();
        }
    }
}

So, finally, here is the way you should handle it in MVVM - ENJOY !!!

P.S.
Please sorry for any code typos, cause I was typing it right into the blog editor - with no IntelliSence support.

No comments: