View Discovery Composition QuickStart
The View Discovery Composition QuickStart illustrates how to use the new view discovery approach for user interface (UI) composition. When using this approach, you specify the views and the region where the views will be loaded. When a region is created, it asks for its associated views and automatically loads them.
Business Scenario
The View Discovery Composition QuickStart is based on a fictitious resource management system. The main window represents a subset of a larger system. In this window, the user can review detailed information about employees of a company. Figure 1 illustrates the QuickStart's main window.
Figure 1
View Discovery Composition QuickStart — WPF version
Building and Running the QuickStart
The QuickStart ships as source code—this means you must compile it before you run it. This QuickStart does not have any prerequisites.
To build and run the QuickStart
- In Visual Studio, open the solution file QuickStarts\UI Composition\ViewDiscovery\ViewDiscoveryComposition.sln.
- Make sure the desired version of the QuickStart is set as the startup project. If it is not, right-click the desired project in Solution Explorer, and then click Set as Startup Project:
- To build and run the Windows Presentation Foundation (WPF) version of the QuickStart, the startup project should be the UIComposition.Desktop project in the Desktop solution folder.
- To build and run the Silverlight version of the QuickStart, the startup project should be the UIComposition.Silverlight project in the Silverlight solution folder.
- On the Build menu, click Rebuild Solution.
- Press F5 to run the QuickStart.
Walkthrough
The following procedure provides steps to explore the business scenario in the View Discovery Composition QuickStart.
To explore the business scenario
In Visual Studio, open the solution file QuickStarts\UI Composition\ViewDiscovery\ViewDiscoveryComposition.sln.
Make sure the desired version of the QuickStart is set as the startup project. For more information about this, see "Building and Running the QuickStart" earlier in this topic.
On the Build menu, click Rebuild Solution.
Press F5 to run the QuickStart. The main window shows a list of employees, as illustrated in Figure 2.
Figure 2
QuickStart main window — Silverlight version
Select an employee. This dynamically loads a view that shows the employee details, as shown in Figure 3.
Figure 3
The General tab for a selected employee
Explore the different tabs. Each tab provides additional information about the employee. Figure 4 shows the Current Projects tab.
Note
The Silverlight version of this QuickStart does not have a Location tab because Silverlight does not have a Frame control or equivalent to display a Web page, unlike WPF.
Figure 4
The Current Projects tab for a selected employee
Implementation Notes
The QuickStart highlights the key implementation details of an application that uses regions, using the view discovery approach to composition. Figure 5 illustrates the key artifacts in the application.
Figure 5
QuickStart conceptual view for view discovery composition
The following artifacts are illustrated in Figure 5:
- Shell window. This is the application's main window. This window contains the main region.
- Employees view. This view is the main view of the Employees module. It hosts the selection region, where the Employees List view is loaded, and the Details region, where instances of the Employees Details view are loaded.
- Employees List view. This view displays a list of employees.
- Employees Details view. This view displays detailed information for an employee and contains a Tab region.
- Projects List view. This view displays the list of projects an employee is working on.
- RegionViewRegistry. This class is responsible for maintaining a list of regions with their associated views.
- Employee module and Project module. These modules register their views on the RegionViewRegistry.
Workflow
The following is the typical workflow that occurs in view discovery composition, as illustrated in Figure 5:
Each module registers its views and the region where these views will be loaded on the RegionViewRegistry. Typically, this is done in the Initialize method of the module.
In the QuickStart, the Employee module registers the (“MainRegion”, EmployeesView) and (“SelectionRegion”, EmployeesListView) pairs, and the Project module registers the (“TabRegion”, ProjectListView) in its Initialize method.
Note
To register a view in a region, that region does not have to already exist. For example, in this QuickStart, the TabRegion does not currently exist because it is part of the EmployeesDetailsView view.
When a region is created, it queries the RegionViewRegistry to obtain the views associated with it. In addition, the region subscribes to the registry so that when a new view is registered for that region, it is notified and the view is loaded in it.
The RegionViewRegistry returns the views associated to the region; these views are pulled and shown inside the region.
Additionally, if a new view is registered for a region that already exists, the region is notified and the view is pulled and shown in the region.
Note
By design, a view is discovered and pulled into the target region independently of the region's visibility.
View Discovery Strategy
The view discovery approach allows pulling views inside regions, based on a registry where modules store a collection of pairs (such as views type, region name). This registry is named the RegionViewRegistry. When a region is created, it looks for all the view types associated with its region name in the RegionViewRegistry. The matching views are created and pulled into the region. When using this approach, the region instance does not have to be found explicitly by name to create the view and inject it into the region.
Typically, views that host other views have context that needs to be available to its child views. For example, if you have a view to select an employee to show its details, dynamically loaded child views probably need to know which employee is currently selected.
View Discovery Approach vs. View Injection Approach
The following are some points to consider when deciding which approach you should use on different situations:
The view discovery model does not have timing issues. For example a module can try to add a view to a region that might not be created yet.
It is simpler to show multiple instances of the same region because you do not need to know about scoped region managers to find the specific region instance to inject your views into.
You could query the RegionViewRegistry class using the GetContents method to get all the views associated with a particular region. For example, this list can be bound to a menu.
In view discovery composition, a region is populated as soon as it gets added to the visual tree, you have less control over when views are added to a particular region. If you want to load a view at a certain point in time it will be difficult to achieve with view discovery composition.
In the QuickStart, the DetailsRegion region will get the EmployeeDetails view pushed in. The view injection model was used here to show the usage of scoped regions. When you select an employee, a new EmployeeDetailsView view gets created if needed and pushed in the DetailsRegion region by the EmployeeController controller.You should not use view discovery composition if you need scoped region managers, such as to have multiple instances of the same view that contains a region at the same time. Because a region gets registered with a region manager, the name has to be unique.
In the QuickStart, several instances of the EmployeeDetailsView view are created and kept in memory when selecting employees from the list. Each view has its own TabRegion region, so each view needs its own region manager.
When a new TabRegion region gets created, it will pull a new instance of the ProjectsListView view registered in the Project module and display it.
RegionViewRegistry
The RegionViewRegistry class is responsible of registering and retrieving the (region name, view type) pairs.
Typically, modules register their views in their Initialize method using a RegionViewRegistry instance. The following code shows the RegisterViewsWithRegions method, which is called from the Initialize method of the EmployeeModule module (located at Modules/UIComposition.Modules.Employee.Desktop).
public void Initialize()
{
this.RegisterTypesAndServices();
RegisterViewsWithRegions();
}
private void RegisterViewsWithRegions()
{
// Method 1:
this.regionManager.RegisterViewWithRegion(RegionNames.MainRegion,
() => this.container.Resolve<EmployeesPresenter>().View);
// Method 2:
this.regionViewRegistry.RegisterViewWithRegion(RegionNames.SelectionRegion
, () => this.container.Resolve<EmployeesListPresenter>().View);
}
The RegisterViewWithRegion method of the RegionViewRegistry class is used to register the region name with its associated view in the registry. As seen in the preceding code, there are two ways to access this method:
From the RegionViewRegistry directly.
From a RegionManager instance, because this is an extension method of that class for easy access.
Note
This extension method is on the RegionManager for easy access, but it does not register the view with that instance of the region manager only. When a region with the specified name is created, regardless in which scoped region manager is registered, the view will be pulled into it.
The RegisterViewWithRegion method has two overloads:
- RegisterViewWithRegion(string regionName, Type viewType);
- RegisterViewWithRegion(string regionName, Func<object> getContentDelegate);
If you want to register a view directly, use the first overload. If you want to provide a delegate, for example to resolve the presenter that is responsible for creating the view in a "presenter first" approach, as seen in the RegisterViewsWithRegions method, use the second overload.
When a region is created, it looks for its associated views in the registry. The matching views are pulled and loaded inside the region. If the first overload is used, a new instance of the view is created using the Service Locator.
RegionContext
The RegionContext attached property is useful when you want to share context from a parent view that hosts a region to its child views. This attached property can hold any simple or complex object.
In the View Discovery Composition QuickStart, the RegionContext is used to pass the selected employee ID to the ProjectListView view to obtain the projects the selected employee worked on.
The following code, located in the EmployeesDetailsView.xaml file, shows how the RegionContext attached property is used in XAML.
<TabControl AutomationProperties.AutomationId="DetailsTabControl" cal:RegionManager.RegionName="{x:Static local:RegionNames.TabRegion}"
cal:RegionManager.RegionContext="{Binding Path=SelectedEmployee.EmployeeId}"
Style="{DynamicResource SimpleTabControl}" Width="Auto" Height="Auto" Margin="0,5,0,0" HorizontalAlignment="Stretch">
To obtain the RegionContext in a view, the GetObservableContext static method of the RegionContext class is used; it passes the view as a parameter and accesses its Value property, as shown in the following code.
private void RegionContextChanged(object sender, PropertyChangedEventArgs e)
{
this.Model.EmployeeId = (int)RegionContext.GetObservableContext(this).Value;
}
The value of the RegionContext can be changed by simply assigning a new value to its Value property. You can also subscribe to an event to detect when the RegionContext property changes, as shown in the following code, which subscribes its PropertyChanged event to the RegionContextChanged event handler.
public ProjectsListView()
{
InitializeComponent();
RegionContext.GetObservableContext(this).PropertyChanged += this.RegionContextChanged;
}
Note
The DataContext property is not used to share context because the DataContext property is typically used for storing the ViewModel of the view.
Acceptance Tests
The View Discovery Composition QuickStart includes a separate solution with acceptance tests. The acceptance tests describe how the application should perform when you follow a series of steps; you can use the acceptance tests to explore the functional behavior of the application in a variety of scenarios.
Some acceptance tests were developed using the testing framework White. To run these tests, you need to download the White assemblies. For more information about White, including download information, see White on CodePlex.
Note
The acceptance tests have been developed and verified with the White 0.1.5.0 release. Although other releases of White might work, it is recommended to use this release to avoid any issues when running the tests.
To run the View Discovery Composition QuickStart acceptance tests
- Place the assemblies required by White in the folder Source\Lib\White. The files are the following:
- Bricks.dll
- Bricks.RuntimeFramework.dll
- Castle.Core.dll
- Castle.DynamicProxy2.dll
- Core.dll
- log4net.config
- log4net.dll
- nunit.framework.dll
- White.NUnit.dll
- Xstream.Core.dll
- In Visual Studio, open the solution file Quickstarts\Visual Composition\VisualComposition.Tests.AcceptanceTest\ VisualComposition.Tests.AcceptanceTest.
- Right-click VisualComposition.Tests.AcceptanceTest, and then click Set as StartUp Project.
- Press F5.
Outcome
You should see the QuickStart window and the tests automatically interact with the application. At the end of the test pass, you should see that all tests have passed.
More Information
To learn about other QuickStarts included with the Composite Application Guidance, see the following topics: