More On UIExtensionSites (Introduction To CAB/SCSF Part 14)

来源:互联网 发布:ipad windows 编辑:程序博客网 时间:2024/05/21 09:07

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.

0 0
原创粉丝点击