WPF – MVVM Animated Dialogs

Recently someone asked what is a good way to show animated dialog boxes/controls while keeping the views and view models separated in an MVVM fashion.

My answer was to use data templates! Such that, you have a view model to represent a dialog box/control (I will refer to these as simply dialogs), whether it be an alert, modal view to edit properties, informational or anything else, and let the view decide how and where to show it by using a common service to which you can pass these alerts in.

Data templates allowed me to get an affect like this:

AnimatedDialogs

The view model layer could be represented using the following dependency graph:

IDIalogService

The IDialogService exposes 1 method (ShowDialog) which is used by the view model in order to show any IDialog, with an optional generic continuation Action<TDialog> so some more logic can be carried out after the dialog closes. The IDialogService also exposes 2 events (DialogShown / DialogClosed) which allow any interested parties to carry out some action whenever a dialog is shown or closed. This is used by the MainViewModel, which hosts the CurrentDialog property, in order to show and hide dialogs.

public interface IDialogService
{
    IContinueWith ShowDialog(TDialog dialog) where TDialog : IDialog;

    event EventHandler DialogShown;
    event EventHandler DialogClosed;
}

The MainViewModel which is used as the DataContext for the MainWindow, looks like the following:

public class MainViewModel : ViewModelBase
{
    public MainViewModel(IDialogService dialogService) { ... }
    public RelayCommand CommandAboutMe { ... }
    public IDialog CurrentDialog { ... }
    public ObservableCollection Posts { ... }
}

The MainViewModel, has the IDialogService injected into it, and it subscribes to the IDialogService.DialogShown and IDialogService.DialogClosed events, which in turn set the CurrentDialog to a given IDialog (on shown) or null (on closed). This allows the external sources to show and hide dialogs on the MainWindow via the IDialogService.

MainWindow.xaml:

<Window x:Class="MvvmAnimatedDialogs.MainWindow" ... >
    <Grid>
        <DockPanel>
             /* Content of window */
        </DockPanel>
        <ContentControl Content="{Binding CurrentDialog}" />
    </Grid>
</Window>

In the MainWindow, the DockPanel contains all of the UI layout for the main window, and the ContentControl is a place-holder for the dialogs, which is bound the the MainViewModel.CurrentDialog. It uses the power of WPF data templates in order to decide how to display whatever IDialog object it is bound to (via the CurrentDialog property) and will display nothing if it is null. It is important to either make the ContentControl the last element, or set its Z-Index to a value higher than any other Z-Index, in order for the dialogs to be shown on top of everything else.

The ContentControl will traverse up the tree, trying to find data templates for an object of the current type that its Content property is bound to, so the data templates are placed as a ResourceDictionary in the App.xaml file.

<Application x:Class="MvvmAnimatedDialogs.App" ....
             xmlns:viewModel="clr-namespace:MvvmAnimatedDialogs.ViewModel">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Dialogs/DialogTemplates.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

And inside the DialogTemplates.xaml, I have DataTemplates for each concrete implementation of IDialog.

DialogTemplates.xaml

<ResourceDictionary>
    <DataTemplate DataType="{x:Type dialogs:SimpleDialog}" Resources="{StaticResource DialogResources}">
        <Border>            
            <Border>                
                <Grid Margin="20">
                    <DockPanel Grid.Column="1">
                        <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right">
                            <Button Content="OK" Command="{Binding CommandOk}" />
                        </StackPanel>
                        <TextBlock Text="{Binding Message}" />
                    </DockPanel>
                </Grid>
            </Border>
        </Border>
    </DataTemplate>
    <DataTemplate DataType="{x:Type dialogs:OkCancelDialog}" Resources="{StaticResource DialogResources}">
        <Border>
            <Border>
                <Grid Margin="20">
                    <DockPanel Grid.Column="1">
                        <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right">
                            <Button Content="OK" Command="{Binding CommandOk}" />
                            <Button Content="Cancel" Command="{Binding CommandCancel}" />
                        </StackPanel>
                        <TextBlock Text="{Binding Message}" />
                    </DockPanel>
                </Grid>
            </Border>
        </Border>
    </DataTemplate>
</ResourceDictionary>

Yes I know the plumbing code for each dialog should be abstracted into its own UserControl/CustomControl but for this exercise copy + paste will have to suffice.

As usual, you can find all the code on GitHub @ codePerf/20150315-WpfMvvmAnimatedDialogs

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax