Introduction
Part 15 of this series of articles explained SmartParts in the CAB, and gave a very brief introduction to Workspaces. This article will continue with the same topics. In particular we will look in more detail at Workspaces.
Workspaces Recap
Part 15 explained the SmartParts can be most easily thought of as user controls or views in a composite application block application.
Part 15 also explained that Workspaces are just controls that allow other controls (SmartParts) to be laid out within them, and differ from other container controls in that they interact with other objects in the CAB in a predefined way. In particular there is a Workspaces collection on any WorkItem, and they have a Show method that shows a SmartPart.
User Interface Design for Business Applications
In an earlier blog posting on user interface design I discussed some of the possible user interface designs for applications that have to display multiple different screens of data. A common design for these applications is to have a tabbed client area for the screens, together with a tree-based window manager of some kind. An alternative is an Outlook-style interface, again possibly with a tree window manager, but dispensing with tabs at the top of the screen. Clearly both of these designs are very similar: we click on a tab or double-click in a tree and our screen gets displayed in a predefined part of the screen.
One thing to notice with both of these designs is that after we have displayed a client screen for the first time we won’t usually close it again. When other screens are displayed our first screen will stay in memory. It will retain the state it was displaying the last time the user interacted with it. When the user requests to see the screen again it will just be brought to the front.
So we have a collection of objects (our screens) with persistent state whilst the application is running. We may create all of these screens at start-up. However more usually we’ll create a screen the first time the user requests it, and then redisplay it the second time it is requested (a sort of lazy initialization). Note that at any given time many of the screens in the collection may not actually be displayed at all.
Building User Interfaces for Business Applications
Of course it is easy enough to create an application of this kind using Windows Forms standard controls. However, there’s some infrastructure code that we are going to have to write. We need:
- some means of caching our screens collection
- a way of allowing new screens to be added to the screens collection
- a method of displaying a screen when it is requested for the first time
- a method of redisplaying the appropriate screen in response to the appropriate request
We’ll also want to make sure we control the scope of our screens carefully so that they can’t be accessed from anywhere in the code. As we all know, global singletons (or global variables of any kind) are a bad thing.
The good news is that Workspaces and SmartParts do all this for us. What’s more they do it in a very simple way.
Workspaces – the IWorkspace Interface
All Workspaces implement the IWorkspace interface. This is the interface we are meant to use to interact with our Workspaces: usually we don’t need any more functionality than this interface provides as we shall see.
The IWorkspace interface has a number of useful methods. In general these behave as explained below, although each type of Workspace is free to implement them in its own way:
void Show(object smartPart)
When this method is called, if the SmartPart passed as a parameter has not previously been shown in this Workspace it will be loaded and then activated. If it has already been shown and not been closed (i.e. if it is in the Workspace’s SmartParts collection) it will just be activated. Usually ‘activated’ means brought to the front and made into the ActiveSmartPart.
void Activate(object smartPart)
If the object is in the Workspace’s SmartParts collection it will be activated as described above. If it’s not in that collection then an ArgumentException will be thrown.
void Hide(object smartPart)
Hides the SmartPart (sends it to the back usually), but leaves it in the Workspace’s SmartParts collection. The next control in the Workspace will usually be activated (made into the ActiveSmartPart). What ‘next’ means here varies by Workspace.
void Close(object smartPart)
Hides the SmartPart and also removes it from the SmartParts collection. The next control in the Workspace will usually be activated.
IWorkspace also has a property ActiveSmartPart, which returns the active SmartPart if there is one. As discussed in part 15 Workspaces have a SmartParts collection, and the IWorkspace interface has a SmartParts property that lets us access this (it returns a ReadOnlyCollection<object>).
IWorkspace also has events SmartPartActivated and SmartPartClosing. You can probably guess when these get fired.
There are also some methods to deal with SmartPartInfo on IWorkspace. I’ll discuss SmartPartInfo below.
Workspaces – Aside on how the Supporting Classes Work
The core functionality of IWorkspace is encapsulated in a class called, appropriately, Workspace. This is an abstract base class that all Workspaces are expected to inherit from. It implements IWorkspace so the individual Workspace classes don’t have to (since they inherit the interface from Workspace).
The base class gives basic implementations of the members of IWorkspace, and also handles things like caching the active smart part, tracking the SmartParts collection, and raising the right events at the right time.
Whilst the way the supporting classes work is quite interesting, you don’t need to know it in any detail to use the Workspace classes. The exception to this is if you want to write your own custom Workspace class, in which case you will need to get to grips with them in more detail. Writing your own custom Workspaces is beyond the scope of these articles for now.
Further Aside on Multiple Inheritance in C#
The Workspace classes also demonstrate a method of achieving a kind of multiple inheritance in C#. This is done by composition, using a class called WorkspaceComposer and an interface called IComposableWorkspace. It’s needed because certain Workspace classes (e.g. TabWorkspace) already inherit a Control class to give their basic functionality (in the case of TabWorkspace this is TabControl). As we know, in .NET a class can’t directly inherit more than one other class.
It is not my intention in these articles to actually discuss the underlying code in the Composite Application Block in any detail. The aim of these articles is to let you use the CAB as a black box component. But the WorkspaceComposer is particularly interesting since it shows us a pattern for achieving multiple inheritance (albeit messily) in C#. If you are interested the code is in the Microsoft.Practice.CompositeUI.SmartParts namespace in the Microsoft.Practices.CompositeUI component.
SmartPartInfo
As we have seen the normal Show method on the IWorkspace interface only lets you pass the SmartPart you want to show as a parameter. There may be other information that the CAB framework will need to know to show your SmartPart correctly. For example, in a TabWorkspace you need to provide the text for a new tab somehow. In a ZoneWorkspace you need to tell the CAB which particular zone the SmartPart will be displayed in.
This problem is solved by passing an additional object to the Show method:
void Show(object smartPart, ISmartPartInfo smartPartInfo)
This additional object implements the interface ISmartPartInfo, which has Title and Description properties. It’s the Title property that’s used to set the tab page title in the TabWorkspace.
The smartPartInfo object can contain any additional information we require. For example, for ZoneWorkspaces it contains the name of the zone in which the SmartPart will be displayed.
The IWorkspace interface also has a method to directly apply the information in a SmartPartInfo object to a SmartPart that has already been displayed:
void ApplySmartPartInfo(object smartPart, ISmartPartInfo smartPartInfo)
In a ZoneWorkspace this can be used to change the zone where a SmartPart is shown in, for example. The next article in this series will give some examples of this.
Conclusion
This article has discussed some of the theory behind Workspaces, and shown the basic interfaces. Part 17 in this series of articles will discuss the various types of Workspace that are available in the CAB, and give some code examples of how to use them.