background image
  logo  
Icon

ModalDialogManager - A simple approach to dealing with modal dialogs in MVVM

Wed Mar 3rd 2010 by Brett
The more I work with MVVM the more I like it. That being said when I started using the pattern early last year I would run into conceptual roadblocks that would halt my progress and kill productivity. One of those concepts that got me stuck was how to deal with modal dialogs. MVVM cleverly uses databinding to decouple the View from the ViewModel - and there is a lot you can do with databinding in WPF. Opening modal dialogs is not one of them.

The issue centers around the face that a Window in WPF cannot be added to a XAML document anywhere other than the root. This would prevent some kind of strategy where you inlined the window in the xaml and invoked the ShowDialog method via binding. (Aside from that - it would lead to some really huge and ugly XAML files.

I solved this problem by creating a Control called ModalDialogManager that I place in the View acting as the parent to the Modal dialog. This control has several properties - one of which called IsOpen which can be used to control the visibility of the dialog. There are also properties for controlling the size of the resultant dialog, resize mode, icon, and the ICommand to be called if the user clicks the Red X.
<Window>
     <Grid>  
          <Button HorizontalAlignment="Right" Command="{Binding ShowMessageCommand, Mode=Default}" 
             Content="{Binding ShowMessageLabel}" VerticalAlignment="Bottom" Margin="0,0,13,8">
          <Button Width="3" Height="0" HorizontalAlignment="Right" 
             Content="Button" VerticalAlignment="Bottom" Margin="0,0,-210,-256">  
 
          <!-- Define ModalDialogManager and wire up to ViewModel called MessageWindow --> 
          <local:ModalDialogManager Title="{Binding Title}" CloseCommand="{Binding OKCommand}" DialogResizeMode="NoResize"
             DialogWidth="300" DialogHeight="300" IsOpen="{Binding IsOpen}" DataContext="{Binding MessageWindow}" />  
          <TextBlock Margin="21,27,31,64" TextWrapping="Wrap" Text="{Binding Status, Mode=Default}"></TextBlock>  
     </Grid>  
</Window> 


There is one important thing that might not be immediately obvious - when the dialog is shown it will use the same DataContext as the ModalDialogManager. A content presenter at the Root of the modal dialog (Window) will be bound to the ViewModel. Therefore you have to use a DataTemplate to associate a View to this ViewModel. In the sample project I do this in App.xaml.

<DataTemplate DataType="{x:Type vm:MessageWindowViewModel}">
     <v:MessageWindow/>
</DataTemplate>
As soon as the MainViewModel invokes the IsOpen property on the MessageWindowViewModel the dialog will be show and code execution will be transferred to the MessageWindowViewModel. This is a boon, because setting IsOpen acts just like ShowDialog in that after the dialog is closed code excution will resume on the next line. Here are the interesting bits about how the ModalDialogManager class works.
        /// 
        /// This is invoked when the red X is clicked or a keypress closes the window - 
        /// 
        public ICommand CloseCommand
        {
            get { return (ICommand)GetValue(CloseCommandProperty); }
            set { SetValue(CloseCommandProperty, value); }
        }
        public static readonly DependencyProperty CloseCommandProperty =
            DependencyProperty.Register("CloseCommand", typeof(ICommand), typeof(ModalDialogManager), new UIPropertyMetadata(null));


        /// 
        /// This should be bound to IsOpen (or similar) in the ViewModel associated with ModalDialogManager
        /// 
        public bool IsOpen
        {
            get { return (bool)GetValue(IsOpenProperty); }
            set { SetValue(IsOpenProperty, value); }
        }
        public static readonly DependencyProperty IsOpenProperty =
            DependencyProperty.Register("IsOpen", typeof(bool), typeof(ModalDialogManager), new UIPropertyMetadata(false, IsOpenChanged));


        public static void IsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ModalDialogManager m = d as ModalDialogManager;
            bool newVal = (bool)e.NewValue;
            if (newVal)
                m.Show();
            else
                m.Close();
        }


        void Show()
        {
            if (_window != null) Close();

            Window w = new Window();
            _window = w;
            w.Closing += w_Closing;
            w.Owner = GetParentWindow(this);
            
            w.DataContext = this.DataContext;
            w.SetBinding(Window.ContentProperty, "");

            w.Title = Title;
            w.Icon = Icon;
            w.Height = DialogHeight;
            w.Width = DialogWidth;
            w.ResizeMode = DialogResizeMode;
            w.ShowDialog();
        }

        void w_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (!_internalClose)
            {
                _externalClose = true;
                if (CloseCommand != null) CloseCommand.Execute(null);
                _externalClose = false;
            }
        }

        bool _internalClose = false;
        bool _externalClose = false;

        void Close()
        {
            _internalClose = true;
            
            if (!_externalClose) _window.Close();
            _window = null;
            _internalClose = false;
        }

        Window GetParentWindow(FrameworkElement current)
        {
            if (current is Window)
                return current as Window;
            else if (current.Parent is FrameworkElement)
                return GetParentWindow(current.Parent as FrameworkElement);
            else
                return null;
        }

A sample project can be downloaded here. Sorry - no Silverlight version yet, but it is on the way.

2 Comments     follow

Hi,

This is by far the most clean and ingenious way I've yet seen to solve the problem of modal dialogs in MVVM. It took me quite some time to wrap my head around the way things work but now that I 'get' it, I find it a very clever way of maintaining a separation of the viewmodel and the view.

Thanks you for this. Maybe you could put up an article on CodeProject to give it more exposure?

Bye,
Steven
posted by Steven on Tue Mar 23th 2010 at 8:57 AM
Hi Steven-
Thanks for the feedback! Your right - I probably should post this on Code Project. I have actually made a couple of enhancements since this post to take into account dialogs that execute immediately upon launch (my use case was a cancellable progress dialog). I normally submit my Silverlight posts to Silverlight Cream, but I haven't been able to find a similar community for WPF. Code Project is probably the best bet.
Brett
posted by Brett on Thu Apr 1st 2010 at 1:43 PM
Login to post comments
 

Recent Posts

By Month