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.
Gregor Stamac
Gregor Stamac

I think displayed text shouldn't be in the ViewModel. The solution above is not much better then creating a MessageBox in the ViewModel. Of course it's difficult to test that. I guess a true MVVM solution would be to bind Close button to a MessageBox and bind the "Yes" button of the MessageBox to CloseDocumentCommand. Is there a way to do that with DXMessageBoxService?

28 October 2013
Alexander (DevExpress Support)
Alexander (DevExpress Support)

Hi Gregor,

You are partially right that the message box text may be set from XAML. However, in a common case, it is possible that the message box text is calculated from the View Model. In such cases, it is necessary to cover the calculation logic by unit-tests. For this reason, we implemented that API.

As for your last comment, I see what you are trying to say. So, it is a reasonable case.I have created a special attached behavior for it. The attached behavior implements confirmation logic. For instance, you need to close the main window when an end-user clicks a button. You have a corresponding command in your ViewModel, and now you need to bind the button to the command. However, when the command is invoked, you need to show a confirmation dialog. For such scenarios, you can use the ConfirmationBehavior. Please review the following thread, which contains a simple sample demonstrating how to use it and share your thoughts about this approach: www.devexpress.com/.../S172484.

29 October 2013
gabriel caro
gabriel caro

Hello,

How to close a View that inherits from UserControl class and implements a RibbonBar from ViewModel. This View is shown using the IDialogService MVVM approach?. The idea is close the View doing click in a button available in the RibbonBar.

Thank you

16 August 2014
Alexander (DevExpress Support)
Alexander (DevExpress Support)

Hi Gabriel,

You can use CurrentWindowService for this purpose. This issue has already been discussed in the following thread: www.devexpress.com/.../Q574092

If you need further assistance, please create a support ticket. This will allow us to provide you with a sample and additional information.

18 August 2014
gabriel caro
gabriel caro

Hi Alexander,

Thank you for your reply.

Is possible to use CurrentWindowService to close a 'window' generates using the WindowedDocumentUIService (all 'windows' inherits from UserControl class).

Could you address me how to define the currentWindowService to use in the specific window where is declared?.

Sorry, most all properties and some MVVM articles says [To Be Supplied]

Thank you.

29 August 2014
gabriel caro
gabriel caro

Hi Alexander,

I found (or better said, I understood how works the CurrentWindowService)  the solution.

Thank you for your support..

Regards

29 August 2014
Alexander (DevExpress Support)
Alexander (DevExpress Support)

Hi Gabriel,

I am glad to hear that you have found a solution. As for our documentation, we are currently working on it. With the next minor update, we will update the documentation, so it will contain more information about our MVVM framework.

31 August 2014
JosB
JosB

Hi Alexander,

Thanks From this great post an the libraries

I'm trying to implement the dialog service I'm using the DevExpress.MVVM.Free from nuget but i cant find the  <dx:DialogService/>

20 April 2015
Michael Ch (DevExpress Support)
Michael Ch (DevExpress Support)

Hello JosB,

Although, the DialogService is not available in the free version of our MVVM Framework because it uses our commercial WPF components, you can easily implement your own DialogService by using base classes provided within assemblies of free versions. Please refer to the documentation.devexpress.com topic, where you will find corresponding information on how to implement a custom service. Take a moment to review it. If you need further assistance or face any difficulties using DevExpress MVVM Framework, I kindly ask you to contact our support team by submitting a ticket to our SC (you can do this here: www.devexpress.com/.../Create) and provide more details about the issue you have faced. We will be happy to assist you.

Thanks,

Michael

20 April 2015
3TM5VX2BJj3
3TM5VX2BJj3

Can this sample be downloaded? If so, what is the URL?

Thank you very much!

21 June 2015
3TM5VX2BJj3
3TM5VX2BJj3

I don't see any links to download the sample app that's used in this post either. Am I missing them?

21 June 2015
Michael Ch (DevExpress Support)
Michael Ch (DevExpress Support)

Hi @JRelyea,

You can download the required examples from here:

www.devexpress.com/.../E4859

www.devexpress.com/.../T144023

The documentation.devexpress.com topic provides

more information about our services and MVVM Framework.

24 June 2015
Anil Pandya 1
Anil Pandya 1

How can I hide the maximize box on the dialog window you created ?

Because, I dont want to allow user to resize or mazimize the window.

21 July 2015
Michael Ch (DevExpress Support)
Michael Ch (DevExpress Support)

Hi Anil,

To customize the DXDialogWindow that is displayed by DialogService, use the DialogService.DialogStyle property. For example, to prevent the DXDialogWindow from being resized, set the DXDialogWindow.ResizeMode property to NoResize in this style as shown in the code snippet below:

<dx:DialogService.DialogStyle>

   <Style TargetType="dx:DXDialogWindow">

       <Setter Property="SizeToContent" Value="WidthAndHeight"/>

       <Setter Property="ResizeMode" Value="NoResize"/>

   </Style>

</dx:DialogService.DialogStyle>

Thanks,

Michael

22 July 2015
Noufal Aboobacker 1
Noufal Aboobacker 1

When I am trying to write test for IMessageBoxService getting an error

System.NotSupportedException

Expression references a method that does not belong to the mocked object: boxService => boxService.Show(It.IsAny<String>(), It.IsAny<String>(), MessageBoxButton.YesNo, MessageBoxImage.Question)

  at Moq.Mock.ThrowIfNotMember(Expression setup, MethodInfo method)

  at Moq.Mock.<>c__DisplayClass1c`2.<Setup>b__1b()

  at xUnitTests.XUnitTests.<EmployeeIsDeletedOnlyIfUsersConformsIt>d__3.MoveNext() in C:\Users\noufal\Documents\Visual Studio 2015\Projects\xUnitTests\xUnitTests\Class1.cs:line 76

--- End of stack trace from previous location where exception was thrown ---

  at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<>c.<ThrowAsync>b__6_0(Object state)

  at Xunit.Sdk.AsyncTestSyncContext.<>c__DisplayClass7_0.<Post>b__1(Object _) in C:\BuildAgent\work\cb37e9acf085d108\src\xunit.execution\Sdk\AsyncTestSyncContext.cs:line 75

19 April 2016
Alexander (DevExpress Support)
Alexander (DevExpress Support)

Hi Noufal,

Please contact our support team. Submit a ticket and describe your issue in detail: www.devexpress.com/.../Create

We will do our best to help you.

20 April 2016
Noufal Aboobacker 1
Noufal Aboobacker 1

Sorry Alexander,

Issue resolved.

I was trying to use the extension method which cause the problem.

Thanks

20 April 2016
Alexander (DevExpress Support)
Alexander (DevExpress Support)

Thank you for informing us. I am glad to hear that the issue is solved.

20 April 2016

Please login or register to post comments.