DevExpress MVVM Framework. ViewInjectionService.

WPF Team Blog
15 May 2015

In this blog post, we’ll review a new service provided by the DevExpress MVVM Framework: ViewInjectionService. This service allows you to integrate any ViewModel (with its View) to any control. ViewInjectionService was inspired by concepts found in PRISM. The primary benefit of this new service is that it offers a common and straightforward mechanism to control ViewModels and associated interactions.

All the features described herein are available in both the free and commercial versions of the DevExpress MVVM framework (included in the DevExpress WPF Subscription).

ViewInjectionService

ViewInjectionService provides a core set of methods and properties that allows you to integrate ViewModels (with their Views) to a component. The recommended way to use ViewInjectionService is to work with its ViewInjectionManager.

ViewInjectionService can work with a large number of controls. The following is a list of supported controls (by default):

  • ContentPresenter descendants;
  • ContentControl descendants;
  • Panel descendants;
  • ItemsControl descendants;
  • Selector descendants;
  • TabControl, DXTabControl;
  • FlowLayoutControl;
  • TileLayoutControl;
  • NavBarControl;
  • DocumentGroup;

Note: if you wish to customize an existing injection strategy or implement your own, you can do so easily by writing your own Strategy.

To use ViewInjectionService, add it to the dxmvvm:Interaction.Behaviors collection of a container-control.

   1: <TabControl>
   2:     <dxmvvm:Interaction.Behaviors>
   3:         <dxmvvm:ViewInjectionService/>
   4:     </dxmvvm:Interaction.Behaviors>
   5: </TabControl>

To directly integrate a ViewModel, you can use one of the base ViewInjectionServiceExtensions’s Inject extension methods. There are tree overloads of the Inject method:

  • Inject(object key, object viewModel)
  • Inject(object key, object viewModel, string viewName)
  • Inject(object key, object viewModel, Type viewType)

ViewInjectionManager

The advantage of ViewInjectionManager is that you can access its properties and methods from any place in your application’s code. To work with multiple regions, ViewInjectionManager uses region identifiers assigned to controls via the RegionName property.

   1: <TabControl>
   2:     <dxmvvm:Interaction.Behaviors>
   3:         <dxmvvm:ViewInjectionService RegionName="{x:Static common:Regions.Main}"/>
   4:     </dxmvvm:Interaction.Behaviors>
   5: </TabControl>

Injecting via the manager is performed by using ViewInjectionManagerExtensions's Inject() extension methods.

  • Inject(string regionName, object key, Func<object> viewModelFactory); - adds a ViewModel provided by the viewModelFactory parameter to the control’s ItemsSource (if you’re using an items control) or Content (if you’re using an content control) property and uses the view passed via ItemTemplate or ContentTemplate.
  • Inject(string regionName, object key, Func<object> viewModelFactory, string viewName); - adds a ViewModel provided by the viewModelFactory parameter to the control’s ItemsSource (if you’re using an items control) or Content (if you’re using a content control) property and the specified view via ViewLocator.
  • Inject(string regionName, object key, Func<object> viewModelFactory, Type viewType); - adds a ViewModel provided by the viewModelFactory parameter to the control’s ItemsSource (if you’re using an items control) or Content (if you’re using a content control) property and the specified view via ViewLocator.

The following parameters are common to all:

  • regionName – specifies the region identifier;
  • key – specifies the identifier of the View and its ViewModel
  • viewModelFactory – ViewModel factory that is invoked when its region is shown
  • viewName or viewType – parameters that specify the view that will be created via ViewLocator.

You can integrate the required elements anywhere within your application, disregarding where and when you show views (even from App.xaml.cs).

The Navigate method allows you to navigate between Views by key, even if they are not yet built.

  • Navigate(string regionName, object key);

To control when ViewModel (with its View) became active, inactive or closed, ViewInjectionManager provides the following methods.

  • RegisterNavigatedEventHandler – specifies the event handler that invoked when View with its ViewModel have successfully navigated to a screen.
  • RegisterNavigatedAwayEventHandler specifies the event handler that invoked when View with its ViewModel have successfully navigated away from a screen.
  • RegisterViewModelClosingEventHandler – specifies the event handler that invoked when View with its ViewModel is about to close.

To unsubscribe you can use the corresponding UnregisterNavigatedAwayEventHandler, UnregisterNavigatedEventHandler, UnregisterViewModelClosingEventHandler methods. However, unsubscribing from these events is not necessarily, because it does not produce memory leaks.

Let’s consider the following example. Assume that you have several views with view models (CustomersView/CustomersViewModel, ProductsView/ ProductsViewModel and SalesView/SalesViewModel) that have to be shown in an items control.

To accomplish this task, place the items control (suppose, it’s a FlipView control) on MainView and add ViewInjectionService to the control’s dxmvvm:Interaction.Behaviors collection as usual.

   1: <UserControl x:Class="DXSample.View.MainView"
   2:     ...
   3:     xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
   4:     xmlns:dxwui="http://schemas.devexpress.com/winfx/2008/xaml/windowsui"
   5:     xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
   6:     xmlns:common="clr-namespace:DXSample.Common"
   7:     xmlns:v="clr-namespace:DXSample.View">
   8:     <Grid>
   9:         <dx:LoadingDecorator Grid.Row="1" >
  10:             <dxwui:FlipView>
  11:                 <dxmvvm:Interaction.Behaviors>
  12:                     <dxmvvm:ViewInjectionService RegionName="{x:Static common:Regions.Main}"/>
  13:                 </dxmvvm:Interaction.Behaviors>
  14:             </dxwui:FlipView>
  15:         </dx:LoadingDecorator>
  16:     </Grid>
  17: </UserControl>

Inject our views with view models by using ViewInjectionManager’s Inject. The following examples uses the Startup event handler:

   1: public partial class App : Application {
   2:     private void Application_Startup(object sender, StartupEventArgs e) {
   3:         InitModules();
   4:         ...
   5:     }
   6:  
   7:     private void InitModules() {
   8:         ViewInjectionManager.Default.Inject(
   9:             Regions.Main,
  10:             ModuleType.Customers,
  11:             () => CustomersViewModel.Create(),
  12:             typeof(CustomersView)
  13:         );
  14:  
  15:         ViewInjectionManager.Default.Inject(
  16:             Regions.Main,
  17:             ModuleType.Sales,
  18:             () => SalesViewModel.Create(),
  19:             typeof(SalesView)
  20:         );
  21:  
  22:         ViewInjectionManager.Default.Inject(
  23:             Regions.Main,
  24:             ModuleType.Products,
  25:             () => ProductsViewModel.Create(),
  26:             typeof(ProductsView)
  27:         );
  28:  
  29:         ViewInjectionManager.Default.Navigate(Regions.Main, ModuleType.Customers);
  30:     }
  31: }

Where the Regions class is a static class with static string properties:

   1: public static class Regions {
   2:     public static string Main { get { return "MainRegion"; } }
   3:     public static string Navigation { get { return "NavigationRegion"; } }    
   4: }

ModuleType is an enumeration:

   1: public enum ModuleType { 
   2:     Customers, 
   3:     Sales, 
   4:     Products 
   5: }

This code is enough to populate the FlipView and produce a working application.

image1

To make the application’s UI more user friendly, let’s add a navigation view to our MainView.

   1: <UserControl x:Class="DXSample.View.MainView"
   2:
   3:     xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
   4:     xmlns:dxwui="http://schemas.devexpress.com/winfx/2008/xaml/windowsui"
   5:     xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
   6:     xmlns:common="clr-namespace:DXSample.Common"
   7:     xmlns:v="clr-namespace:DXSample.View">
   8:     <Grid>
   9:         <Grid.RowDefinitions>
  10:             <RowDefinition Height="Auto"/>
  11:             <RowDefinition Height="*"/>
  12:         </Grid.RowDefinitions>
  13:         <dxui:TileBar Padding="10" >
  14:             <dxmvvm:Interaction.Behaviors>
  15:                 <dxmvvm:ViewInjectionService RegionName="{x:Static common:Regions.Navigation}" />
  16:             </dxmvvm:Interaction.Behaviors>
  17:         </dxui:TileBar>
  18:         <dx:LoadingDecorator Grid.Row="1">
  19:             <dxwui:FlipView>
  20:                 <dxmvvm:Interaction.Behaviors>
  21:                     <dxmvvm:ViewInjectionService RegionName="{x:Static common:Regions.Main}"/>
  22:                 </dxmvvm:Interaction.Behaviors>
  23:             </dxwui:FlipView>
  24:         </dx:LoadingDecorator>
  25:     </Grid>
  26: </UserControl>

The next step is to inject items to the TileBar.

   1: public partial class App : Application {
   2:     private void Application_Startup(object sender, StartupEventArgs e) {
   3:         InitModules();
   4:
   5:     }
   6:  
   7:     private void InitModules() {
   8:         ViewInjectionManager.Default.Inject(
   9:             Regions.Navigation, 
  10:             ModuleType.Customers, 
  11:             () => NavigationItemViewModel.Create(…), 
  12:             typeof(NavigationItemView)
  13:         );
  14:         ViewInjectionManager.Default.Inject(
  15:             Regions.Navigation, 
  16:             ModuleType.Sales, 
  17:             () => NavigationItemViewModel.Create(…), 
  18:             typeof(NavigationItemView)
  19:         );
  20:         ViewInjectionManager.Default.Inject(
  21:             Regions.Navigation, 
  22:             ModuleType.Products, 
  23:             () => NavigationItemViewModel.Create(…), 
  24:             typeof(NavigationItemView)
  25:         );
  26:
  27:         ViewInjectionManager.Default.Navigate(Regions.Navigation, ModuleType.Customers);
  28:
  29:     }
  30: }

Our sample is almost ready. We only need to synchronize the TileBar and FlipView, so they display the same element. We’ll use RegisterNavigatedEventHandler to achieve this:

   1: public class CustomersViewModel {
   2:
   3:     protected CustomersViewModel() {
   4:
   5:         ViewInjectionManager.Default.RegisterNavigatedEventHandler(this, () => {
   6:             ViewInjectionManager.Default.Navigate(Regions.Navigation, ModuleType.Customers);
   7:         });
   8:     }
   9:
  10: }
  11: public class NavigationItemViewModel {   
  12:
  13:     protected NavigationItemViewModel() {
  14:         ViewInjectionManager.Default.RegisterNavigatedEventHandler(this, () => {
  15:             ViewInjectionManager.Default.Navigate(Regions.Main, ModuleType);
  16:         });
  17:     }
  18:
  19: }

image2

An example that illustrates use of ViewInjectionService can be found here.

Should you have any questions or need additional assistance feel free to share your comment with us. We’d love to hear what you think.

Tags
2 comment(s)
Brian Jenkins 2
Brian Jenkins 2

I'm using this with a DXTabControl.

I call Inject and I get the view place in the control fine.

ViewInjectionManager.Default.Inject(Regions.MainRegion,key, TabViewModel.CreateNewTabViewModel, typeof(TabView));

But then I try to add another to the control and I get errors.

Additional information: Cannot register services with the same RegionName

How do I inject another view of the same type into that tab region?

7 July, 2015
Michael Ch (DevExpress Support)
Michael Ch (DevExpress Support)

Hello Brian,

As I can see, you have created a corresponding ticket in our SC: T264332. Please refer to that thread for further discussion.

Thanks,

Michael

8 July, 2015

Please login or register to post comments.