Prerequisites
The code covered in this article uses a proprietary control suite from DivElements. You must purchase a license to use the DivElements product or download a 30-day evaluation copy to run the code. You can download SandDock at http://www.divelements.com/.
Introduction
The CompositeUI Application Block (CAB) is the latest offering from Microsoft to aid developers in creating extensible, modular, pluggable applications. If you’re not familiar with CAB, I strongly suggest you stop reading this article and cruise over to MSDN and read through some of the documentation available there. There are also a number of hands on labs that will help you understand what the CAB is all about.
There are a lot of features offered by this application block. I have spent the last three weeks looking over it and am just barely beginning to put the pieces together. The portion of the block covered in this article is Workspaces. The graphics below show my DockableWindowWorkspace
and TabbedDocumentWorkspace
in action.
Workspaces for Dummies
I don't claim to fully understand the concepts behind the CompositeUI Application Block, but here is my sixty second summary of what Workspaces are and how you use them. The Workspace
acts as a container for the SmartPart
views exposed by your WorkItems
. The CAB comes with several built in Workspaces
for showing SmartParts
in Windows, TabControls, Zones, and Decks. The WindowWorkspace
shows each SmartPart
in its own Window. The TabWorkspace
shows each SmartPart
in its own TabPage
. If you are wondering what a SmartPart
and WorkItem
are, that is a pretty good sign you need to go through the CAB hands on labs before reading the rest of this article.
The design of the application block allows a developer to change a Workspace
without breaking the application. This leads to the concept of a "shell" application. The shell application provides one or more Workspaces
and allows dynamically loaded modules to display WorkItems
in those Workspaces
. The shell application can be modified to completely change the way a user interacts with WorkItems
. I will be demonstrating this concept by modifying one of the hands on lab samples to use my custom Workspaces
.
Microsoft has made it fairly painless and straight forward to create custom Workspace
s. One of the hands on labs walks you through creating a custom Workspace
using a TreeView
control. My custom workspaces were created based on the WindowWorkspace
included in the CAB source download.
Workspace Design
Both the DockableWindowWorkspace
and the TabbedDocumentWorkspace
must be constructed with an existing SandDockManager
. Those of you who have used the SandDock controls from DivElements will be familiar with the SandDockManager
. The SandDock
controls allow you to create an interface similar to that provided by Visual Studio, complete with pinnable/collapsible windows that can be rearranged by the user. See the screen shots below for an example of such a UI.
The SandDockManager
is a component that must be added to your shell application form. The code below from BankShellForm.cs shows the creation of the custom Workspaces
, passing the SandDockManager
previously added to the form.
[InjectionConstructor]
public BankShellForm(WorkItem workItem,
IWorkItemTypeCatalogService workItemTypeCatalog)
: this()
{
this.workItem = workItem;
this.workItemTypeCatalog = workItemTypeCatalog;
this.sideBarWorkspace = new DockableWindowWorkspace(this.sandDockManager);
this.contentWorkspace = new TabbedDocumentWorkspace(this.sandDockManager);
this.workItem.Workspaces.Add(sideBarWorkspace, "sideBarWorkspace");
this.workItem.Workspaces.Add(contentWorkspace, "contentWorkspace");
}
Internally, the SandDockManager
does most of the work for us; the custom Workspace
s are just wrappers around the SandDockManager
that implement the IWorkspace
interface.
IWorkspace
exposes methods for showing, hiding, activating, and closing SmartParts
. The custom Workspaces
simply show, hide, activate, and close either a DockableWindow
or a TabbedDocument
associated with the SmartPart
.
Persisting Layouts
The SandDock
controls support the concept of persisting their layout between instances of the application. This means that if your savvy users decide to take time rearranging their Workspace
windows and tabs to their liking, the SandDockManager
will create an XML file that can be saved to the users' disk and loaded the next time they launch the application. The code below from BankShell.cs is a quick and dirty implementation that saves/loads the SandDock
layout to a file called SandDockLayout.txt located in the same directory as the application executable.
private string _ReadLayout()
{
using (FileStream fs = System.IO.File.Open("SandDockLayout.txt",
FileMode.OpenOrCreate, FileAccess.Read))
{
using (StreamReader sr = new StreamReader(fs))
{
return sr.ReadToEnd();
}
}
}
private void _WriteLayout(string layout)
{
using( FileStream fs = System.IO.File.Open("SandDockLayout.txt",
FileMode.Create, FileAccess.Write))
{
using (StreamWriter sw = new StreamWriter(fs))
{
sw.Write(layout);
}
}
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
if (!e.Cancel)
{
_WriteLayout(this.sandDockManager.GetLayout());
}
}
protected override void OnLoad(EventArgs e)
{
string layout = _ReadLayout();
if (!String.IsNullOrEmpty(layout))
{
try
{
this.sandDockManager.SetLayout(layout);
}
catch { }
}
}
This is a great feature to take advantage of but may prove difficult for WorkItems
that are loaded dynamically. To help you implement this feature in your applications, I created the IDockableSmartPart
interface that you can use to mark your SmartParts
that will persists their layout.
IDockableSmartPart
Implementers simply define a Guid
for their SmartPart
that will be used by the SandDockManager
to identify the location and layout of the SmartPart
between uses of the applications. See the example code below from SideBarView.cs:
public partial class SideBarView : UserControl, IDockableSmartPart
{
private static readonly Guid dspGuid =
new Guid("{B69E82F9-5FA2-4309-8FC0-0F421D12F0EB}");
Guid IDockableSmartPart.Guid
{
get { return dspGuid; }
}
}
You can test the BankTeller
application by running the application, moving the SideBarView
to the right side of the screen, close the application, then re-open the application. The SideBarView
should be displayed on the right side of the screen now. Wherever you put the SideBarView
, it should be remembered and reloaded in the correct location each time.
You can use the built in State
and IStatePersistanceProviders
for your WorkItems
to remember their Guids
between uses, and pass those Guids
to your IDockableSmartParts
so they will be loaded and arranged properly by the custom Workspaces.
Unit Testing
I pilfered the unit tests for the TabWorkspace
and WindowWorkspace
that come with the CompositeUI Application Block source code and modified them to suit my needs. If you have nUnit installed on your machine, you can run the GUI interface on the WCPierce.Practices.CompositeUI.WinForms.Test
assembly and see all of the pretty green lights. I added very little to the existing tests so I would say the DockableWindowWorkspace
and TabbedDocumentWorkspace
have about 90% of their features covered by the unit tests.
(UPDATED: alexsantos posted the code to correct this problem. Use the download link above for the new code). One problem I ran into with the TabbedDocumentWorkspace
was properly setting focus to sibling tabs when an existing tab was closed. Those of you with greater experience using the SandDock
controls may be able to solve this. If anyone comes up with a valid solution, please let me know and I will incorporate it into the Workspaces
.
Conclusion
I do want to make it perfectly clear that the code download does not include a dockable/pinnable windowing framework. That amazing functionality is provided by the SandDock
controls from DivElements. The code download does include a nice little wrapper that, when coupled with the amazing work done by the Microsoft Patterns & Practices team, will allow you to create highly dynamic and customizable applications in a modular fashion.
I greatly appreciate comments, ratings, feedback, etc. So if you found this article helpful, or you think it stinks, please let me know via the comments section below.