Monday, October 30, 2006

As I started learning how to use the CAB EventBroker with Publish (Pub) and Subscribe (Sub) I found many examples of simple event publishers. However the code was always confusing because the examples looked similar to this:

An event has a "topic" and a scope. The topic is also referred to as the Event URI. The topic is the key that identifies an event. I do not mean to imply that there is a dictionary of events and that the topic is a key string because I have not inspected the implementation. I mean to say that the topic is used to publish the event and it is used by subscribers to listen for the event.

The scope specifies the visibility of the event.

namespace Microsoft.Practices.CompositeUI.EventBroker{// Summary:// Defines the scope for a publication of an Microsoft.Practices.CompositeUI.EventBroker.EventTopic.public enum PublicationScope{ // Summary: // Indicates that the topic should be fired on all the Microsoft.Practices.CompositeUI.EventBroker.PublicationScope.WorkItem // instances, regarding where the publication firing occurred. Global = 0, // // Summary: // Indicates that the topic should be fired only in the Microsoft.Practices.CompositeUI.EventBroker.PublicationScope.WorkItem // instance where the publication firing occurred. WorkItem = 1, // // Summary: // Indicates that the topic should be fired in the Microsoft.Practices.CompositeUI.EventBroker.PublicationScope.WorkItem // instance where the publication firing occurred, and in all the Microsoft.Practices.CompositeUI.EventBroker.PublicationScope.WorkItem // descendants. Descendants = 2,}}

WorkItems are a key component when defining scope.

So, I have an event. That's great, but not very interesting. I want something to listen for the event or in other words subscribe to the event. So I create a class called MyMessageUser. Remember that all of these classes are in the same project. I have not yet experimented with Modules.

I found many examples of what an event subscription looks like and so I implemented MyMessageUser thusly:

I put "int i = 3;" in the method so that I could place a break point there to see if and when the method was called.

I set my break point on i = 3 and fired up the application and clicked on my UserButton. Nothing. So, I made MyMessageUser a SmartPart. Nothing. What was I missing? I couldn't find any example to shed some light. Then I remember that events are associated with WorkItems. So I decided to add MyMessageUser to my WorkItem's Items.

When does the event handler get instantiated? It happens as a result of adding the SmartPart to a WorkItem. Exactly where it happens I haven't investigated.

Review

SmartParts (sometime referred to as Views) publish events. When the SmartPart is added to a WorkItem the CAB framework wires up the event handler.

Any class can subscribe to events. It doesn't have to be a SmartPart or Controller. It DOES have to be added to the items of the WorkItem.

Please remember that these are my opinions on how things work and should be set up. As I learn more about CAB I am sure that some of my assumptions, metaphors, and descriptions will prove to be inaccurate.

Multiple Events with One Handler

Now I want to push my ideas a bit and see what can happen. Can one event handler subscribe to multiple events? Why yes it can!

From my previous experiments I know that inorder to instantiate the event publisher UserButtonClick2 I have to add it to a WorkItem Items list. When you add an item to the WorkItems Item list you want to keep the reference it returns so I added a member variable to UserButton to keep the reference to the MyMessagePublisher.

//Add a control that has a SmartPartPlaceholder that will control the placement of the UserButton SimpleView simpView = this.Items.AddNew<SimpleView>( "SimpleView" );

//show the view in the workspace... mainWorkSpace.Show( simpView ); }}

Now when I run the application in the debugger it stops twice on my break point inside of MyMessageUser.HandlerUserButtonClickEvent.

Pretty sweet!

Conclusion

The Event Broker and Publish Subscribe functionality of the Composite UI Application Block is a little tricky to understand. The tricky-ness has to do with finding simple examples that explain one aspect at a time. Even my example has to build upon the previous example.

Publishers and Subscribers are "scanned" by the CAB framework when they are added to a WorkItem. This scanning looks for attributes and if CAB finds EvenPublication or EventSubscription attributes it auto-magically hooks things up!

I read that SmartParts should communicate with other SmartParts via Pub/Sub and that SmartParts should not communicate to "presenters" via Pub/Sub. I don't know why that statement was made and I am confident that the reasoning will become apparent as I do more.

Thursday, October 26, 2006

I have recently started using the Microsoft .NET Composite UI Application Block (CAB) from Patterns and Practices. I immediately realized that there is a significant learning curve and started searching the Web for examples and tutorials. For various reasons I found the tutorials to be confusing so I decided that when I started to get things figured out I would share my experience.

Please note that I am new to the CAB and my current understanding may not be correct with the overall architecture of CAB.

First, down load CAB. I use the December 2005 release for C#. I also downloaded the documentation and labs.

Install CAB and take note of where it is installed so that you can find the necessary DLLs to include in a .NET 2.0 Windows Forms project.

There are five classes that will be needed to make a simple Windows Form application using CAB.

Form. You will need a Form.

WorkItem. You will need a WorkItem in conjunction with a Form.

Shell. You will need a class that inherits from FormShellApplication. To create a Shell you must have a Form and a WorkItem.

Button View. You will need a UserControl which contains a Button control.

After you create a new C# .NET 2.0 Windows Application open the Toolbox and drag the CAB DLLs from the "Microsoft Composite UI App Block\CSharp\Source\CompositeUI.WinForms\bin\Debug" directory into a new group so that you can drag & drop CAB components in the designer.

Form

The form is just a normal Windows Form. In the designer drag a "DeckWorkspace" onto the form. Don't make it fill the entire Form inorder to see how the Workspace constrains/controls the location of UI components such as a Button. Name the Workspace control "MainFormWorkspace". The name is used as a key in collection therefore it is important to remember the name you choose.

Workspaces

A Workspace is a place where UI parts will be displayed.

WorkItem

A WorkItem is a container. It is associated to the application when the Shell is created. When the application is run the CabApplication.Run() calls rootWorkItem.Run(). Inside of your subclass of WorkItem override the method OnRunStarted().

Inside of your OnRunStarted method you can access a collection of Workspaces. Remember when we created the Form we added a DeckWorkspace and named it "MainFormWorkspace". Use that name as a key and get an interface to the workspace.

IWorkspace mainWorkSpace = this.Workspaces["MainFormWorkspace"];

So, what can you do with a Workspace. You can call Workspace.Show! But what is it going to show? This is where it gets a little confusing to me and it took me a while to get any controls to show up on my Form.

Remember we are inside of OnRunStarted() of our WorkItem. The WorkItem has the Workspaces collection and it has an Items collection. Before you call Show() on your Workspace you must add your UserControls (that are also SmartParts), but the interesting thing is that you add these UserControls to the WorkItem and NOT to the Workspace. I am still new at this so I am not sure why the UserControls are added to the WorkItem so when I get a better feel and understanding I will post why the architecture is thus. Remember I just want to get a button up on the Form. I know it has to do with seperation of concerns.

Shell

A CAB application runs in a shell. You will need a class that subclasses FormShellApplication. The constructor of your shell will associate the Form and the WorkItem.

You must update your static void Main method to call the run method on your shell.

View

A View is a UserControl that is also a SmartPart. It seems that it has to be a UserControl. In my first attempt I subclassed Button and couldn't get it to show up on the Form. So, I made a UserControl and added a Button to that control. Then I put the attribute [SmartPart] on the UserControl. Once I added the UserControl to the WorkItem.Items collection and then called Show on my Workspace the UserControl appeared on the Form.

Source Code

Find your static void Main() method and change it to run your CAB application. The class that contains Main in my project is Program.cs.

UserButton is a subclass of UserControl. Because the IDE divides a custom UserControl into two source files I will include them both. UserButton.cs and UserButton.Designer.cs.Notice that the UserButton class has the attribute [SmartPart]. This is essential.

In "your" WorkItem subclass' OnRunStarted add your SmartParts. In my example this was the UserButton which is a subclass of the UserControl. Add these SmartParts to the Items collection of your WorkItem.

Get an interface to your Workspace. In this case it is the DeckWorkspace that was "dropped" on the Form. The DeckWorkspace name is "MainFormWorkspace". This is a key to access the Workspaces collection of the WorkItem.

Call Show on the Workspace passing it the UserButton (which is a SmartPart).

SmartPartPlaceholder

After figuring out how to get a Button on the Form I was a little confused that it filled the entire area of the Workspace.

After some study I figured out how to use Microsoft.Practices.CompositeUI.WinForms.SmartPartPlaceholder.

Create a new SmartPart by subclassing UserControl. I called this SimpleView.

Drag a SmartPartPlaceholder onto the UserControl (SimpleView) and size it and position it as you like.

Set the "SmartPartName" to the ID of the UserButton. The ID of the UserButton is the key string you use when you add the UserButton to your WorkItem's Item collection. Look at MyWorkItem.cs and notice that when I added the UserButton I passed a string with the value "UserButton". That string is the key and that key must be the SmartPartName. The SmartPartName can be set by viewing the properties of the SmartPartPlaceholder that you placed on the SimpleView UserControl.

Add SimpleView to the WorkItem Items collection AFTER you have added the UserButton. This is done in the OnRunStarted method of your WorkItem class. It must be done after the UserButton was added or it doesn't work.

Change the call to the Workspace Run method by passing the SimpleView instead of the UserButton. This is critical.

Below is the source code. Notice that MyWorkItem was modified and that a new class was added called SimpleView.

Conclusion

The Composite UI Application Block (CAB) can be a bit tricky. Just getting a simple example up and running can take a lot of study.

You have to have a Shell ( a subclass of FormShellApplication).

In order to instantiate a Shell you have to have a Windows Form and a WorkItem.

On the Windows Form you must place a Workspace. The Workspace defines the area of the form where controls (SmartParts) will appear. The name of this Workspace is used as a key in the WorkItem collection Workspaces. Be sure to make note of your Workspace name. I used a DeckWorkspace in this example.

SmartParts are subclasses of UserControl that have the attribute [SmartPart].

Add the SmartParts to your WorkItem's collection called "Items".

Call Show on the Workspace. This call is done inside of the WorkItem's OnRunStarted method.

SmartPartPlaceholders define size and location for SmartParts. The SmartPartPlaceholder is added to a UserControl. SmartPartPlaceholder.SmartPartname is set to the ID of some UserControl. The ID of a UserControl is the string passed into the WorkItem Items collection when you call AddNew on that collection.

Add the SmartPart that constains the SmartPartPlaceholder AFTER you have added the UserControl that the SmartPartPlaceholder has identified in the SmartPartname.