Dialogs In WPF (MVVM) - Part Three

In this article, I decided to continue to improve the project from the previous articles (part 1 and part 2). Changes to be made are shown below.
  • Allocate dialog service into separate class library;
  • Instantiate dialog service declaratively (in XAML) via dependency property;
  • Some code clean up.
Class library for dialog Service
 
Dialog Service will be responsible to display a dialog Window. The UI design will depend on a view model of a dialog, but some properties and functionality should be provided regardless of any particular design. Since the dialog window belongs to the Service, the reference to the parent Window should be passed from the calling side. Defined Service objects consists of the following
  1. dialog window (view) – A container to hold particular UI design of a dialog;
  2. dialog view model – Base view model to inherit by any dialog view model;
  3. dialog Service – A class which define a piece of code to popup a dialog window;
  4. dialog Service interface – An interface for the Service to implement, to separate calling view model and Service implementation.
Let's start.
 
Step

Open WpfApplication1 solution attached to the article (you may download WpfApplicationResult.zip from part 2).
 
Step 1

Add new class library project to the solution and name it WpfDialogService. This solution is targeted to .NET Framework 4 (but you can decide).
 
Step 2

Delete an empty Class1 from WpfDialogService project.
 
Step 3
 
Add new item -> User control (WPF) -> give a name DialogView, make sure you added reference to System.Xaml assembly to compile the project.
 
Step 4

Replace UserControl base class with Window in DialogView markup and the code at the backend file to compile the project.
 
Step 5
 
Set Window properties in markup.
  1. WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight" 
Step 6
 
Replace Grid container with
  1. <ContentPresenter x:Name="ContentPresenter" Content="{Binding}"></ContentPresenter> 
Final view is given below.
  1. <Window x:Class="WpfDialogService.DialogView"  
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  4.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"   
  5.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"   
  6.         xmlns:local="clr-namespace:WpfDialogService"  
  7.         mc:Ignorable="d"   
  8.         d:DesignHeight="300" d:DesignWidth="300"  
  9.         WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight">  
  10.     <ContentPresenter x:Name="ContentPresenter" Content="{Binding}"></ContentPresenter>  
  11. </Window> 
Step 7

Add a new class and give it the name DialogViewModelBase. Further, any view model for dialog views should inherit the base view model. Add a public property Owner of type Window and CloseDialog method, as shown.
  1. using System.Windows;  
  2.   
  3. namespace WpfDialogService  
  4. {  
  5.     public class DialogViewModelBase  
  6.     {  
  7.         public Window Owner { getset; }  
  8.   
  9.         public void CloseDialog(Window view)  
  10.         {  
  11.             if (view != null)  
  12.                 view.DialogResult = true;  
  13.         }  
  14.     }  

Step 8

Add new class, give it the name DialogService and extract its interface IDialogService, as shown below.
  1. namespace WpfDialogService  
  2. {  
  3.     public interface IDialogService  
  4.     {  
  5.         void ShowDialogModal(DialogViewModelBase vm);  
  6.     }  
  7. }  
  8.   
  9. namespace WpfDialogService  
  10. {  
  11.     public class DialogService : IDialogService  
  12.     {  
  13.         public void ShowDialogModal(DialogViewModelBase vm)  
  14.         {  
  15.             DialogView v = new DialogView();  
  16.             v.Owner = vm.Owner;  
  17.             v.DataContext = vm;  
  18.             v.ShowDialog();  
  19.             v.Owner = null;  
  20.         }  
  21.     }  

Using dialog service
 
Step 9

Delete DialogFacade and DialogService subfolders from Dialogs folder in WpfApplication1 project. Remove all the references of the deleted items to build the project (XAML and the code at the backend).
 
Step 10

Add reference to WpfDialogService library into WpfApplication1.
 
Step 11

Add DialogService property to MainWindowViewModel:
  1. public IDialogService DialogService { getset; } 
Step 12

It is a bit tricky to get DialogService instance initialized in XAML of the main Window. Let’s try to use a dependency property for this purpose. Take a look at the very first line in MainWindow.xaml: 
  1. <Window x:Class="WpfApplication1.Views.MainWindow" ...> 
It means that MainWindow has a Window as a base class. If we have a DialogService as a global resource, we can use it as a value of a property. Unfortunately, the Window does not have any property of such type and we can’t change it easily but we can create a middle class MainWindowBase derived from the Window and add a dependency property of type DialogService.
  1. using System.Windows;  
  2. using WpfApplication1.ViewModels;  
  3. using WpfDialogService;  
  4.   
  5. namespace WpfApplication1.Views  
  6. {  
  7.     public class MainWindowBase : Window  
  8.     {  
  9.         public IDialogService DialogService  
  10.         {  
  11.             get { return (IDialogService)GetValue(DialogServiceProperty); }  
  12.             set { SetValue(DialogServiceProperty, value); }  
  13.         }  
  14.   
  15.         public static readonly DependencyProperty DialogServiceProperty =  
  16.             DependencyProperty.Register("DialogService"typeof(IDialogService), typeof(MainWindowBase), new PropertyMetadata(null, PropertyChangedCallback));  
  17.   
  18.         static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)  
  19.         {  
  20.             Window v = d as Window;  
  21.             if (v == null)  
  22.                 return;  
  23.   
  24.             MainWindowViewModel vm = v.DataContext as MainWindowViewModel;  
  25.             if (vm == null)  
  26.                 return;  
  27.   
  28.             IDialogService ds = e.NewValue as IDialogService;  
  29.             if (ds == null)  
  30.                 return;  
  31.   
  32.             vm.DialogService = ds;  
  33.         }  
  34.     }  

Step 13

Add a global resource for DialogService to App.xaml: add "ds" namespace and a resource with key DialogDervice.
  1. <Application x:Class="WpfApplication1.App"  
  2.              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  4.              xmlns:viewModels="clr-namespace:WpfApplication1.ViewModels"  
  5.              xmlns:dialogYesNo="clr-namespace:WpfApplication1.Dialogs.DialogYesNo"  
  6.              xmlns:ds="clr-namespace:WpfDialogService;assembly=WpfDialogService"  
  7.              StartupUri="Views/MainWindow.xaml">  
  8.     <Application.Resources>  
  9.         <viewModels:ViewModelLocator x:Key="ViewModelLocator" />  
  10.         <ds:DialogService x:Key="DialogService" />  
  11.         <DataTemplate DataType="{x:Type dialogYesNo:DialogYesNoViewModel}">  
  12.             <dialogYesNo:DialogYesNoView></dialogYesNo:DialogYesNoView>  
  13.         </DataTemplate>  
  14.     </Application.Resources>  
  15. </Application> 
Step 14

Now, we can change XAML of MainWindow. Let’s change the code at the backend first. It should look, as shown below.
  1. namespace WpfApplication1.Views  
  2. {  
  3.     /// <summary>  
  4.     /// Interaction logic for MainWindow.xaml  
  5.     /// </summary>  
  6.     public partial class MainWindow : MainWindowBase  
  7.     {  
  8.         public MainWindow()  
  9.         {  
  10.             InitializeComponent();  
  11.         }  
  12.     }  

Pay attention to the base class for MainWindow in the code above. It was changed from Window to MainWindowBase. Now, we can change markup for MainWindow and set the value of DialogService dependency property.
  1. <views:MainWindowBase x:Class="WpfApplication1.Views.MainWindow"  
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
  4.         xmlns:views="clr-namespace:WpfApplication1.Views"  
  5.         Title="MainWindow" Height="350" Width="525"   
  6.         DataContext="{Binding MainWindowViewModel, Source={StaticResource ResourceKey=ViewModelLocator}}"  
  7.         DialogService="{Binding ., Source={StaticResource ResourceKey=DialogService}}">  
  8.     <Grid>  
  9.         <Button Content="Button" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="75"   
  10.                 Command="{Binding OpenDialogCommand}"   
  11.                 CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>  
  12.     </Grid>  
  13. </views:MainWindowBase> 
In XAML given above, MainWindow has a MainWindowBase as a base class and view namespace was added because MainWindowBase belongs to it.

Step15

Take a look at DialogService dependency property [Step 12]. The callback method PropertyChangedCallback is defined. It helps to set a value of DialogService property in MainWindowViewModel.

Step 16

Make DialogYesNoViewModel is inherited from DialogViewModelBase, modify OnYesClicked and OnNoClicked methods to call CloseDialog method of the base class is shown below.
  1. ...  
  2. private void OnYesClicked(object parameter)  
  3. {  
  4.     this.YesClicked(this, EventArgs.Empty);  
  5.     CloseDialog(parameter as Window);  
  6. }  
  7.   
  8. private void OnNoClicked(object parameter)  
  9. {  
  10.     this.NoClicked(this, EventArgs.Empty);  
  11.     CloseDialog(parameter as Window);  
  12. }  
  13. ... 
Step 17

Add Message property to DialogYesNoViewModel, as shown below.
  1. public string Message { getprivate set; } 
Set the property in the constructor, as shown below.
  1. public DialogYesNoViewModel(string message)  
  2. {  
  3.     Message = message;  
  4.     this.yesCommand = new RelayCommand(OnYesClicked);  
  5.     this.noCommand = new RelayCommand(OnNoClicked);  

Step 18

Modify OnOpenDialog method in MainWindowViewModel to use DialogService property to open a dialog,
  1. private void OnOpenDialog(object parameter)  
  2. {  
  3.     var vm = new Dialogs.DialogYesNo.DialogYesNoViewModel("Question");  
  4.     vm.YesClicked += OptionYes;  
  5.     vm.NoClicked += OptionNo;  
  6.     vm.Owner = parameter as Window;  
  7.     if (DialogService != null)  
  8.         DialogService.ShowDialogModal(vm);  

Please find the source at GitHub repository.

Up Next
    Ebook Download
    View all
    Learn
    View all