View Creation Mechanisms
- 8 minutes to read
Some services provide the capability to display child Views (like IDialogService, IDocumentManagerService, etc.). Usually, these services are derived from the ViewServiceBase class. This class provides three properties that can be set in XAML: ViewTemplate, ViewTemplateSelector, and ViewLocator. All services derived from the ViewServiceBase class support these properties and provide three approaches of creating child Views.
- ViewTemplate and ViewTemplateSelector, tightly-coupled View Models
- ViewLocator, tightly-coupled View Models
- ViewLocator, loosely-coupled View Models
- Summary
Each creation mechanism is used in a specific application architecture. You can find more information about architecture types of MVVM applications in the following documentation topic: Interaction of ViewModels.
In this article, you will review all the approaches on the DialogService example. This service allows you to display child Views in a modal window. Please review the following documentation article about the DialogService: DialogService.
ViewTemplate and ViewTemplateSelector, tightly-coupled View Models
This approach is usually used in the tightly-coupled View Models architecture, in which View Models may have direct links to other View Models and create them. The DialogService documentation topic contains a tutorial and an example that demonstrate the ViewTemplate and ViewTemplateSelector view creation approach. In that example, a child View is defined in XAML.
<UserControl x:Class="Example.View.ChildView" ...
d:DataContext="{dxmvvm:ViewModelSource ViewModel:ChildViewModel}">
...
</UserControl>
<UserControl x:Class="Example.View.MainView" ...
xmlns:ViewModel="clr-namespace:Example.ViewModel"
xmlns:View="clr-namespace:Example.View"
xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
DataContext="{dxmvvm:ViewModelSource ViewModel:MainViewModel}">
<dxmvvm:Interaction.Behaviors>
<dx:DialogService ...>
<dx:DialogService.ViewTemplate>
<DataTemplate>
<View:ChildView/>
</DataTemplate>
</dx:DialogService.ViewTemplate>
</dx:DialogService>
</dxmvvm:Interaction.Behaviors>
...
</UserControl>
The ChildViewModel is created from the MainViewModel and passed as a parameter to the DialogService.
public class MainViewModel {
...
ChildViewModel childViewModel = new ChildViewModel();
DialogService.ShowDialog(
dialogCommands: new List<UICommand>() { ... },
title: "Child View",
viewModel: childViewModel,
);
...
}
In this case, the ChildView is created based on the defined DialogService.ViewTemplate, and the passed ChildViewModel is assigned to the DataContext of the created ChildView.
Alternatively, a View can be dynamically generated from the passed ViewModel. The DialogService.ViewTemplateSelector property supports this approach. The code below demonstrates it.
public class ChildViewTemplateSelector : DataTemplateSelector {
public DataTemplate ChildViewTemplate { get; set; }
public DataTemplate DefaultViewTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
if(item is ChildViewModel)
return ChildViewTemplate;
return DefaultViewTemplate;
}
}
<Common:ChildViewTemplateSelector x:Key="childViewTemplateSelector">
<Common:ChildViewTemplateSelector.ChildViewTemplate>
<DataTemplate>
<View:ChildView/>
</DataTemplate>
</Common:ChildViewTemplateSelector.ChildViewTemplate>
<Common:ChildViewTemplateSelector.DefaultViewTemplate>
<DataTemplate>
<TextBlock Text="Default Template"/>
</DataTemplate>
</Common:ChildViewTemplateSelector.DefaultViewTemplate>
</Common:ChildViewTemplateSelector>
<dx:DialogService ViewTemplateSelector="{StaticResource childViewTemplateSelector}" />
This approach is useful if you want to show different Views with different View Models using only one service. Alternatively, you can place several services with different names. Please review the following articles for more details.
Services in ViewModelBase descendants
ViewLocator, tightly-coupled View Models
The ViewLocator provides a simple composition mechanism that for creating child Views by names.
<UserControl x:Class="Example.View.ChildView" ...
d:DataContext="{dxmvvm:ViewModelSource ViewModel:ChildViewModel}">
...
</UserControl>
<UserControl x:Class="Example.View.MainView" ...>
<dxmvvm:Interaction.Behaviors>
<dx:DialogService/>
</dxmvvm:Interaction.Behaviors>
...
</UserControl>
public class MainViewModel {
...
ChildViewModel childViewModel = new ChildViewModel();
DialogService.ShowDialog(
dialogCommands: new List<UICommand>() { ... },
title: "Child View",
documentType: "ChildView",
viewModel: childViewModel,
);
...
}
The preceding code implicitly calls the default ViewLocator to create the ChildView based on the passed document type parameter. Since, the ChildViewModel is created from the MainViewModel, you can set any parameter of the created ChildViewModel, and then pass it to the specified ChildView through the DialogService. This approach also implies a tightly-coupled View Model architecture.
The ViewServiceBase class provides the ViewLocator property. By default, this property is null, which means that the service uses the default ViewLocator (ViewLocator.Default). If the default ViewLocator implementation does not meet your requirements, you can change the default ViewLocator (by setting the ViewLocator.Default property) or define a specific ViewLocator at the service level (by setting the ViewServiceBase.ViewLocator property).
ViewLocator, loosely-coupled View Models
If you use the loosely-coupled architecture, you can create child Views, without passing View Models to the child Views.
This approach involves defining the ChildViewModel in the ChildView XAML.
<UserControl x:Class="Example.View.ChildView" ...
DataContext="{dxmvvm:ViewModelSource ViewModel:ChildViewModel}">
...
</UserControl>
<UserControl x:Class="Example.View.MainView" ...>
<dxmvvm:Interaction.Behaviors>
<dx:DialogService/>
</dxmvvm:Interaction.Behaviors>
...
</UserControl>
public class MainViewModel {
...
DialogService.ShowDialog(
dialogCommands: new List<UICommand>() { ... },
title: "Child View",
documentType: "ChildView",
parameter: "Parameter",
parentViewModel: this,
);
...
}
In the case above, the ChildView is created based on the document type parameter. The ChildViewModel is not passed through the DialogService, because it is already defined in the ChildView XAML.
To implement interaction between View Models, you can pass a parameter to the ChildViewModel through the ISupportParameter interface. See the Passing data between ViewModels (ISupportParameter) article for more details.
The last parameter in the DialogService.ShowDialog invocation is a parent view model. With this parameter, you can pass the main View Model to the child one to allow the child View Model to access services defined in the main View Model. See the ViewModel relationships (ISupportParentViewModel) article for more details.
Summary
Services derived from the ViewServiceBase class support an interface in which there is a particular method for creating child Views. This method has the following parameters.
- DocumentType (String) - is used to create a child View through the ViewLocator.
- ViewModel (Object) - a View Model that is passed to the DataContext of the created child View.
- Parameter (Object) - an object that is passed to the parameter of the child View Model (the child View Model should support the ISupportParameter interface).
- ParentViewModel (Object) - an object that is set to the ISupportParentViewModel.ParentViewModel property of the child View Model.
The method that takes all the parameters is usually hidden. Instead, there are several extension methods.
- Method1(object ViewModel) - this method is used when a child View is defined though the ViewServiceBase.ViewTemplate or ViewServiceBase.ViewTemplateSelector property. The child View should not contain a View Model, because it is passed through the service. (ViewTemplate and ViewTemplateSelector, tightly-coupled View Models).
- Method2(string DocumentType, object ViewModel) - this method uses the ViewLocator to create a child View and passes the specified View Model to the created child View. (ViewLocator, tightly-coupled View Models).
- Method3(string DocumentType, object Parameter, object ParentViewModel) - the method creates a child View through the ViewLocator. The child View already contains a View Model. The Parameter and ParentViewModel are passed to the child View Model. (ViewLocator, loosely-coupled View Models).
For instance, the main ShowDialog method in the IDialogService interface is hidden.
public interface IDialogService {
[EditorBrowsable(EditorBrowsableState.Never)]
UICommand ShowDialog(IEnumerable<UICommand> dialogCommands, string title, string documentType, object viewModel, object parameter, object parentViewModel);
}
And there are extension methods for this interface.
public static class DialogServiceExtensions {
public static UICommand ShowDialog(this IDialogService service, IEnumerable<UICommand> dialogCommands, string title, object viewModel);
public static MessageBoxResult ShowDialog(this IDialogService service, MessageBoxButton dialogButtons, string title, object viewModel);
public static UICommand ShowDialog(this IDialogService service, IEnumerable<UICommand> dialogCommands, string title, string documentType, object viewModel);
public static MessageBoxResult ShowDialog(this IDialogService service, MessageBoxButton dialogButtons, string title, string documentType, object viewModel);
public static UICommand ShowDialog(this IDialogService service, IEnumerable<UICommand> dialogCommands, string title, string documentType, object parameter, object parentViewModel);
public static MessageBoxResult ShowDialog(this IDialogService service, MessageBoxButton dialogButtons, string title, string documentType, object parameter, object parentViewModel);
}