DevExpress MVVM Framework. Introduction to Services, DXMessageBoxService and DialogService.

WPF Team Blog
30 September 2013

 

OTHER RELATED ARTICLES:

  1. Getting Started with DevExpress MVVM Framework. Commands and View Models.
  2. THIS POST: DevExpress MVVM Framework. Introduction to Services, DXMessageBoxService and DialogService.
  3. DevExpress MVVM Framework. Interaction of ViewModels. IDocumentManagerService.
  4. DevExpress MVVM Framework. Introduction to POCO ViewModels.
  5. DevExpress MVVM Framework. Interaction of ViewModels. Messenger.
  6. DevExpress MVVM Framework. Using Scaffolding Wizards for building Views.
  7. DevExpress MVVM Framework. Data validation. Implementing IDataErrorInfo.
  8. DevExpress MVVM Framework. Using DataAnnotation attributes and DevExpress Fluent API.
  9. DevExpress MVVM Framework. Behaviors.
  10. DevExpress MVVM Framework. TaskbarButtonService, ApplicationJumpListService and NotificationService.
  11. DevExpress MVVM Framework. Asynchronous Commands.
  12. DevExpress MVVM Framework. Converters.


Previously, we examined the basic capabilities of the DevExpress MVVM Framework (Commands and View Models). Today, we will explore more interest things by examining how to perform View-related actions from the View Model layer.

We start by looking at the simple task: displaying a MessageBox through a View Model.

The easiest, yet incorrect, way to solve this task is to use the MessageBox.Show method directly from the View Model. Obviously, this approach has a number of serious drawbacks. In fact, the MessageBox is a part of the View, so the use of the MessageBox from the View Model layer breaks the main MVVM rule: the View Model layer should not refer to the View layer. Thus, using this incorrect approach makes it impossible to write unit-tests for View Models.

For solving such tasks in MVVM, the DevExpress MVVM Framework has a special mechanism – Services.

Let’s discuss how to solve the posed task with Services. We have the following View Model…

   1: public class DocumentViewModel : ViewModelBase {
   2:     public ICommand CloseDocumentCommand { get; private set; }
   3:     public DocumentViewModel() {
   4:         CloseDocumentCommand = new DelegateCommand(OnCloseDocumentCommandExecute);
   5:     }
   6:     void OnCloseDocumentCommandExecute() {
   7:         MessageBoxResult canCloseDocument;
   8:         //canCloseDocument = 
   9:         //    MessageBox.Show("Want to save your changes?", " Document", MessageBoxButton.YesNoCancel);
  10:         if(canCloseDocument == MessageBoxResult.Yes) {
  11:             //...
  12:         }
  13:     }
  14: }

… and the following View:

   1: <UserControl x:Class="MessageBoxServiceTest.View.DocumentView"
   2:              xmlns:ViewModel="clr-namespace:MessageBoxServiceTest.ViewModel"
   3:              ...>
   4:     <UserControl.DataContext>
   5:         <ViewModel:DocumentViewModel/>
   6:     </UserControl.DataContext>
   7:     ...
   8:         <Button Content="Close Document" Command="{Binding CloseDocumentCommand}" .../>
   9:     ...
  10: </UserControl>

The DevExpress.Xpf.Mvvm library provides the IMessageBoxService interface. The implementation of this interface is contained in the DevExpress.Xpf.Core library – the DXMessageBoxService. To add this service to our View (DocumentView), add it to the Interaction.Behaviors collection as follows.

   1: <UserControl xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
   2:              xmlns:dxmvvm=http://schemas.devexpress.com/winfx/2008/xaml/mvvm ...>
   3:     <UserControl.DataContext>
   4:         ...
   5:     </UserControl.DataContext>
   6:     <dxmvvm:Interaction.Behaviors>
   7:         <dx:DXMessageBoxService/>
   8:     </dxmvvm:Interaction.Behaviors>
   9:     ...
  10: </UserControl>

Services are automatically injected to View Models, so they are available from there via an interface that is provided by a certain service.

As you may have noticed, our View Model (DocumentViewModel) is inherited from the ViewModelBase class. So, the DocumentViewModel supports the GetService<T> method that returns an interface used to access the DXMessageBoxService.

   1: public class DocumentViewModel : ViewModelBase {
   2:     public ICommand CloseDocumentCommand { get; private set; }
   3:     public IMessageBoxService MessageBoxService { get { return GetService<IMessageBoxService>(); } }
   4:     ...
   5:     void OnCloseDocumentCommandExecute() {
   6:         MessageBoxResult canCloseDocument = MessageBoxService.Show(
   7:             messageBoxText: "Want to save your changes?", 
   8:             caption: "Document", 
   9:             button: MessageBoxButton.YesNoCancel);
  10:         if(canCloseDocument == MessageBoxResult.Yes) {
  11:             //...
  12:         }
  13:     }
  14: }

Blog.Services.001

Thus, follow the steps below to use Services.

  1. Define a service in XAML
  2. Get access to the service’s interface from your View Model via the GetService<T> method

The use of Services makes it easy to create unit-tests for your View Models. Let’s write a test for the above-mentioned DocumentViewModel (Moq Framework is used).

   1: [TestFixture]
   2: public class DocumentViewModelTests {
   3:     [Test]
   4:     public void Test() {
   5:         bool serviceIsCalled = false;
   6:         var viewModel = new DocumentViewModel();
   7:         var service = new Mock<IMessageBoxService>(MockBehavior.Strict);
   8:         service.
   9:            Setup(foo => foo.Show(
  10:                "Want to save your changes?", "Document", MessageBoxButton.YesNoCancel, 
  11:                 MessageBoxImage.None, MessageBoxResult.None)).
  12:            Returns((string text, string caption, MessageBoxButton button,
  13:                 MessageBoxImage image, MessageBoxResult none) => {
  14:                serviceIsCalled = true;
  15:                return MessageBoxResult.OK;
  16:            });
  17:         ((ISupportServices)viewModel).ServiceContainer.RegisterService(service.Object);
  18:         viewModel.CloseDocumentCommand.Execute(null);
  19:         Assert.IsTrue(serviceIsCalled);
  20:     }
  21: }

The DXMessageBoxService for Silverlight will be available with the one of minor 13.1 versions.

 

 

DialogService.

Now, let’s move to a more complex task. For instance, we need to show a modal window for user registration. This window should contain two buttons: Register and Cancel. The Register button should be disabled if inputted data is invalid. When the window is closed, registration results should be passed to the main View Model. For this task, the DevExpress MVVM Framework provides the IDialogService interface and its implementation – DialogService.

Suppose that we have a View with the Show Registration Form button.

   1: <UserControl x:Class="DialogServiceTest.View.MainView"
   2:              xmlns:ViewModel="clr-namespace:DialogServiceTest.ViewModel" ...>
   3:     <UserControl.DataContext>
   4:         <ViewModel:MainViewModel/>
   5:     </UserControl.DataContext>
   6:     ...
   7:         <Button Content="Show Registration Form" Command="{Binding ShowRegistrationFormCommand}" .../>
   8:     ...
   9: </UserControl>

The button is bound to the MainViewModel command.

   1: public class MainViewModel : ViewModelBase {
   2:     public ICommand ShowRegistrationFormCommand { get; private set; }
   3:     public MainViewModel() {
   4:         ShowRegistrationFormCommand = new DelegateCommand(OnShowRegistrationFormCommandExecute);
   5:     }
   6:     void OnShowRegistrationFormCommandExecute() {
   7:     }
   8: }

When the ShowRegistrationFormCommand is invoked, we need to show a dialog window that contains another View.

   1: <UserControl x:Class="DialogServiceTest.View.RegistrationView" ...>
   2:     <StackPanel Orientation="Horizontal" ...>
   3:         <TextBlock Text="User Name: " .../>
   4:         <TextBox Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" .../>
   5:     </StackPanel>
   6: </UserControl>

The RegistrationView contains only one editor bound to the RegistrationViewModel. Please note that we do not set the View Model for the RegistrationView directly. In our case, we’ll create a View Model for the RegistrationView at the main View Model level. We’ll return to this mechanism later. If you need to set a design-time View Model for your view, use the d:DataContext property.  The RegistrationViewModel implementation is as follows.

   1: public class RegistrationViewModel : ViewModelBase {
   2:     string userName = string.Empty;
   3:     public string UserName {
   4:         get { return userName; }
   5:         set { SetProperty(ref userName, value, () => UserName); }
   6:     }
   7: }

Next, we implement display a dialog window that contains the RegistrationView. To do this, add the DialogService to the main View.

   1: <UserControl x:Class="DialogServiceTest.View.MainView"
   2:              xmlns:View="clr-namespace:DialogServiceTest.View"
   3:              xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
   4:              xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm" ...>
   5:     ...
   6:     <dxmvvm:Interaction.Behaviors>
   7:         <dx:DialogService DialogWindowStartupLocation="CenterOwner">
   8:             <dx:DialogService.ViewTemplate>
   9:                 <DataTemplate>
  10:                     <View:RegistrationView/>
  11:                 </DataTemplate>
  12:             </dx:DialogService.ViewTemplate>
  13:             <dx:DialogService.DialogStyle>
  14:                 <Style TargetType="dx:DXDialogWindow">
  15:                     <Setter Property="Width" Value="300"/>
  16:                     <Setter Property="Height" Value="160"/>
  17:                 </Style>
  18:             </dx:DialogService.DialogStyle>
  19:         </dx:DialogService>
  20:     </dxmvvm:Interaction.Behaviors>
  21:     ...
  22: </UserControl>

The DialogService provides several properties for the customization of the dialog window.

  1. The ViewTemplate is used for setting a View that will be shown inside the dialog window.
  2. The ViewTemplateSelector sets a DataTemplateSelector that returns a certain ViewTemplate by the passed View Model.
  3. The DialogStyle sets a Style for the dialog window.
  4. The DialogWindowStartupLocation sets window’s startup position. This property is added to the DialogService, because the Window.WindowStartupLocation is not a dependency property and it cannot be set from a Window style.

Next, we’ll use the defined DialogService from our View Model.

   1: public class MainViewModel : ViewModelBase {
   2:     public ICommand ShowRegistrationFormCommand { get; private set; }
   3:     IDialogService DialogService { get { return GetService<IDialogService>(); } }
   4:     ...
   5:     void OnShowRegistrationFormCommandExecute() {
   6:         RegistrationViewModel registrationViewModel = new RegistrationViewModel();
   7:         DialogService.ShowDialog(
   8:             dialogCommands: null, 
   9:             title : "Registration Dialog", 
  10:             viewModel : registrationViewModel
  11:         );
  12:     }
  13: }

As you can see in the above code snippet, we show the dialog window via the ShowDialog method. The IDialogService has several extension methods for different scenarios. For instance, to set standard buttons for the dialog, use the following extension.

   1: MessageBoxResult ShowDialog(this IDialogService service, 
   2:     MessageBoxButton dialogButtons, string title, object viewModel);

Our scenario is more complex, so we’ll use another extension that allows setting custom buttons:

   1: UICommand ShowDialog(this IDialogService service, 
   2:     IList<UICommand> dialogCommands, string title, object viewModel);

This method takes a collection of UICommand objects as a parameter. In fact, the UICommand is a ready-to-use View Model for a dialog button. The UICommand is defined as follows.

   1: public class UICommand : BindableBase {
   2:     public object Caption { get; set; }
   3:     public ICommand Command { get; set; }
   4:     public object Id { get; set; }
   5:     public bool IsCancel { get; set; }
   6:     public bool IsDefault { get; set; }
   7:     public object Tag { get; set; }
   8: }

Let’s create two UICommands at the MainViewModel level and pass these UICommands to the ShowDialog method.

   1: public class MainViewModel : ViewModelBase {
   2:     ...
   3:     RegistrationViewModel RegistrationViewModel = null;
   4:     void OnShowRegistrationFormCommandExecute() {
   5:         if(RegistrationViewModel == null)
   6:             RegistrationViewModel = new RegistrationViewModel();
   7:         UICommand registerCommand = new UICommand() {
   8:             Caption = "Register",
   9:             IsCancel = false,
  10:             IsDefault = true,
  11:             Command = new DelegateCommand<CancelEventArgs>(
  12:                 x => { },
  13:                 x => !string.IsNullOrEmpty(RegistrationViewModel.UserName)
  14:             ),
  15:         };
  16:         UICommand cancelCommand = new UICommand() {
  17:             Id = MessageBoxResult.Cancel,
  18:             Caption = "Cancel",
  19:             IsCancel = true,
  20:             IsDefault = false,
  21:         };
  22:         UICommand result = DialogService.ShowDialog(
  23:             dialogCommands: new List<UICommand>() { registerCommand, cancelCommand }, 
  24:             title : "Registration Dialog", 
  25:             viewModel : RegistrationViewModel
  26:         );
  27:         if(result == registerCommand) {
  28:             //...
  29:         }
  30:     }
  31: }

Please note that dialog commands take a CancelEventArgs object as a parameter. When dialog commands are invoked, the dialog is closed by default. To prevent this behavior, it is necessary to set the CancelEventArgs.Cancel parameter to True. For instance:

   1: UICommand dialogCommand = new UICommand() {
   2:     Command = new DelegateCommand<CancelEventArgs>(OnDialogCommandExecute),
   3: };
   4: void OnDialogCommandExecute(CancelEventArgs parameter) {
   5:     parameter.Cancel = true;
   6: }

When the registration process is finished and the dialog is closed, the ShowDialog method returns a command, which closed the dialog. After that, you can implement the necessary logic at the main View Model level.

   1: void OnShowRegistrationFormCommandExecute() {
   2:     ...
   3:     UICommand result = DialogService.ShowDialog(
   4:         dialogCommands: new List<UICommand>() { registerCommand, cancelCommand }, 
   5:         title : "Registration Dialog", 
   6:         viewModel : RegistrationViewModel
   7:     );
   8:     if(result == registerCommand) {
   9:         //...
  10:     }
  11: }

Blog.Services.002

An asynchronous DialogService version for Silverlight will be available in 13.2.

You can find more information about Services in our documentation. This page contains links to Code Examples for each Service.

This is all for today. Thank you for your time!

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
No Comments

Please login or register to post comments.