WPF/CAB Layer

Contents

Overview

This documentation describes the purpose and usage of the WPF/CAB layer provided by the SCSF Contribution project. It assumes the reader has an understanding of the Composite Application Block (CAB) and at least a basic understanding of Windows Presentation Foundation (WPF). Some sections (such as Implementing a Shell) assume you are not using SCSF recipes to generate code. In the case that you are, such sections can be used to gain greater insight into the workings of the WPF/CAB layer.

About the WPF/CAB Layer

The WPF/CAB layer facilitates writing CAB-based applications in a "pure" WPF environment. In other words, it allows developers to write use CAB in their WPF applications without resorting to any form of interop. In addition, it allows existing Windows Forms CAB applications to be migrated to WPF solutions. It is not dependent upon the Windows Forms CAB layer (in the CompositeUI.Winforms assembly) that shipped with the original CAB.

The WPF/CAB layer provides all the core services required of WPF-based CAB applications including activation (ensuring the correct work item is activated based on input focus), builder strategies (to ensure that relevant items are added to work items during build up), command adapters (to translate between CAB commands and WPF elements), UI adapters (to allow hosting of UI elements such as menu items) and workspaces (to allow smart parts to be layed out as required by the application).

The WPF/CAB layer provided by SCSF Contribution supersedes the original implementation. In fact, it is a complete rewrite of the original and concentrates on correctness, usability, and performance.

FAQs

  • How this is related to Composite UI Application Block?
This is an add on for the CAB that allows working with a pure WPF shell as oposed to a Winforms shell.
SCSF 2007 provides integration with WPF through Winforms interop. The shell will still be a Winforms Form. The workspaces and UIExtensionSites will work with Winforms technology as well. However the smartparts that you can host on those workspaces could be either Winforms or WPF. Levereaging SCSF 2007 is recommended in brown field scenarios where you want to migrate an existing Winforms app in a gradual way. It is also recommended when your app still needs support to host Winforms smartpart.
The WPF CAB layer might be used in green field scenarios where NET 3.0 can be used.
Acropolis will be part of the windows platform. The concepts on Acropolis will be very similar to the Composite UI Block, but will differ in implementation. Acropolis might be recommended for long term projects that could span up to the release of Acropolis. For more information read Glenn Block post from the p&p team.
  • Can I build XBAP applications using this WPF/CAB layer?
No, this is not currently possible due to assumptions made by the core CAB implementation. XBAP applications are asynchronous in nature, but CAB's CabApplication implementation currently assumes that the call to Start() will block until the application has exited. I have created this issue to better explain and track this issue.

Developing Applications with the WPF/CAB Layer

Implementing a Shell

CAB applications are hosted inside a shell, which inherits directly or indirectly from the Microsoft.Practices.CompositeUI.CabApplication class. The WPF/CAB layer provides several subclasses of this class as shown in the following diagram:
ApplicationShell.png

WindowsApplication

The abstract WindowsApplication class serves as a base class for the ApplicationShellApplication and WindowShellApplication classes. It adds services and builder strategies that are common to all WPF/CAB applications.

ApplicationShellApplication

The ApplicationShellApplication class should generally be prefered by WPF-based CAB applications over the WindowShellApplication class. The reason for this is that it provides a more natural starting point for an application and requires less code.

In order to start your CAB application via this class, follow these steps (assumes an existing WPF project with appropriate references):
  • Open the App.xaml.cs file and modify the declaration of the class to implement the IInitializeApplication interface:
public partial class App : System.Windows.Application, SCSFContrib.CompositeUI.WPF.IInitializeApplication
{
}
  • If necessary, add an implementation of the InitializeComponent method:
public partial class App : System.Windows.Application, SCSFContrib.CompositeUI.WPF.IInitializeApplication
{
	public void InitializeComponent()
	{
	}
}

IMPORTANT: This step is only necessary in the (rare) circumstance that the compiler does not generate an InitializeComponent method automatically. You should never implement this interface explicitly, unless your explicit implementation invokes the generated InitializeComponent method.
  • Add a shell implementation class called, for example, Shell:
public sealed class Shell : SCSFContrib.CompositeUI.WPF.ApplicationShellApplication<Microsoft.Practices.CompositeUI.WorkItem, App>
{
	[STAThread]
	public static void Main()
	{
		new Shell().Run();
	}
}
  • Open the properties for the project and change the Startup object to the Shell class.
  • (Optional) Open the properties for the App.xaml file and change the Build Action from ApplicationDefinition to Page. This will ensure you have only the one entry method defined for your application.
At this point you should be able to run the application and see a window open.

WindowShellApplication

The WindowShellApplication class provides an implementation of CabApplication that uses a WPF Window as a shell. Typically you would use the ApplicationShellApplication in preference to this class. It exists primarily for completeness.

In order to start your CAB application via this class, follow these steps (assumes an existing WPF project with appropriate references):
  • Add a shell implementation class called, for example, Shell:
public sealed class Shell : SCSFContrib.CompositeUI.WPF.WindowShellApplication<Microsoft.Practices.CompositeUI.WorkItem, Window1>
{
	[STAThread]
	public static void Main()
	{
		App app = new App();
		app.InitializeComponent();
		new Shell().Run();
	}
}
  • Open the App.xaml file then remove the StartupUri attribute and add a resource to be referenced from elsewhere in your application:
<Application x:Class="WindowsApplication1.App"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
	<Application.Resources>
		<Style TargetType="{x:Type Window}"/>
	</Application.Resources>
</Application>
  • Open the Window1.xaml file and reference the above resource:
<Window x:Class="WindowsApplication1.Window1"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	Title="WindowsApplication1" Height="300" Width="300"
	Style="{StaticResource {x:Type Window
">
}}
Note that these steps are necessary only to ensure the compiler generates an InitializeComponent method for the Application subclass, which it no longer will do after removing the StartupUri element from the App.xaml file.

Notice the explicit instantiation and initialization of the WPF Application object (of type App) prior to executing the CAB application. This step is unnecessary when using the ApplicationShellApplication class, since the WPF Application object will be instantiated and initialized automatically in that case.
  • Open the properties for the project and change the Startup object to the Shell class.
At this point you should be able to run the application and see a window open.

Workspaces

The WPF/CAB layer provides several implementations of CAB's IWorkspace interface that allow you to define the layout of your smart parts within your application. These workspaces are fully-fledged WPF controls and do not use any Winforms interop. As such, they are support WPF concepts such as dependency properties, styling etc. Here is a class diagram showing the relevant workspace types:
Workspaces.png

WindowWorkspace

The WindowWorkspace class provides an implementation of IWorkspace that allows smart parts to be displayed inside a WPF Window. This class works in conjunction with the WindowSmartPartInfo class, which provides a way for the various properties of the containing window to be configured. It also provides a way to determine the dialog result when showing modal windows.

A smart part can be displayed via a WindowWorkspace as follows (all these examples assume that an instance of WindowWorkspace has already been created and is referenced by the _windowWorkspace field):
MySmartPart mySmartPart = SmartParts.AddNew<MySmartPart>();
_windowWorkspace.Show(mySmartPart);

The above code will cause the smart part to display in a default Window instance as follows (I resized the window to make the screenshot smaller):
WindowWorkspaceDefault.png
By using the WindowSmartPartInfo class, the Window hosting the smart part can be customized. For example:
MySmartPart mySmartPart = SmartParts.AddNew<MySmartPart>();
WindowSmartPartInfo wspi = new WindowSmartPartInfo();
wspi.Width = 150;
wspi.Height = 65;
wspi.IsModal = true;
wspi.ResizeMode = ResizeMode.NoResize;
wspi.ShowInTaskbar = false;
wspi.Title = "My Smart Part";
wspi.WpfStyle = Application.Current.FindResource("WindowStyle") as Style;
_windowWorkspace.Show(mySmartPart, wspi);

if (wspi.ModalResult.Value)
{
	//dialog was accepted by user (they clicked OK)
}

This code produces the following:
WindowWorkspaceCustomized.png

DeckWorkspace

The DeckWorkspace class provides an implementation of IWorkspace that decks smart parts on top of each other. Only one smart part is visible at any one time, and that smart part is the one on top of the deck. Hiding a smart part will cause it to move to the bottom of the deck. If only one smart part has been added to the DeckWorkspace, hiding it will have no effect.

Showing smart parts in a DeckWorkspace is very simple and there is no SmartPartInfo implementation for DeckWorkspaces. The following XAML shows how a DeckWorkspace can be hosted inside a WPF application:
<Window x:Class="WindowsApplication1.Window1"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:cab="http://schemas.scsfcontrib.com/extensions/wpf/wpfcab" 
	Title="WindowsApplication1">
	<StackPanel>
		<Label>Above DeckWorkspace</Label>
		<cab:DeckWorkspace x:Name="_deckWorkspace"/>
		<Label>Below DeckWorkspace</Label>
	</StackPanel>
</Window>

A smart part can then be displayed in the DeckWorkspace as follows:
MySmartPart mySmartPart = WorkItem.SmartParts.AddNew<MySmartPart>();
_deckWorkspace.Show(mySmartPart);

This results in the following:
DeckWorkspace.png
The following code shows various uses of the DeckWorkspace:
MySmartPart mySmartPart1 = WorkItem.SmartParts.AddNew<MySmartPart>();
MySmartPart mySmartPart2 = WorkItem.SmartParts.AddNew<MySmartPart>();
MySmartPart mySmartPart3 = WorkItem.SmartParts.AddNew<MySmartPart>();
_deckWorkspace.Show(mySmartPart1);
_deckWorkspace.Show(mySmartPart2);
_deckWorkspace.Show(mySmartPart3);

//at this point, mySmartPart3 is visible

_deckWorkspace.Hide(mySmartPart3);

//now mySmartPart2 is visible and mySmartPart3 has moved to the bottom of the deck

_deckWorkspace.Close(mySmartPart2);

//now mySmartPart1 is visible and mySmartPart2 is no longer in the DeckWorkspace at all

_deckWorkspace.Close(mySmartPart1);

//now mySmartPart3 is visible and mySmartPart1 is no longer in the DeckWorkspace at all

_deckWorkspace.Hide(mySmartPart3);

//mySmartPart3 is still visible because it's the only smart part in the DeckWorkspace

_deckWorkspace.Close(mySmartPart3);

//now no smart parts are visible because there are none left in the DeckWorkspace

TabWorkspace

The TabWorkspace class provides an implementation of IWorkspace that displays smart parts in separate TabItems within a WPF TabControl. As users navigate the tabs in the TabControl, the corresponding smart part is activated and deactivated in the TabWorkspace. Hiding a smart part will cause the entire TabItem to be hidden (but still present in the TabControl). Closing a smart part will cause the TabItem to be removed altogether.

The TabWorkspace class works in conjunction with the TabSmartPartInfo class, which provides a way to customize the TabItems created and displayed by the TabWorkspace. The following XAML shows how a TabWorkspace can be hosted inside a WPF application:
<Window x:Class="WindowsApplication1.Window1"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:cab="http://schemas.scsfcontrib.com/extensions/wpf/wpfcab" 
	Title="WindowsApplication1">
	<StackPanel>
		<Label>Above TabWorkspace</Label>
		<cab:TabWorkspace x:Name="_tabWorkspace"/>
		<Label>Below TabWorkspace</Label>
	</StackPanel>
</Window>

A smart part can then be displayed in the TabWorkspace as follows:
MySmartPart mySmartPart = WorkItem.SmartParts.AddNew<MySmartPart>();
_tabWorkspace.Show(mySmartPart);

This results in the following:
TabWorkspaceDefault.png
The TabItem used to host the smart part can be customized via a TabSmartPartInfo instance as follows:
MySmartPart mySmartPart1 = WorkItem.SmartParts.AddNew<MySmartPart>();
MySmartPart mySmartPart2 = WorkItem.SmartParts.AddNew<MySmartPart>();
TabSmartPartInfo tspi = new TabSmartPartInfo("My First Tab");
_tabWorkspace.Show(mySmartPart1, tspi);
Button button = new Button();
button.Content = "My Second Tab";
tspi = new TabSmartPartInfo(button);
tspi.Activate = false;
_tabWorkspace.Show(mySmartPart2, tspi);

Notice how - true to WPF - any content can be used in the TabItem's header (in this case, a WPF Button instance). This results in the following:
TabWorkspaceCustomized.png

ZoneWorkspace

The ZoneWorkspace class provides an implementation of IWorkspace that displays smart parts in designated zones. It is unique amongst the other workspaces in that more than one smart part may be visible at a time. The active smart part is determined by input focus. That is, placing the input focus on a smart part will ensure that it is active.

The ZoneWorkspace class works in conjunction with the ZoneSmartPartInfo class, which provides a way to specify which zone a smart part should be displayed in. If no zone name is specified, the default zone will be used. If no default zone has been configured for the ZoneWorkspace, a zone name must be specified when showing a smart part.

The following XAML shows how a ZoneWorkspace can be hosted inside a WPF application:
<Window x:Class="WindowsApplication1.Window1"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:cab="http://schemas.scsfcontrib.com/extensions/wpf/wpfcab" 
	Title="WindowsApplication1">
	<StackPanel>
		<Label>Above ZoneWorkspace</Label>
		<cab:ZoneWorkspace x:Name="_zoneWorkspace" DefaultZone="{Binding ElementName=_contentZone}">
			<StackPanel>
				<ContentControl cab:ZoneWorkspace.ZoneName="_headerZone"/>
				<ContentControl x:Name="_contentZone"/>
				<ContentControl cab:ZoneWorkspace.ZoneName="_footerZone"/>
			</StackPanel>
		</cab:ZoneWorkspace>
		<Label>Below ZoneWorkspace</Label>
	</StackPanel>
</Window>

In this case, there are three zones in the ZoneWorkspace: a header zone, a footer zone and a default content zone. Notice how the default zone and zone names have all been specified directly in the XAML; there is no need to configure this in code-behind (of course, you can do so if you prefer). Displaying a smart part in the default zone is as simple as:
_zoneWorkspace.Show("A smart part hosted in the default zone.");

This results in the following:
ZoneWorkspaceDefault.png
An instance of ZoneSmartPartInfo can be used to specify the zone in which a smart part will be placed:
_zoneWorkspace.Show("HEADER", new ZoneSmartPartInfo("_headerZone"));
_zoneWorkspace.Show("A smart part hosted in the default zone.");
_zoneWorkspace.Show("FOOTER", new ZoneSmartPartInfo("_footerZone"));

This results in the following:
ZoneWorkspaceCustomized.png
Note that each zone in the ZoneWorkspace acts like a deck (see DeckWorkspace). That is, you can show multiple smart parts in the same zone but only the one top-most in the deck will be visible. Hiding a smart part will send it to the bottom of the deck.

ContentControlWorkspace

The ContentControlWorkspace is an abstract base class for any WPF workspaces that inherit directly from WPF's ContentControl class. It deserves a mention because it greatly simplifies the task of implementing a workspace in such a case. Both the DeckWorkspace and ZoneWorkspace classes inherit from this class.

UI Adapters

CAB UI adapters facilitate shared application regions such as menu bars, toolbars, status bars etc. The WPF/CAB layer provides a couple of implementations of CAB's IUIElementAdapter interface.

ItemsControlUIAdapter

The ItemsControlUIAdapter class adapts an instance of WPF's ItemsControl class (or a subclass thereof) for CAB applications. As such, it can be used to turn any ItemsControl into an extension point in your application. For example, WPF's MenuItem, ToolBar, StatusBar and TreeViewItem classes are all ItemsControls and can therefore be adapted by the ItemsControlUIAdapter class.

Suppose you have the following XAML:
<Menu>
	<MenuItem x:Name="_fileMenuItem" Header="_File"/>
</Menu>

This code registers the File menu item as an extension point:
WorkItem.UIExtensionSites.RegisterSite(UIExtensionSiteNames.File, _fileMenuItem);

The following code adds an item to the File extension point:
MenuItem menuItem = new MenuItem();
menuItem.Header = "Some menu item";
WorkItem.UIExtensionSites[UIExtensionSiteNames.File].Add(menuItem);

ItemsControlChildUIAdapter

The ItemsControlChildUIAdapter class adapts an instance of DependencyObject that resides directly inside an ItemsControl. An example use case is adapting Separator instances that are hosted in a MenuItem. This provides an easy mechanism to group commands.

Here's an example. Suppose you have the following XAML:
<Menu>
	<MenuItem Header="_File">
		<MenuItem Header="New"/>
		<MenuItem Header="Open"/>
		<MenuItem Header="Save"/>
		<Separator x:Name="_filePrintSeparator"/>
		<MenuItem Header="Page Setup"/>
		<MenuItem Header="Print"/>
		<Separator x:Name="_filePropertiesSeparator"/>
		<MenuItem Header="Properties"/>
	</MenuItem>
</Menu>

The following code registers the two separators as distinct UI extensibility points:
WorkItem.UIExtensionSites.RegisterSite(UIExtensionSiteNames.FilePrint, _filePrintSeparator);
WorkItem.UIExtensionSites.RegisterSite(UIExtensionSiteNames.FileProperties, _filePropertiesSeparator);

Now it is possible for code to easily add commands to the correct location within the file menu. For example:
MenuItem menuItem1 = new MenuItem();
menuItem1.Header = "Some print command";
WorkItem.UIExtensionSites[UIExtensionSiteNames.FilePrint].Add(menuItem1);

MenuItem menuItem2 = new MenuItem();
menuItem2.Header = "Some properties command";
WorkItem.UIExtensionSites[UIExtensionSiteNames.FileProperties].Add(menuItem2);

The Visualizer

The CAB visualizer is a developer tool that can be used to view the internal workings of your CAB applications as they run. The WPF/CAB layer provides a visualizer window that can be used to host any number of visualizations. To enable the visualizer window, add the following to your App.config file:
<configSections>
	<section name="CompositeUI"
		type="Microsoft.Practices.CompositeUI.Configuration.SettingsSection, Microsoft.Practices.CompositeUI"
		allowExeDefinition="MachineToLocalUser" />
</configSections>
<CompositeUI>
	<visualizer>
		<!-- example for the WPF Visualizer (split across lines only for formatting purposes) -->
		<add type="SCSFContrib.CompositeUI.Visualizer.WpfVisualizer, SCSFContrib.CompositeUI,
			Version=1.0.0.0, Culture=neutral, PublicKeyToken=64f7747366518fca">
	</visualizer>
</CompositeUI>

For each visualization that you want displayed, add another <add/> element. See the CAB documentation for information on how to write visualizations.

Last edited Feb 25, 2008 at 7:25 PM by kentcb, version 20

Comments

makaveli_0000 Apr 27, 2008 at 1:44 PM 
Good work, thank you..