DevExpress MVVM Framework. Interaction of ViewModels. IDocumentManagerService.

08 October 2013

OTHER RELATED ARTICLES:

  1. Getting Started with DevExpress MVVM Framework. Commands and View Models.
  2. DevExpress MVVM Framework. Introduction to Services, DXMessageBoxService and DialogService.
  3. THIS POST: 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 a common Service mechanism and two Service implementations: DXMessageBoxService and DialogService. In this post, we’ll explore View creation and ViewModel interaction mechanisms.

View creation mechanisms

There are two main approaches to create Views with Services:

  1. ViewTemplate and ViewTemplateSelector.
  2. ViewLocator.

ViewTemplate and ViewTemplateSelector

In our previous blog post, we created a Registration View and displayed it in a separate dialog window via a DialogService. Our View was defined at the DialogService ViewTemplate

   1: <dx:DialogService ...>
   2:     <dx:DialogService.ViewTemplate>
   3:         <DataTemplate>
   4:             <View:RegistrationView/>
   5:         </DataTemplate>
   6:     </dx:DialogService.ViewTemplate>
   7:     ...
   8: </dx:DialogService>

The Registration ViewModel was created from the main ViewModel and passed as a parameter to the DialogService.

   1: RegistrationViewModel registrationViewModel = new RegistrationViewModel();
   2: DialogService.ShowDialog(..., viewModel: registrationViewModel);

Alternatively, a View can be dynamically generated from the passed ViewModel. The ViewTemplateSelector supports this approach.

   1: public class DialogViewTemplateSelector : DataTemplateSelector {
   2:     public DataTemplate RegistrationViewTemplate { get; set; }
   3:     public DataTemplate DefaultViewTemplate { get; set; }
   4:     public override DataTemplate SelectTemplate(object item, DependencyObject container) {
   5:         if(item is RegistrationViewModel)
   6:             return RegistrationViewTemplate;
   7:         return DefaultViewTemplate;
   8:     }
   9: }
 
   1: <Helper:DialogViewTemplateSelector x:Key="dialogViewTemplateSelector">
   2:     <Helper:DialogViewTemplateSelector.RegistrationViewTemplate>
   3:         <DataTemplate>
   4:             <View:RegistrationView/>
   5:         </DataTemplate>
   6:     </Helper:DialogViewTemplateSelector.RegistrationViewTemplate>
   7:     <Helper:DialogViewTemplateSelector.DefaultViewTemplate>
   8:         <DataTemplate>
   9:             <TextBlock Text="Default Template"/>
  10:         </DataTemplate>
  11:     </Helper:DialogViewTemplateSelector.DefaultViewTemplate>
  12: </Helper:DialogViewTemplateSelector>
  13: <dx:DialogService ViewTemplateSelector="{StaticResource dialogViewTemplateSelector}" ...>
  14:     ...
  15: </dx:DialogService>

ViewLocator

Alternatively, the ViewLocator provides a simple composition mechanism and also supports creating a View by name.

   1: <dxmvvm:Interaction.Behaviors>
   2:     <dx:DialogService/>
   3: </dxmvvm:Interaction.Behaviors>
 
   1: UICommand result = DialogService.ShowDialog(
   2:     dialogCommands: new List<UICommand>() { registerCommand, cancelCommand },
   3:     title: "Registration Dialog",
   4:     documentType: "RegistrationView",
   5:     viewModel: registrationViewModel
   6: );

The preceding code implicitly calls the default ViewLocator to create a View for the document type at the parameter.

The default ViewLocator is limited to creating Views contained in the main application. To create Views from other libraries, register these additional libraries by creating a custom ViewLocator on application startup.

   1: ViewLocator viewLocator = 
   2:     new ViewLocator(typeof(MainView).Assembly, typeof(RegistrationView).Assembly);
   3: ViewLocator.Default = viewLocator;

If the default ViewLocator implementation does not meet your requirements, you can implement a custom ViewLocator. For instance, the following ViewLocator on-demand library loading:

   1: ViewLocator.Default = new MainViewLocator();
   2:  
   3: public class MainViewLocator : IViewLocator {
   4:     public object ResolveView(string name) {
   5:         if(name == "RegistrationView") {
   6:             Assembly assembly = Assembly.LoadFrom(@"DialogServiceTestLib.dll");
   7:             Type t = assembly.GetType("DialogServiceTest.View.RegistrationView");
   8:             return Activator.CreateInstance(t);
   9:         }
  10:         return null;
  11:     }
  12: }

You may also set a particular ViewLocator at the Service level. Any custom Service can support View creation by inheriting from the ViewServiceBase class and exposing its ViewLocator property.

 

 

Interaction of ViewModels

We’ve explored the mechanisms of View creation. Since Views are generally unaware of each other, interaction is mediated at the ViewModel level. ViewModel interactions are implemented by either of the following approaches:

  1. Closely-coupled ViewModels.
  2. Loosely-coupled ViewModels.

Closely-coupling implies ViewModel creation by other ViewModels. Loose-coupling implies ViewModels bind to Views while ViewModel interaction is handled with parameter passing. Both strategies can be used simultaneously.

We’ll examine these interaction patterns in detail.

Closely-coupled ViewModels

The previous DialogService example is built entirely on this interaction type. The Registration ViewModel is created in the main ViewModel code which has access to the properties on the child ViewModel.

   1: RegistrationViewModel registrationViewModel = new RegistrationViewModel();
   2: DialogService.ShowDialog(..., viewModel : registrationViewModel);

Loosely-coupled ViewModels

The ViewModelBase is a basic ViewModel class supporting two interfaces:

   1: public interface ISupportParameter {
   2:     object Parameter { get; set; }
   3: }
   4: public interface ISupportParentViewModel {
   5:     object ParentViewModel { get; set; }
   6: }

ISupportParameter passes parameters from the main ViewModel to the child ViewModel. There’s no restriction to what can serve as a parameter. For instance, a ViewModel editing a table row can be passed a row or key as a parameter.

ISupportParentViewModel makes it possible to access Services of the main ViewModel from the child ViewModel. I.e., all services defined by the main View are available in the child ViewModel.

With a ViewModel defined in XAML…

   1: <dx:DialogService ...>
   2:     <dx:DialogService.ViewTemplate>
   3:         <DataTemplate>
   4:             <View:RegistrationView>
   5:                 <View:RegistrationView.DataContext>
   6:                     <ViewModel:RegistrationViewModel/>
   7:                 </View:RegistrationView.DataContext>
   8:             </View:RegistrationView>
   9:         </DataTemplate>
  10:     </dx:DialogService.ViewTemplate>
  11:     ...
  12: </dx:DialogService>

…call a Service method to pass the parameter and parent ViewModel:

   1: DialogService.ShowDialog(
   2:     ...
   3:     parameter: "Parameter", 
   4:     parentViewModel: this,
   5: );

In this case, the DialogService picks up the ViewModel from the DataContext of the generated View and sets the Parameter and ParentViewModel properties on this ViewModel.

Messenger

Messenger also implements ViewModel interaction. This class exchanges messages between loosely-coupled parts of the application. We’ll discuss this pattern in detail in a future article.

 

 

IDocumentManagerService

IDocumentManagerService is an abstraction of the document manager. With version 13.1, we introduced two implementations of this service: WindowedDocumentUIService and TabbedDocumentUIService.

WindowedDocumentUIService

WindowedDocumentUIService supports the display and control of Views in separate windows. Consider the simple sample. We have two Views -- Document1View and Document2View -- corresponding to ViewModels that are assigned in XAML (via DataContext).

The main View presents two buttons to open each document (View).

   1: <UserControl x:Class="DocumentServiceTest.View.MainView"
   2:              xmlns:ViewModel="clr-namespace:DocumentServiceTest.ViewModel"
   3:              ...>
   4:     <UserControl.DataContext>
   5:         <ViewModel:MainViewModel/>
   6:     </UserControl.DataContext>
   7:     <StackPanel Orientation="Horizontal">
   8:         <Button Content="Show Document1 Window" 
   9:             Command="{Binding ShowDocumentCommand}" CommandParameter="Document1View" />
  10:         <Button Content="Show Document3 Window" 
  11:             Command="{Binding ShowDocumentCommand}" CommandParameter="Document2View" />
  12:     </StackPanel>
  13: </UserControl>

 

   1: public class MainViewModel : ViewModelBase {
   2:     public ICommand ShowDocumentCommand { get; private set; }
   3:     public MainViewModel() {
   4:         ShowDocumentCommand = new DelegateCommand<string>(OnShowDocumentCommandExecute);
   5:     }
   6:     void OnShowDocumentCommandExecute(string document) {
   7:     }
   8: }

The main application window contains the main view.

   1: <Window x:Class="DocumentServiceTest.WindowedDocumentUIServiceWindow"
   2:         xmlns:View="clr-namespace:DocumentServiceTest.View"
   3:         ...>
   4:     <Grid>
   5:         <View:MainView ...>
   6:         </View:MainView>
   7:     </Grid>
   8: </Window>

To display the Document1View and Document2View from the main ViewModel, we will use the WindowedDocumentUIService. Add this service to the main view at the main window level.

   1: <Window x:Class="DocumentServiceTest.WindowedDocumentUIServiceWindow"
   2:         xmlns:View="clr-namespace:DocumentServiceTest.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:     <Grid>
   7:         <View:MainView>
   8:             <dxmvvm:Interaction.Behaviors>
   9:                 <dx:WindowedDocumentUIService>
  10:                     <dx:WindowedDocumentUIService.WindowStyle>
  11:                         <Style TargetType="Window">
  12:                             <Setter Property="Height" Value="300"/>
  13:                             <Setter Property="Width" Value="400"/>
  14:                         </Style>
  15:                     </dx:WindowedDocumentUIService.WindowStyle>
  16:                 </dx:WindowedDocumentUIService>
  17:             </dxmvvm:Interaction.Behaviors>
  18:         </View:MainView>
  19:     </Grid>
  20: </Window>

The service is now available from the main ViewModel. We’ll use it to display the child views.

   1: public class MainViewModel : ViewModelBase {
   2:     public ICommand ShowDocumentCommand { get; private set; }
   3:     IDocumentManagerService DocumentManager { 
   4:         get { return GetService<IDocumentManagerService>(); } 
   5:     }
   6:     public MainViewModel() {
   7:         ShowDocumentCommand = new DelegateCommand<string>(OnShowDocumentCommandExecute);
   8:     }
   9:     void OnShowDocumentCommandExecute(string document) {
  10:         IDocument doc = DocumentManager.CreateDocument(document, null, this);
  11:         doc.DestroyOnClose = true;
  12:         doc.Title = document;
  13:         doc.Show();
  14:     }
  15: }

In the preceding code, we created a document via the service and call the document’s Show method. Our document supports the IDocument interface and provides access to a container with the appropriate View. While using the WindowedDocumentUIService, this container is a Window. IDocument is defined in the DevExpress.Xpf.Mvvm library as follows:

   1: public interface IDocument {
   2:     void Show();
   3:     void Close(bool force = true);
   4:     bool DestroyOnClose { get; set; }
   5:     object Title { get; set; }
   6:     object Content { get; }
   7: }

We have the following result after running the program.

3.Blog.Services.001

TabbedDocumentUIService

It’s a cool thing that the IDocumentManagerService allows switching UI styles while leaving the View and ViewModel code unchanged.

Let’s modify our application to use the Tabbed MDI UI. All our changes are at the main window code.

   1: <Window x:Class="DocumentServiceTest.TabbedDocumentUIServiceWindow"
   2:         xmlns:View="clr-namespace:DocumentServiceTest.View"
   3:         xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
   4:         xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
   5:         xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking"
   6:         ...>
   7:     <Grid>
   8:         <dxdo:DockLayoutManager>
   9:             <dxdo:LayoutGroup Orientation="Vertical">
  10:                 <dxdo:LayoutPanel Caption="Navigation" ItemHeight="Auto">
  11:                     <View:MainView>
  12:                         <dxmvvm:Interaction.Behaviors>
  13:                             <dxdo:TabbedDocumentUIService 
  14:                                 DocumentGroup="{Binding ElementName=documnetGroup}"/>
  15:                         </dxmvvm:Interaction.Behaviors>
  16:                     </View:MainView>
  17:                 </dxdo:LayoutPanel>
  18:                 <dxdo:DocumentGroup x:Name="documnetGroup" 
  19:                     Caption="Documents" ItemHeight="*"/>
  20:             </dxdo:LayoutGroup>
  21:         </dxdo:DockLayoutManager>
  22:     </Grid>
  23: </Window>

 

3.Blog.Services.002

 

CODE EXAMPLE: Download an example of these two services available from the DevExpress documentation: http://documentation.devexpress.com/#WPF/CustomDocument15745.

In our upcoming 13.2, we’ll introduce the new IDocumentManagerService implementation for building a modern Windows 8 UI.

Thank you for your time. Feel free to sound off with your thoughts in the comments below.

6 comment(s)
Manuel Grundner [DevExpress MVP]

Great post, great idea.

>>If several solutions already exist, why would DevExpress offer yet another MVVM framework?

>>The answer is, the better part of our customers use the MVVM pattern (including third party MVVM frameworks); we have a clear picture of the challenging scenarios in MVVM development. We can address issues solely within our components, but sometimes issues are related to the MVVM framework.

>>With an MVVM library of our own, MVVM capabilities are exposed at the component level.The end-result is a holistic solution for a well-designed MVVM application whose parts fit together perfectly.

However, i think DevExpress should think more about supporting external frameworks and do not implement their own "master frameworks". For me that seams like DevExpress is doing something wrong? Not using standards? Following paths that nobody can follow, without being bound to DevExpress?

>>The answer is, the better part of our customers use the MVVM pattern (including third party MVVM frameworks); we have a clear picture of the challenging scenarios in MVVM development.

Means for me you have to reduce costs. That is absolute okay. But nothing in the current blog series is something that will cause an issue with (for example prism). Writing your own services and providers will not help developers to avoid pitfalls. Seems to me like reinventing the squared wheel?

In your last post (community.devexpress.com/.../devexpress-mvvm-framework-introduction-to-services-dxmessageboxservice-and-dialogservice.aspx) you said it is easy to unit-test. Please focus on that. This is the hardest part dealing with DevExpress code. Thats what the MVVM pattern is about, testing the ViewModel Code!

Please don't get me wrong :) I like DevEx-Controls, and you do a HUGE amout of work to make things easier for us.

But sometimes i think, only (we) choosed DevExpress (WinForms, XAF, no WPF so far) i have to forget everything i know, and start to train from (zero)?

Greetings, Manuel

Ps.: See this as an open letter and suggestion, not a customer complaint :)

17 October, 2013
Alexander (DevExpress Support)

Hi Manuel,

Every DevExpress component can be used with any third-party frameworks. Moreover, you can use the DevExpress MVVM Framework with any external framework simultaneously and in any proportion. The DX MVVM Framework is just an implementation of our vision of MVVM. In fact, it is not related to our components and may be used separately. By creating our own framework, we tried to take into account all our MVVM experience.

I myself have some experience with PRISM and I like this framework, because it provides great capabilities. However, I also see certain negative features in it. If you also have experience with PRISM, I am sure you agree with me that it is hard to debug an application based on the PRISM framework. Any small issue causes unclear exceptions. Another thing about PRISM is that it appears to be rather complex. This is what made us develop our own framework. I don't want to say that PRISM is bad and DevExpress MVVM is good, but I see weak points of each framework. A weak point of the DX MVVM Framework is that now it does not have many composition capabilities as compared with PRISM. This is what should be improved.

What I said in the first post:

>>With an MVVM library of our own, MVVM capabilities are exposed at the component level.

It should be considered separately. We just have the opportunity to further facilitate the use of our components together with the DevEpxress MVVM framework, and we use this opportunity. An example of such an improvement can be the TabbedDocumentUIService service mentioned at the end of this article.

>>Writing your own services and providers will not help developers to avoid pitfalls. Seems to me like reinventing the squared wheel?

Manuel, bethink the attached behavior pattern. This pattern provides the capability to create isolated code that improves different view parts. Services are nothing more than a subset of the attached behavior mechanism. Services are constructed in the same manner as any attached behavior, but using services, you automatically get the capability to use and control the created attached behavior functionality in the view model part.

As you can see, we already created several ready-to-use services. However, in some cases you may need to create your own service.

>>In your last post (community.devexpress.com/.../devexpress-mvvm-framework-introduction-to-services-dxmessageboxservice-and-dialogservice.aspx) you said it is easy to unit-test. Please focus on that. This is the hardest part dealing with DevExpress code. Thats what the MVVM pattern is about, testing the ViewModel Code!

Okay, Manual. I am glad to hear that this information is useful. I will try to keep it coming.

Thank you for your feedback. It is very important for us to hear your thoughts about our job. I will be looking forward to your response when I publish the next post in this blog series :-)

21 October, 2013
Prabodha WIjerathne

Hi,

I'm trying to load user control to a DocPanel.

I know how to do it using "TabbedDocumentUIService". But I do not need my UserControlls to be loaded as tabs.

How can I load UserControl to the MainWindow.xaml without tabs.

Thanks.

26 November, 2014
Alexander (DevExpress Support)

Hello,

I apologize for the delayed response. In such cases, I would like to ask you to create a ticket in our Support Center. Your question is technical and requires creating a sample project, which I cannot provide you in a blog comment. So, please contact our support team. We will be happy to assist you.

1 December, 2014
Rudy CS

I want to close the dialog from view model.

There is a timer and after some period of time and no response from user, the dialog automatically closes itself.

Could you tell me how to do it?

Thanks

2 April, 2015
Michael Ch (DevExpress Support)

Hello Rudy,

We apologize for the delayed response. Technically, it's impossible to provide you with a detailed answer and corresponding sample project here. 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). We will be happy to assist you.

21 April, 2015

Please login or register to post comments.