Rich Newman

October 27, 2007

Introduction to SmartParts and Workspaces (Introduction to CAB/SCSF Part 15)

Introduction

Part 14 of this series of articles discussed UIExtensionSites. This article continues our discussion of user interface elements in the Composite Application Block by giving an introduction to SmartParts, and a very brief introduction to Workspaces. Part 16 will expand on this discussion. Workspaces and SmartParts are interlinked and to understand one you need some understanding of the other.

Why do we need Workspaces and SmartParts?

We don’t have to use any of the new user interface elements described in this article. We can create a composite application as described in previous articles using just the existing Form class, UserControl class and the various visual components found in the standard toolbox in Visual Studio. Part 1 and part 5 of this series of articles showed how we can construct a very basic multiple document interface (MDI) application comprising three independent modules. Those articles didn’t use any of the CAB user interface components.

Many introductory texts on the Composite Application block start with Workspaces and SmartParts. This series of articles has left them until close to the end. This is because I personally found SmartParts very confusing (and still do to a certain extent), and because we don’t need to use them to get the benefits of the Composite Application Block. Workspaces are more useful, as we shall see.

SmartParts

‘SmartParts’ were first introduced in the original CAB code back in 2005. As mentioned above the concept can be confusing. SmartParts are most easily thought of as user controls in a CAB application. In fact they can be any visual element low-level element in your CAB user interface. They can, for example, be child forms in an MDI application.

However, in almost every case you will come across SmartParts will be user controls (including the normal children in CAB MDI applications).

Microsoft’s CAB documentation defines a SmartPart as ‘a view of data (in the MVC pattern) such as a control, a Windows Form, or a wizard page’. This is slightly misleading as SmartParts don’t necessarily have to display any data, and don’t have to be in an MVC pattern.

SmartParts Collections

In part 2 of this series of articles we saw that a WorkItem can be thought of as a ‘run-time container of components’ and that the WorkItem class has various collection classes associated with it (e.g. Items, Services, WorkItems). One of these collection classes is the SmartParts collection. As we shall see later in this article, another collection class on a WorkItem is the Workspaces collection.

So perhaps a better definition of a SmartPart is as any object that is put into the SmartParts collection in a WorkItem in the CAB.

However, even this definition is confused by the fact that the Workspaces collection itself has a SmartParts collection. This means there are two SmartParts collections at different places in any WorkItem. This article will call these ‘WorkItem SmartParts’ and ‘Workspace SmartParts’. The two collections behave in different ways, which are discussed below. Before that we’ll take a quick look at what a Workspace actually is. This brief exposition will be expanded on in part 16.

Workspaces

Microsoft’s CAB documentation defines Workspaces as:

“The components that encapsulate a particular visual layout of controls and SmartParts, such as within tabbed pages.”

Workspaces are themselves controls that allow other controls to be laid out within them. In particular they are designed to allow SmartParts to be laid out within them.

Several Workspace types are available in the Visual Studio toolbox and can just be dragged onto our screens. In this sense they are like the existing layout controls in the .NET Framework (e.g. the TabControl).

Where Workspaces differ from existing layout controls is in the fact that they are part of and utilize the CAB dependency injection containers (Workitems). As with SmartParts, WorkItems have a ‘Workspaces’ collection which, obviously, contains all the Workspaces associated with the WorkItem.

We can create a Workspace in our Workspaces collection in the usual way with the AddNew keyword. However, we don’t need to do this if we’ve just dragged our Workspace onto a screen: the ObjectBuilder will recognize it as a Workspace when it is created, and add it to the appropriate collections.

Workspaces also interact with SmartParts in a standard way: they have a Show method that will display a SmartPart, an ActiveSmartPart property that gets a reference to the active SmartPart in the Workspace, and so on. These methods will be examined in more detail in part 16 of this series of articles.

SmartParts Collections: WorkItem SmartParts

We look now at the SmartParts collections in more detail. To show the concepts involved there is a code example available.

The SmartParts collection in a WorkItem is just a filter on the Items collection, filtering for any Items where the underlying class is decorated with the attribute ‘SmartPart’. If we have an object whose class is decorated with the SmartPart attribute and add it directly to either the Items collection or the SmartParts collection it will appear in both. However, if we try adding an object NOT decorated with the attribute to the SmartParts collection it will get added to the Items collection only.

The underlying type of our SmartPart doesn’t matter for this . It can be any class at all. As long as that class is decorated with ‘SmartPart’ it will behave as described above. The intention is that objects added to this collection be visual ones (user controls, forms etc.) but they don’t have to be.

Example of WorkItem SmartParts

This is illustrated in the code example. Here we have a user control, UserControl1, that is decorated with the SmartPart attribute:

    [SmartPart]
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }
    }

In the AfterShellCreated method we add this to the SmartParts collection of the RootWorkItem:

RootWorkItem.SmartParts.AddNew<UserControl1>();

We then examine what effect that has on the various collection classes on the RootWorkItem. We can see that the UserControl1 object is in both the SmartParts collection and the Items collection as we would expect (see below for the results output).

Similarly we define a plain old .NET class and also decorate that with the SmartPart attribute:

    [SmartPart]
    internal class TestClass {}

We then just add that to the Items collection of the RootWorkItem in AfterShellCreated:

RootWorkItem.Items.AddNew<TestClass>();

If we now look at the RootWorkItem collection classes we see that the TestClass object is in both the SmartParts collection and the Items collection in exactly the same way as the UserControl instance above. It is a ‘SmartPart’ even though it has no visual element.

The full output of the relevant collections from this example is shown below under ‘Output from SmartParts Example’.

SmartParts Collections: Workspace SmartParts

As discussed above, the second SmartParts collection is on a Workspace. An object gets added to this collection if we call the Show method on the Workspace with the object as the parameter.

For the Workspace SmartParts collection it doesn’t matter whether the object’s class is decorated with the ‘SmartPart’ attribute or not: it gets added to the Workspace SmartParts collection when the Show method of the Workspace is called regardless.

Note that the Show method doesn’t add the object into the Items collection of the WorkItem, nor does it add the object into the WorkItem SmartParts collection.

We’ll examine how Workspaces work, and how they interact with SmartParts, in more detail in part 16 of this series of articles. In particular we’ll look at why the Show method is a useful one, and we’ll examine how SmartPartInfo classes work and why we need them. For now you can just accept that this second collection exists and can be useful.

Issues with Workspace SmartParts

One issue here is that objects can only be added to the SmartParts collection of a Workspace if they inherit System.Windows.Forms.Control at some level. That is, in this case our SmartParts are forced to be visual controls.

Also you can’t add SmartParts directly to the SmartParts collection of a Workspace. The only way to add an object to this collection is to use the Show method of the Workspace (I think). Note that they don’t get removed from this collection just because you call Show with a different SmartPart.

You were warned that this is confusing.

Example of Workspace SmartParts

The code example discussed above has been extended to show how Workspace SmartParts work. We have added a DeckWorkspace (one of the more common Workspace classes) to our Shell form simply by dragging it from the Toolbox. We’ve also added another user control class to the project, UserControl2.

We then display the user control in the Workspace by calling the Show method:

this.Shell.deckWorkspace1.Show(new UserControl2());

This has the effect of adding UserControl2 to the SmartParts collection of the Workspace. However, UserControl2 is NOT added to the Items collection of the WorkItem, nor to the SmartParts collection of the WorkItem.

In the example above we defined a plain old .NET class and added it to the WorkItem’s SmartParts collection. We can’t do this with the Workspace’s SmartParts collection because this collection will only accept objects that inherit from Control at some level.

One thing to note here is that UserControl2 is decorated with the SmartPart attribute, but this makes no difference at all to the example. We can remove it and the example will work in the same way. This is because this attribute is not relevant for the SmartParts collection on a Workspace.

Output from SmartParts Example

The SmartParts example uses some simple looping code to output the collections created to the Output window. The results of this are shown below. As we can see, UserControl1 and TestClass (discussed in the section ‘Example of WorkItem SmartParts’ above) appear in the Items collection, and in the SmartParts collection at RootWorkItem level. However, UserControl2 (from the section ‘Example of Workspace SmartParts’) appears ONLY in the SmartParts collection on the Workspace. This is what we’d expect as discussed above.

ITEMS:
[d506f65b-e818-4379-b15a-5e53bfe7777f, Microsoft.Practices.CompositeUI.State]
[f4d17dcd-13cc-4d61-b24b-0b3cd03c88a8, Shell.Form1, Text: Form1]
[deckWorkspace1, Microsoft.Practices.CompositeUI.WinForms.DeckWorkspace]
[1d141251-3436-4a8a-8bd3-1e63ceca3d9e, Shell.UserControl1]
[632482f5-aaa3-490b-b800-3575bea33a06, Shell.TestClass]
SMARTPARTS:
[1d141251-3436-4a8a-8bd3-1e63ceca3d9e, Shell.UserControl1]
[632482f5-aaa3-490b-b800-3575bea33a06, Shell.TestClass]
WORKSPACES:
[deckWorkspace1, Microsoft.Practices.CompositeUI.WinForms.DeckWorkspace]
       SMARTPARTS:
       Shell.UserControl2

The SmartPart Attribute

Another source of confusion with SmartParts is what exactly the SmartPart attribute does. Several texts mention that it doesn’t seem to make much difference whether it’s there or not, and this is true to an extent with the Workspace SmartParts collection.

In fact there are (at least) two places where the SmartPart attribute makes a difference:

  1. As we’ve already seen any object decorated with the SmartPart attribute that is added to either the Items or the SmartParts collection of a WorkItem will effectively be added to both collections. Any object without the SmartPart attribute added to either collection will end up in the Items collection only. Remember that the SmartParts collection of a WorkItem is really just a filter on the Items collection for any objects that have the SmartPart attribute.
  2. The SmartPart attribute also makes a difference if we add a Control to the Controls collection of the Shell, or to the Controls collection of a Control on the Shell, when the Shell is being initialized (in its constructor). In this case the ObjectBuilder recognizes that we have a WorkItem SmartPart. It adds the SmartPart to the Items collection of the WorkItem and hence to the SmartParts collection (since it has the SmartPart attribute).

This is still confusing, but the behaviour in 2 is there so that if you drag a User Control with the SmartPart attribute onto the Shell from the Toolbox then it will get added to the SmartParts collection of the root WorkItem at start up.

Example of Effects of SmartPart Attribute on User Controls Dragged on to the Shell Form

Some example code showing this is available. This has a user control with the SmartPart attribute applied (SmartPartUserControl) and a user control with no SmartPart attribute (NormalUserControl). Both of these have been dragged onto the Shell form (Form1).

Additionally in the constructor of the Shell form we manually instantiate a SmartPartUserControl and add it to the Form’s Controls collection:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            SmartPartUserControl smartPartUserControl = new SmartPartUserControl();
            smartPartUserControl.Name = "Manually instantiated user control";
            this.Controls.Add(smartPartUserControl);
        }
    }

That’s all the code that’s been added. Once again the code dumps the various relevant WorkItem collections in AfterShellCreated by using simple loops outputting to the Output window:

ITEMS:
[fa70dbe9-0c30-443e-9fe6-a13e353994e0, Microsoft.Practices.CompositeUI.State]
[d67d7177-cf63-4b4f-9447-ef2fd1e8271b, Shell.Form1, Text: Form1]
[userControl11, Shell.SmartPartUserControl]
[deckWorkspace1, Microsoft.Practices.CompositeUI.WinForms.DeckWorkspace]
[Manually instantiated user control, Shell.SmartPartUserControl]
SMARTPARTS:
[userControl11, Shell.SmartPartUserControl]
[Manually instantiated user control, Shell.SmartPartUserControl]
WORKSPACES:
[deckWorkspace1, Microsoft.Practices.CompositeUI.WinForms.DeckWorkspace]
       SMARTPARTS:

As we can see, both SmartPartUserControls have been added to both the Items and WorkItem SmartParts collections, whilst the NormalUserControl does not appear (although we can see it on the Shell fine: it just hasn’t been added to these collections). This is what we’d expect from the discussions above.

Summary of the SmartParts Collections and their Intention

The intention of the Workspace SmartParts is to give us (and the CAB Framework) a collection of SmartParts that have been shown in a Workspace but not closed. These can be re-activated (effectively brought to the front). We can show Controls that are not marked with the SmartPart attribute in a Workspace (and hence arguably aren’t really SmartParts). However this collection needs to track everything that has been shown, and so it all ends up in this collection.

The intention of the WorkItem SmartParts is to give us a collection of SmartParts that we as developers can control and use. Here we do want everything in the collection to have the SmartPart attribute: this is what distinguishes SmartParts from ordinary Items as far as this collection is concerned.

Workspace SmartParts have to be Controls (i.e. inherit from System.Windows.Forms.Control at some level). WorkItem SmartParts can be any object.

My personal opinion is that it would have been less confusing if the CAB developers had given the SmartParts collection on the Workspace a different name: ‘ViewsShown’ or something similar might have been better.

Conclusion

This article gave a brief introduction to SmartParts and showed why they can be confusing. It also gave an even briefer introduction to Workspaces. Part 16 of this series of articles will examine Workspaces in more detail, and expand on how we can use SmartParts in our code. It will also look at the SmartPartInfo class.

October 26, 2007

User Interface Design for Business Applications

Introduction

This article is going to give a quick tour of the various high-level user interface designs for business applications that need to display multiple windows. It will discuss multiple document interface (MDI), single document interface (SDI) and other paradigms for handling multiple windows. The article will illustrate these concepts by looking at the user interfaces in Microsoft’s various desktop applications, both good and bad.

This article will be referred to by my series of articles on the CAB and SCSF but is not part of that series.

Business Applications

Before we start we need to consider what we mean by business applications. For the purposes of this discussion I mean applications that have some or all of the following characteristics:

  • display of data, often in grid form
  • some means of interrogating the data (querying, sorting, filtering, drilling down)
  • calculations based on the data, comparison of data (reconciliations), and display of results
  • some kind of data entry and persistence
  • data updates from other applications, maybe in real time
  • exports to Excel or paper or other formats
  • feeds to other systems

I work in banking, and would say that all the applications we build fall into this category, with the possible exception of internet-based applications for clients. Such applications may or may not be classic online transaction processing systems (OLTP).

In general these applications do not need to catch the user’s attention in competition with other applications, unlike internet-based consumer applications. As a result they largely will not use multimedia effects (video, animation, graphics). They are intended to be functional above all else.

Having said that such applications often do need to have a rich and responsive user experience. It is still hard to do that with HTML-based interfaces, even with the advent of Ajax. Often these applications are solely for internal use within an organization, where there will be a consistent desktop computer base with appropriate security. As a result deployment of smart client applications becomes possible and these applications are often built as smart clients.

High-level Design of User Interfaces for Business Applications

In this article I’m going to illustrate the various possible high-level designs for business applications using Microsoft’s own selection of applications. Whilst these are not really ‘business applications’ in the sense I have described above, they do illustrate the possible designs.

The difficulty that we are trying to address here is that our business applications will typically need to show many different screens with many different types of data in them. We may want to show more than one screen simultaneously, and may even allow data to be dragged between screens (although it’s arguable that this is one user interface paradigm that should not be used). There usually needs to be some central way of managing the various screens (a window manager), as well as some means of transitioning between them. Above all it’s critical that the user can easily navigate the system, and can use it in a flexible way that fits with their requirements.

Multiple Document Interface (MDI)

The ‘classic’ Multiple Document Interface (MDI) design has been with us for many years now, and at one stage was a very common way of handling the user interface problems described above.

A Multiple Document Interface application has a main ‘shell’ MDI window with just a menu bar and possibly a toolbar. The user can load individual screens from these bars, and the screens will just ‘float’ within the window.

Typically these applications have a ‘Window’ menu option, with ‘Tile’ and ‘Cascade’ options that rearrange all open windows. We also usually have a list of open windows on the ‘Window’ menu to allow us to find an individual screen.

There are several applications of this sort still around, even in the Microsoft stable:

access20032.jpg

entmgr2.jpg

One advantage of this sort of user interface design for enterprise applications is that it can be very easy to develop. Every screen is a form, there’s an easy way of creating one directly from a menu click, and window management is simple. If you cache your data on the client and use a simple model-view-controller pattern you can keep the data on the screens up-to-date relatively easily as well.

Of course the major disadvantage is that the user experience isn’t that good. It’s easy to ‘lose’ windows, and as you can see even in my simple screenshots above, windows overlap and what you want to see is often hidden.

It’s for the reasons outlined above that Microsoft has been phasing out this sort of interface from its own products.

Single Document Interface (SDI)

In a Single Document Interface (SDI) application there is only one window in each instance of the application. If you want a second window you start a second complete instance of the application. Switching between windows happens at the operating system level when you switch between applications. In Microsoft Windows this means you that you use the Taskbar to select a different window, as all windows will have an icon in the taskbar.

Microsoft uses this paradigm for Microsoft Word. If you open two documents in Word you get two separate instances of the application:

word2007a2.jpg

This is the default behaviour. It is possible to use Word as a proper ‘classic’ MDI application by changing one if its many options settings: if you click the Office button (top left), click Word Options, go to the Advanced tab and scroll down to the Display section there is a checkbox labelled ‘Show all windows in the Taskbar’. If you clear this Word will have an MDI interface:

word2007b2.jpg

Excel behaves strangely with regard to this. It has the same menu option, but its default behaviour with the ‘Show all windows in the Taskbar’ checkbox checked is different. Here if you open two Excel documents they are still MDI (i.e. they appear in the same window), although they do have two window icons in the Taskbar:
excel2007a2.jpg

If you clear the checkbox all that happens is that you get one icon in the Taskbar instead of two (i.e. there’s almost no difference):

excel2007b2.jpg

SDI does make some sense for Word and Excel where you will typically only have a few spreadsheets or word processing documents open at a time and may want a way to navigate quickly between them. The operating system Taskbar is ideal for this.

However, this sort of interface is usually not applicable to the complex business applications discussed earlier in the article. These applications will have multiple screens with different data being shown, and trying to manage all of those and their interaction through the Taskbar would be extremely difficult.

For this reason it is rare for business applications to be designed using SDI.

Basic Tabbed Document Interfaces (‘TDI’) – Browsers

Many modern desktop applications now use tabbed document interfaces of some kind. Rather than having multiple floating windows that are difficult to control (MDI), or each window in a separate instance of the application (SDI) we allow multiple windows but insist that they are arranged as tabs. This is dubbed ‘TDI’ for ‘Tabbed Document Interface’.

For example, almost all browsers now support different pages being shown on different tabs:

firefox2.jpg

Even Internet Explorer now supports this in version 7, having held out as an SDI application in versions up to that point.

Disadvantages of Basic Tabbed Document Interfaces

There are two major disadvantages of pure tabbed interfaces for business applications:

  1. An obvious disadvantage of this design in its purest form is that it isn’t possible to display two windows alongside each other, and thus is difficult to arrange for drag and drop between them. Browsers typically get around this problem by allowing a new document to be shown in a new tab or a new window, thus creating a mixed TDI/SDI model. This is probably not an appropriate solution for business applications.
  2. Another disadvantage for the kind of business applications we are talking about here is that we may need to support a very large number of screens (windows) being open simultaneously. A simple tabbed arrangement can make it very difficult for a user to find the window they are after. On our current application we had to abandon all use of tabs for this reason (although we were also using a window manager, see below).

Montage Tabbed Interfaces – ‘IDE-style’ interfaces

Microsoft has recently moved some of its old ‘classic’ MDI applications into a tabbed format, but with surrounding property and window management panes:

access20072.jpg

sqlmgtstudioa2.jpg

Note that both of the screenshots above are of updated versions of the MDI applications we saw in the ‘classic’ MDI section above. They have the advantage over those older interfaces that we are less likely to ‘lose’ a window and usually won’t need to use a Window menu to navigate. The layout is tidier too.

Note also that SQL Server Management Studio solves problem 1 mentioned above in the section ‘Disadvantages of Basic Tabbed Document Interfaces’ by allowing the central tabbed area to be split horizontally or vertically into multiple ‘tab groups’:

sqlmgtstudiob2.jpg


Tree-Based Window Managers

These applications also show for the first time the now common approach for doing window management in modern user interfaces. We have a tree view in a pane to either the right or left of our main document window. This tree organizes our documents in a logical hierarchy, meaning we can find the document we are after fairly easily. Clicking or double-clicking in the tree will open the document or bring it to the front if it is already open. This is a big step forward from a Window menu that just listed open documents in a random order. However, the old MDI version of SQL Server Enterprise Manager also had a tree-based window manager, so it’s not a new idea.

These window managers mitigate problem 2 mentioned above in the section ‘Disadvantages of Basic Tabbed Document Interfaces’: we still have the problem that we may have a very large number of tabs open, but at least we have a tree that will let us go to the one we need.

Integrated Development Environment Interfaces

Of course both of the ‘Montage Tabbed Document Interface’ applications shown above resemble the older integrated development environment interfaces all developers are now familiar with from such applications as Visual Studio and Eclipse:

visstudio2.jpg

As we all know, this user interface is highly customizable. We can display tabbed documents alongside each other (as shown above) and drag between them. We can ‘tear off’ any of the surrounding panes and have them float free of the main window. We can easily then dock them to any part of the interface.

Again we have a tree-based window manager in the Solution Explorer pane that allows us to go directly to a tab if we have too many open to easily find the one we need (and we all know that can be a problem).

Disadvantages of the Integrated Development Environment User Interface

This kind of interface is great for an integrated development environment; it’s hard to find a developer who doesn’t like Eclipse or Visual Studio (at least now it doesn’t fall over every few minutes). However a user interface this complex may not be appropriate for a business application. The tear-off property windows in Visual Studio in particular are a nice idea, but lead to the old problem of you not necessarily being able to find your window, particularly if you have multiple screens. I doubt many developers actually use this feature: I certainly don’t.

Users of business applications usually don’t need to be able to highly customize the user interface. Indeed allowing them to do so can cause problems if a complex application is used in ways the developers are not expecting. In general it can be better to retain a little more control. It’s certainly harder to program (and hence more expensive to maintain) an application that allows multiple panes to be ‘torn off’ from the main application and kept synchronized with the application.

Montage Interfaces without Tabs – Outlook-style Interfaces

For some of the reasons cited above Outlook-style interfaces are currently very popular for business applications:

outlooka2.jpg

Even if you use this product every day it’s worth thinking about what’s good and bad about the interface.

We have a tree-based window manager, which in the corporate environment is often used to manage quite extensive trees of documents. Emails can be displayed in the main window, as shown, or can be in their own window with a separate icon in the Taskbar, which is useful to be able to find them when you are writing a new one whilst still checking new mail.

There are no tabs here either, other than the collapsible sections themselves, which clearly aren’t quite the same thing as allowing multiple documents to be opened in a tabbed view.

The pane to the left hand side has collapsible sections of course, allowing us to select completely different areas of functionality of the application. This is an attractive way of doing things if we are writing complex business applications. Of course it’s particularly attractive if we are writing a composite (CAB) application as each module can be on a different collapsible section.

outlookb2.jpg

What’s interesting about the screenshot above is that we don’t need a tree window manager for our notes, so Microsoft have used the space to put in a simple set of radio buttons to allow us to change the view. This really isn’t a great use of space, and this is one drawback of this design: for simple areas of functionality you may not need a window manager. You can find yourself trying to invent something useful to put in the Outlook pane when you don’t really need it.

However overall this isn’t a bad starting place for a design for a business application. It’s simple but quite powerful. Many of the tools vendors have realized this and ship components that support such an interface. In fact most of the major vendors seem to have fully mocked up versions of Outlook running using their tools as demonstrations.

Conclusion

This article has looked at some possible high level interface designs for business applications that need to display multiple windows of data, and considered some of the pros and cons of each. For a composite application an Outlook-style interface can be a good starting point.

References

Wikipedia on:
Multiple Document Interfaces
Single Document Interfaces
Tabbed Document Interfaces

Microsoft Design Specifications and Guidelines – Window Management

October 20, 2007

More on UIExtensionSites (Introduction to CAB/SCSF Part 14)

Introduction

Part 13 of this series of articles discussed UIExtensionSites, and gave a basic example of how they work.

This article discusses UIExtensionSites in more detail, including a discussion of their use of the Adapter design pattern.

Whilst most of this article is fairly straightforward, I should say now that I am very sceptical of the value of UIExtensionSites and we have decided not to use them in our current project. I’ll discuss this below.

Additional Functionality of the UIExtensionSites

The basic example in part 13 showed how we can register a ToolStrip as a UIExtensionSite in a WorkItem using the RegisterSite method. We can then access it from other modules via the UIExtensionSites collection of the WorkItem.

In particular we can then add ToolStripButtons to it using the Add method of the UIExtensionSite.

In addition to the Add method, a UIExtensionSite has a Remove method which, clearly, removes an item that has been added to the UIExtensionSite.

A UIExtensionSite is really just a simple collection class behind the scenes (in fact it’s a wrapper around List<object>). Obviously in the case of a ToolStrip it’s a collection of ToolStripButtons.

In addition to the Add and Remove methods, the UIExtensionSite class has some of the usual public methods you’d expect on a collection class: Count, Clear, Contains, and GetEnumerator (which allows ‘foreach’ to be used on the collection, of course).

Types that can be added to a UIExtensionSite

The only types that can be registered as UIExtensionSites without additional work are types that contain ToolStripItems: that is, ToolStrips, MenuStrips and StatusStrips.

It is possible to set up other user interface items so that they can be used as UIExtensionSites. To do this you need to write an adapter class for your user interface items. We’ll examine this in more detail below.

Restrictions of UIExtensionSites

So we have a means of adding a ToolStrip, MenuStrip or StatusStrip to a collection that can be accessed from any module in a Composite Application Block project. We can then add or remove ToolStripItems from the ToolStrip, MenuStrip or StatusStrip. We can clear them as well.

However, that’s it. If we want to do anything more sophisticated we can’t easily. For example, if we have a menu item that can be checked we can’t use these interfaces to find out if it’s checked or not. Equally, if we just want to hide a ToolStrip from code it can’t be done easily.

You might think you could cast the UIExtensionSite back to its original type to get this functionality. So to hide the ToolStrip you might cast the UIExtensionSite to ToolStrip and call the Hide method. Unfortunately this isn’t possible: there isn’t a direct cast to the underlying type. We’ll examine the relationship between ToolStrip and UIExtensionSite in a little more detail below.

Decoupling

So what is the UIExtensionSite giving us? I’m sure the original idea was to decouple our underlying user interface items (ToolStrips etc.) from the way individual modules would access them. By having a generic UIExtensionSite with a generic interface we could plug in any user interface element we wanted, and maybe even swap them around. So if we wanted to use a different ToolStrip to Microsoft’s we could do so: all we’d really need to do would be to ensure the methods in the interface (Add, Remove etc.) worked correctly.

However even this isn’t possible with the current implementation of UIExtensionSite. If you look back at the code in the basic example in part 13 we are explicitly creating a ToolStripButton object in our client code:

            ToolStripButton toolStripButton = new ToolStripButton("Show Red Screen");
            toolStripButton.Click += new System.EventHandler(toolStripButton_Click);
            UIExtensionSite uiExtensionSite = parentWorkItem.UIExtensionSites["ShellToolStrip"];
            uiExtensionSite.Add<ToolStripButton>(toolStripButton);

In this code we have decoupling in the sense that we are handling a generic UIExtensionSite object rather than a specific ToolStrip object. However we are still tightly coupled to Microsoft’s ToolStrip since we’re creating the ToolStripButton. If we wanted to swap out the ToolStrip and replace it with a different one we’d have to change all this code.

As I mentioned in the introduction, I am struggling to understand what UIExtensionSites are giving us: they make it difficult to access core functionality of our ToolStrips etc. without giving us proper decoupling.

How is the Basic Example Working?

In the example in part 13 we added a ToolStrip to the UIExtensionSites collection of the RootWorkItem. We then retrieved the same object from the same collection. However, when we retrieved the object it had type UIExtensionSite. This meant that we then called the Add method of UIExtensionSite to add buttons to the ToolStrip.

You may be wondering how we managed to do this. UIExtensionSite is a class, and clearly ToolStrip doesn’t inherit it. How are we calling Add on UIExtensionSite and getting ToolStripButtons added to a ToolStrip class? The answer to this is that we are using an adapter pattern, as we shall see in more detail below.

Using Other User Interface Elements as UIExtensionSites

As mentioned above we can use other user interface elements than just ToolStrips, MenuStrips and StatusStrips as UIExtensionSites. However, we have to do a little more work to get this going. In fact, we have to write an adapter class for our user interface element. This tells the CAB what to do when the Add or Remove methods are called on the associated UIExtensionSite.

Before we discuss how to do this we’ll quickly recap the Adapter pattern.

The Adapter Design Pattern

The adapter is a pattern that we use when we want a class to implement a standard interface but don’t want to (or can’t) change the actual code of the class. In the case of our UIExtensionSites our user interface elements (e.g. ToolStrips) will have methods for adding and removing individual items (e.g. ToolStripButtons). However we want to be able to use a generic ‘Add’ method on a UIExtensionSite object to add ToolStripButtons. We clearly can’t change the ToolStrip class to implement this, so we use an adapter.

The book ‘Design Patterns: Elements of Reusable Object-Oriented Software’ (the ‘Gang of Four’ book) says the intent of the Adapter pattern is to:

“Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.”

The Adapter class diagram is below:

adapterclassdiagram.jpg

The idea here is that we have some class Adaptee that has a ‘SpecificRequest’ method that the client wants to call. However, the client code uses an Adapter class to invoke the functionality, calling the ‘Request’ method. Adapter is an abstract base class: it’s just defining an interface that the client code will use.

Furthermore we can’t, or don’t want to, change the Adaptee class to actually inherit from Adapter. So instead we write a ConcreteAdapter class. This inherits Adapter, and so has a Request method. It also takes a reference to the Adaptee class (usually in its constructor) and caches it in a data member. This means we can write a very simple implementation of the Request method in our Adapter class that simply forwards the call to the SpecificRequest method on the Adaptee object in the data member.

Now the client code can work with Adapter objects, calling the Request method, but still get the functionality of the SpecificRequest method of the Adaptee.

Of course the Adapter class can do much more work than just forwarding method calls to methods with different names. The Adaptee might need several calls to do the work we want done in the call to Request on the interface, and the Adapter class can clearly deal with that as well.

One consequence of the use of this pattern is that we can’t cast our Adaptee class to Adapter since it doesn’t implement it directly. This explains why in our examples we can’t cast our UIExtensionSite (or the IUIElementAdapter data member it contains) back to our original ToolStrip. As mentioned above this restricts quite severely what we can do with a UIExtensionSite.

How this is Implemented in the CAB for UIExtensionSites

For UIExtensionSites in the Composite Application Block this pattern is made slightly more complex. The Patterns and Practices group have separated out the interface implemented by our Adapter base class into an actual C# interface called IUIElementAdapter which is then implemented by our abstract base class
UIElementAdapter<TUIElement>. You don’t need to worry about the details of this. All you need to know is that to implement your own adapter you need to inherit from UIElementAdapter<TUIElement> where TUIElement is the type of the objects that will be added to or removed from the UIExtensionSite. Inheriting this class compels you to override the abstract Add and Remove methods that it contains.

As you’ve probably realized, the Composite Application Block already contains an adapter class for ToolStrips, MenuStrips and StatusStrips. This is snappily called ‘ToolStripItemCollectionUIAdapter’and is in the CompositeUI.WinForms library.

Custom UIExtensionSite Example

This example will again be based on the Red and Blue form example used in part 13. This time we will add a panel to the left side of the screen that display link labels. Our link labels will bring the appropriate red or blue screen to the front:

linklabelpanelmdiapplication.jpg

The panel will be set up as a UIExtensionSite. It will therefore have an Add method that allows you to add a LinkLabel into the panel, and similarly have a Remove method that allows you to take one out.

The code for this example is available.

LinkLabelPanel User Control

To implement this the first thing we need is a user control to act as the panel for the LinkLabels. This needs to have methods to add and remove labels. To set this up all we have to do is add a UserControl to the Shell project, make its background white, and add methods to handle the LinkLabels as below:

    public partial class LinkLabelPanel : UserControl
    {
        public LinkLabelPanel()
        {
            InitializeComponent();
        }
 
        private const int labelSpacing = 22;
        private int nextLabelTop = 10;
        private const int left = 3;
 
        public void AddLabel(LinkLabel label)
        {
            label.Location = new Point(left, nextLabelTop);
            nextLabelTop += labelSpacing;
            this.Controls.Add(label);
        }
 
        public void RemoveLabel(LinkLabel label)
        {
            this.Controls.Remove(label);
        }
    }

As you can see this keeps track of where the next LinkLabel should be positioned vertically in data member nextLabelTop. We can add a LinkLabel to the panel by calling AddLabel, and this adds it to the Controls collection and positions it appropriately.

Note that the Remove method isn’t particularly sophisticated since it does no repositioning of existing controls: it will just take the LinkLabel it is passed off the screen and leave a gap where it previously was.

The Adapter

To be able to use this as a UIExtensionSite we need an adapter class that inherits UIElementAdapter<LinkLabel> (since LinkLabel is the type that we are adding and removing from the UIExtensionSite). This is a very simple adapter that just forwards the calls to the underlying LinkLabelPanel. It gets a reference to this LinkLabelPanel passed in in its constructor:

    public class LinkLabelPanelUIAdapter : UIElementAdapter<LinkLabel>
    {
        LinkLabelPanel panel;
        public LinkLabelPanelUIAdapter(LinkLabelPanel panel)
        {
            this.panel = panel;
        }
 
        protected override LinkLabel Add(LinkLabel uiElement)
        {
            panel.AddLabel(uiElement);
            return uiElement;
        }
 
        protected override void Remove(LinkLabel uiElement)
        {
            panel.RemoveLabel(uiElement);
        }
    }

Using the LinkLabelPanel

We can now use the LinkLabelPanel in the same way as we did the ToolStrip in the example in part 13.

Firstly we add the user control to our Shell form, dock it to the left, and make its Modifiers property have value ‘Internal’.

We then register the UIExtensionSite in method AfterShellCreated. One slight difference here is that we need to tell the CAB which adapter it needs to use, and we do this by using a different overload of RegisterSite:

        protected override void AfterShellCreated()
        {
            base.AfterShellCreated();
            this.Shell.IsMdiContainer = true;
            RootWorkItem.Items.Add(this.Shell, "Shell");
            RootWorkItem.UIExtensionSites.RegisterSite("LinkLabelPanel", new LinkLabelPanelUIAdapter(this.Shell.linkLabelPanel1));
        }

As mentioned above our adapter needs to have a reference to the LinkLabelPanel passed to it in its constructor.

Now we can add LinkLabels to our UIExtensionSite from our individual Red and Blue module projects. Again this is done in the same way as for the ToolStrip example in part 13. We create a new LinkLabel, retrieve the UIExtensionSite that represents the LinkLabelPanel from the UIExtensionSites collection, and use the Add method to add the LinkLabel to it. We set up the Click event of the LinkLabel using .NET events, again analogously to what we did with the Click event for our ToolStripButtons in part 13:

        private Form1 form;
        public override void Load()
        {
            // Code as before
            base.Load();
            Form shell = (Form)parentWorkItem.Items["Shell"];
            form = new Form1();
            form.MdiParent = shell;
            form.Show();
 
            LinkLabel label = new LinkLabel();
            label.Text = "Show Red Screen";
            label.Click += new System.EventHandler(label_Click);
            UIExtensionSite uiExtensionSite = parentWorkItem.UIExtensionSites["LinkLabelPanel"];
            uiExtensionSite.Add<LinkLabel>(label);
        }
 
        void label_Click(object sender, System.EventArgs e)
        {
            form.BringToFront();
        }

That’s it: if we repeat the code immediately above in both the Red and the Blue projects our example we will work as we desire.

Conclusion

UIExtensionSites allow us to access user interface elements in the shell of a Composite Application Block project in a generic way. The CAB provides us with code (an adapter) to let us use ToolStrips, MenuStrips and StatusStrips as UIExtensionSites. If we wish to use other user interface elements as UIExtensionSites we can quite simply write our own adapter class.

However, UIExtensionSites are quite limited in the functionality they provide, restricting us to just Add and Remove methods for elements of the UIExtensionSite. As a result we may need some more powerful mechanism to provide the rich user interface support we require.

Part 15 of this series of articles will discuss Workspaces and SmartParts.

October 13, 2007

Introduction to UIExtensionSites (Introduction to the CAB/SCSF Part 13)

Introduction

Part 12 of this series of articles went into some detail on events in the Composite Application Block, and concluded our discussions of commands and events.

As we’ve already discussed in this series of articles, the Composite Application Block (CAB) is intended as a means of creating smart client applications that are composed of several independent ‘modules’. We’ve also seen that the CAB has a number of other features to make development easier in general, in particular a dependency injection framework.

However, when we think of a ‘smart client’ we usually think of a well-designed and powerful user interface. The CAB introduces a number of new classes to aid in the design and implementation of such user interfaces.

So far this series of articles has not really discussed user interfaces at all, and certainly hasn’t covered any of the user interface elements of the CAB. The next few articles will remedy this.

UIExtensionSites

This article and the next will discuss one of the CAB user interface classes, the UIExtensionSite. A UIExtensionSite is a user interface element of the shell (the containing window) for a composite application. It might be a ToolStrip, a MenuStrip or a StatusBar, but could be any shared user interface element. The idea of the UIExtensionSite class is to allow each individual module to talk to these elements of the shell in a decoupled way.

In this article we’ll start with a basic example. In fact, we’re going to base the example on an earlier one, which will be recapped first.

Basic Example Starting Point – Recap of the BasicMDIApplication from Part 5

For this example we start with the code for the first example from part 5 of this series of articles, ‘BasicMDIApplication’. This contains three projects, none of which reference each other. However, each project contains one form, and when we run the application two of the forms appear as MDI children of the third. The two MDI children are coloured red and blue respectively.

This was done in a slightly contrived way (it’s not the way you’d do it in production code). In our Shell project in method AfterShellCreated we added the Shell form itself to our RootWorkItem’s Items collection:

        protected override void AfterShellCreated()
        {
            base.AfterShellCreated();
            this.Shell.IsMdiContainer = true;
            RootWorkItem.Items.Add(this.Shell, "Shell");
        }

Then we accessed it from the same collection in each of the other projects. We did this in the Load method of theModuleInit class in each project. There we set the MdiParent property of the project’s form to be the Shell form:

        public override void Load()
        {
            base.Load();
            Form shell = (Form)parentWorkItem.Items["Shell"];
            Form1 form = new Form1();
            form.MdiParent = shell;
            form.Show();
        }

Note that here we had access to the parentWorkItem of the project (which is the same as the RootWorkItem). We got access to this through dependency injection.

The original code is available here.

Basic Example Extended

We’ll extend this so that the Shell form has a ToolStrip with two buttons. One of these will bring the red MDI child to the front, and one will bring the blue MDI child to the front. Remember that there are no direct references between any of the projects, so it isn’t immediately obvious how we do this.

Furthermore, each individual project will set up its own ToolStrip button. So the ToolStrip itself will be in the Shell project. The Red project will contain code to create the ‘Show Red Form’ button and add it to the ToolStrip. Similarly the Blue project will contain code to create the ‘ShowBlueForm’ button and add it to the ToolStrip.

Basic Example – Code in the Shell Project

To do this we first add the ToolStrip to the Shell form using the visual designer. We’ll call it ‘shellToolStrip’. To make life easy in this simple example we’ll give it a scope of ‘Internal’ (by changing the ‘Modifiers’ property).

Then we extend AfterShellCreated to register this ToolStrip as a UIExtensionSite:

        protected override void AfterShellCreated()
        {
            base.AfterShellCreated();
            this.Shell.IsMdiContainer = true;
            RootWorkItem.Items.Add(this.Shell, "Shell");
            RootWorkItem.UIExtensionSites.RegisterSite("ShellToolStrip", this.Shell.shellToolStrip);
        }

The RegisterSite method has the effect of adding the object passed in its second parameter (the shellToolStrip) to the UIExtensionSites collection of the RootWorkItem. The UIExtensionSites collection is indexed by name, and RegisterSite gives the object the name passed in the first parameter (“ShellToolStrip”).

Basic Example – Code in the MDI Child Projects

As in the original example, we have access to the RootWorkItem in the individual projects containing the MDI child forms. So we can retrieve a UIExtensionSite object from the UIExtensionSites collection of that RootWorkItem. The UIExtensionSite object represents the ToolStrip. UIExtensionSite has an Add method we can use to add a ToolStripButton to the UIExtensionSite.

So in the ModuleInit classes (RedModuleInit and BlueModuleInit) of the appropriate MDI child projects we create such a button. We give it an appropriate event handler for its Click event. We’re using standard .NET events here:

        private Form1 form;
        public override void Load()
        {
            // Code as before
            base.Load();
            Form shell = (Form)parentWorkItem.Items["Shell"];
            form = new Form1();
            form.MdiParent = shell;
            form.Show();

            // Create a new button, hook it up to an event handler, 
            // and add it to our ToolStrip by using the UIExtensionSite.
            ToolStripButton toolStripButton = new ToolStripButton("Show Red Screen");
            toolStripButton.Click += new System.EventHandler(toolStripButton_Click);
            UIExtensionSite uiExtensionSite = parentWorkItem.UIExtensionSites["ShellToolStrip"];
            uiExtensionSite.Add<ToolStripButton>(toolStripButton);
        }

        void toolStripButton_Click(object sender, System.EventArgs e)
        {
            form.BringToFront();
        }

That’s all there is to this example. The only change to the above code for the Blue project is that the ToolStripButton is initialized to have Text “Show Blue Button” rather than “Show Red Button”.

Basic Example – Results

The code for this is available. If we run it we get a screen as below. Clicking “Show Blue Screen” brings the blue child form to the front, clicking “Show Red Screen” brings the red child form to the front.

ToolStripMDIApplication

As previously with this example, please note that this is not the way you would normally set up an MDI application in the CAB (although it might well be the way you would set up a ToolStrip). The example simplifies many aspects of the code in order to demonstrate the core concepts.

Conclusion

In this article we’ve created an application with three separate projects that don’t reference each other, but having a common ToolStrip in the Shell project with buttons contributed by the other projects.

In part 14 we will examine how this is working in more detail, show how we can use UIExtensionSites for other user interface elements, and discuss what value this is adding to the development process.

October 7, 2007

Table of Contents for ‘Introduction to CAB/SCSF’ Articles (2)

I’ve revised the table of contents to give some detail on what each of the articles is about:

Part 1 Modules and Shells

A guide to these two core concepts without the need to understand dependency injection or WorkItems. Explains what a composite application is and why we might want one, and shows a naive application that uses the CAB to run three separate projects simultaneously without them referencing each other. Also explains some of the mysteries of how CAB applications behave at start-up.

Part 2 WorkItems

A quick initial look at WorkItems, explaining their importance both as containers of code and as a hierarchy that allows us to control the scope of the code.

Part 3 Introduction to Dependency Injection

A discussion of dependency injection and why it’s useful in general, without reference to the Composite Application Block. A code example is given. The relationship to the strategy pattern is examined, as well as the various different types of dependency injection.

Part 4 An Aside on Inversion of Control, Dependency Inversion and Dependency Injection

A discussion of the concepts of inversion of control and dependency inversion, and how they relate to dependency injection. Again these concepts are discussed without direct reference to the Composite Application Block.

Part 5 Dependency Injection and the Composite Application Block

This article finally revisits the Composite Application Block, showing how we can use dependency injection to get hold of WorkItems in projects that are not conventionally referenced, and hence access the objects in their containers. It discusses the various ways of doing dependency injection in the CAB using the attributes ComponentDependency, ServiceDependency and CreateNew, and gives an example illustrating this. It further discusses the ObjectBuilder briefly, and explains how dependency injection works in the WorkItems hierarchy.

Part 6 Constructor Injection in the Composite Application Block

A brief article on how to use constructor injection with the CAB, and why we might not want to.

Part 7 Introduction to Services in the Composite Application Block

Discusses what services are in general, what they are in the Composite Application Block, and how the Services collection differs from the Items collection. Gives a basic example, and an example of splitting interface from implementation in a service.

Part 8 Creating and Using Services in the Composite Application Block

Dives into services in much more detail, including an in-depth examination of the various ways of creating and retrieving services.

Part 9 The Command Design Pattern

Another article looking at some theory without direct reference to the Composite Application Block: explains the command pattern, how it relates to .NET, and why its a good thing if you’re writing menus.

Part 10 Commands in the Composite Application Block

Shows how to use Commands in the Composite Application Block to hook up clicks on menus to their handlers. Explains why we might want to do it this way rather than with the more usual .NET approach using events. Looks at how to handle Status with Commands, the parameters passed to a CommandHandler, and discusses writing your own CommandAdapters to handle other invokers than menus. Gives a CommandAdapter example.

Part 11 Introduction to Events in the Composite Application Block

Recaps the usual events in .NET and explains why we might want something simpler. Gives a basic example of the Composite Application Block’s alternative approach.

Part 12 Events in the Composite Application Block

Goes into detail of what we can do with the Composite Application Block’s events: examines the handling of scope, how the EventTopics collection works, use of the ThreadOption enumeration to ensure that our event executes on the GUI thread, more flexible event handling with AddSubscription and RemoveSubscription, hooking up .NET events to CAB events with AddPublication, and how to disable CAB events.

The Shocking Blue Green Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 81 other followers