Monday, October 30, 2006

Composite UI Application Block EventBroker and Publish and Subscribe

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:

  [EventPublication( "topic://CABTestOne/UserButtonClick", PublicationScope.Global )]
public event EventHandler<EventArgs> UserButtonClick;

public void UserButtonClicked( object sender, MouseEventArgs e )
{
if( UserButtonClick != null )
{
UserButtonClick( this, null );
}
}

The confusing part to me was why would UserButtonClick ever be null or not null.
Where did it get instantiated?

As I studied the example code I came across a comment that Pub/Sub should be used
to communicate between views and it should not be used to communicate between a
view and its presenter.

I recalled that a "view" is a SmartPart. My previous example has a SmartPart called
UserButton. So I started modifying the UserButton class.

First I added an event handler to the UserButton to handle "MouseClick" events.
I did this through the designer by getting properties on UserButton and adding the
event handler.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.CompositeUI.EventBroker;

namespace CABTestOne
{
[SmartPart]
public partial class UserButton : UserControl
{
public UserButton( )
{
InitializeComponent( );
}

private void button1_MouseClick( object sender, MouseEventArgs e )
{
UserButtonClicked( sender, e );
}
}
}

Then I added an EventPublication attribute to UserButton. (I will omit the using
statements )


namespace CABTestOne
{
[SmartPart]
public partial class UserButton : UserControl
{
public UserButton( )
{
InitializeComponent( );
}

private void button1_MouseClick( object sender, MouseEventArgs e )
{
UserButtonClicked( sender, e );
}

[EventPublication( "topic://CABTestOne/UserButtonClick", PublicationScope.Global )]
public event EventHandler<EventArgs> UserButtonClick;

public void UserButtonClicked( object sender, MouseEventArgs e )
{
if( UserButtonClick != null )
{
UserButtonClick( this, null );
}
}
}
}

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:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI.EventBroker;

namespace CABTestOne
{
public class MyMessageUser
{
[EventSubscription( "topic://CABTestOne/UserButtonClick", Thread = ThreadOption.UserInterface )]
public void HandleUserButtonClickEvent( object sender, EventArgs args )
{
int i = 3;
}
}
}


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.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.CompositeUI.WinForms;
using Microsoft.Practices.ObjectBuilder;

namespace CABTestOne
{
public class MyWorkItem : WorkItem
{

protected override void OnRunStarted( )
{
base.OnRunStarted( );

//Add my pub/sub stuff
this.Items.AddNew<MyMessageUser>( "MyMessageUser" );
//work spaces control the layout of items contained in the WorkItem
IWorkspace mainWorkSpace = this.Workspaces["MainFormWorkspace"];

//Add a user button to this work item.
UserButton db = this.Items.AddNew<UserButton>( "UserButton" );

//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 I ran it in the debugger and BANG it stopped on my break point in MyMessageUser.
Sweet!


Remember my concern?


        public void UserButtonClicked( object sender, MouseEventArgs e )
{
if( UserButtonClick != null )
{
UserButtonClick( this, null );
}
}

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


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

  2. 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!


    public class MyMessageUser
{
[EventSubscription( "topic://CABTestOne/UserButtonClick", Thread = ThreadOption.UserInterface )]
[EventSubscription( "topic://CABTestOne/UserButtonClick2", Thread = ThreadOption.UserInterface
)
]
public void HandleUserButtonClickEvent( object sender, EventArgs args )
{
int i = 3;
}
}

Now I need something that publishes this new event topic //CabTestOne/UserButtonClick2.

I decided to make a new class called MyMessagePublisher. Here is the code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI.EventBroker;

namespace CABTestOne
{
public class MyMessagePublisher
{
[EventPublication( "topic://CABTestOne/UserButtonClick2", PublicationScope.Global)]
public event EventHandler<EventArgs> UserButtonClick2;

public void UserButtonClicked( object sender, MouseEventArgs e )
{
if( UserButtonClick2 != null )
{
UserButtonClick2( this, null );
}
}
}
}


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.

using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.CompositeUI.EventBroker;

namespace CABTestOne
{
[SmartPart]
public partial class UserButton : UserControl
{
public MyMessagePublisher publisher;

public UserButton( )
{
InitializeComponent( );
}

private void button1_MouseClick( object sender, MouseEventArgs e )
{
UserButtonClicked( sender, e );
publisher.UserButtonClicked( sender, e );
}

[EventPublication( "topic://CABTestOne/UserButtonClick", PublicationScope.Global )]
public event EventHandler<EventArgs> UserButtonClick;

public void UserButtonClicked( object sender, MouseEventArgs e )
{
if( UserButtonClick != null )
{
UserButtonClick( this, null );
}
}
}
}


When the UserButton recieves the MouseClick event it then calls the MyMessagePublisher.UserButtonClicked
method.


I modified MyWorkItem to add the MyMessagePublisher to UserButton.


    public class MyWorkItem : WorkItem
{

protected override void OnRunStarted( )
{
base.OnRunStarted( );

//Add my pub/sub stuff
this.Items.AddNew<MyMessageUser>( "MyMessageUser" );

//work spaces control the layout of items contained in the WorkItem
IWorkspace mainWorkSpace = this.Workspaces["MainFormWorkspace"];

//Add a user button to this work item.
UserButton db = this.Items.AddNew<UserButton>( "UserButton" );

//Add my pub/sub stuff
db.publisher = this.Items.AddNew<MyMessagePublisher>( "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

Composite UI Application Block Tutorial

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.

  1. Form. You will need a Form.

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

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

  4. 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.


//Program.cs
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.WinForms;
using Microsoft.Practices.ObjectBuilder;

namespace CABTestOne
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main( )
{
new MyFirstCabApplication().Run( );
}
}
}

MyFirstCabApplication is a subclass of FormShellApplication. FormShellApplication
uses generics to associate the Form and the WorkItem.


//MyFirstCabApplication.cs
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.WinForms;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.ObjectBuilder;

namespace CABTestOne
{
public class MyFirstCabApplication : FormShellApplication<MyWorkItem,Form1>
{

}
}


MyWorkItem is a subclass of WorkItem. The application will call OnRunStarted of
the WorkItem. It is able to do this because the constructor of MyFirstCabApplication
specifies MyWorkItem.


//MyWorkItem.cs
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.CompositeUI.WinForms;
using Microsoft.Practices.ObjectBuilder;

namespace CABTestOne
{
public class MyWorkItem : WorkItem
{

protected override void OnRunStarted( )
{
base.OnRunStarted( );

//work spaces control the layout of items contained in the WorkItem
IWorkspace mainWorkSpace = this.Workspaces["MainFormWorkspace"];

//Add a user button to this work item.
UserButton db = this.Items.AddNew<UserButton>( "UserButton" );

//show the UserButton in the workspace...
mainWorkSpace.Show( db );
}
}
}


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.


//UserButton.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI.SmartParts;

namespace CABTestOne
{
[SmartPart]
public partial class UserButton : UserControl
{
public UserButton( )
{
InitializeComponent( );
}
}
}

//UserButton.Designer.cs
namespace CABTestOne
{
partial class UserButton
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose( bool disposing )
{
if( disposing && ( components != null ) )
{
components.Dispose( );
}
base.Dispose( disposing );
}

#region Component Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent( )
{
this.button1 = new System.Windows.Forms.Button( );
this.SuspendLayout( );
//
// button1
//
this.button1.BackColor = System.Drawing.Color.Khaki;
this.button1.Dock = System.Windows.Forms.DockStyle.Fill;
this.button1.Location = new System.Drawing.Point( 0, 0 );
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size( 69, 32 );
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = false;
//
// UserButton
//
this.AutoScaleDimensions = new System.Drawing.SizeF( 6F, 13F );
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add( this.button1 );
this.Name = "UserButton";
this.Size = new System.Drawing.Size( 69, 32 );
this.ResumeLayout( false );

}

#endregion

private System.Windows.Forms.Button button1;
}
}


The Form is also split across two files. I will include them both. Form1.cs and
Form1.Designer.cs.

//Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace CABTestOne
{
public partial class Form1 : Form
{
public Form1( )
{
InitializeComponent( );
}

}
}
//Form1.Designer.cs
namespace CABTestOne
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose( bool disposing )
{
if( disposing && ( components != null ) )
{
components.Dispose( );
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent( )
{
this.MainFormWorkspace = new Microsoft.Practices.CompositeUI.WinForms.DeckWorkspace( );
this.SuspendLayout( );
//
// MainFormWorkspace
//
this.MainFormWorkspace.BackColor = System.Drawing.SystemColors.ControlDark;
this.MainFormWorkspace.Location = new System.Drawing.Point( 12, 12 );
this.MainFormWorkspace.Name = "MainFormWorkspace";
this.MainFormWorkspace.Size = new System.Drawing.Size( 268, 146 );
this.MainFormWorkspace.TabIndex = 0;
this.MainFormWorkspace.Text = "MainFormWorkspace";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF( 6F, 13F );
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size( 292, 273 );
this.Controls.Add( this.MainFormWorkspace );
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout( false );

}

#endregion

private Microsoft.Practices.CompositeUI.WinForms.DeckWorkspace MainFormWorkspace;






}
}

Review


  1. Create a Form.

  2. Create a subclass of Microsoft.Practices.CompositeUI.WorkItem.

  3. Create a subclass of Microsoft.Practices.CompositeUI.WinForms.FormShellApplication.

  4. Update your static void Main to "RUN" your FormShellApplication subclass.

  5. Override the OnRunStarted method of your WorkItem class.

  6. 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.

  7. 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.

  8. 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.


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

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

  3. 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.

  4. 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.

  5. 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.


//MyWorkItem.cs
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.CompositeUI.WinForms;
using Microsoft.Practices.ObjectBuilder;

namespace CABTestOne
{
public class MyWorkItem : WorkItem
{

protected override void OnRunStarted( )
{
base.OnRunStarted( );

//work spaces control the layout of items contained in the WorkItem
IWorkspace mainWorkSpace = this.Workspaces["MainFormWorkspace"];

//Add a user button to this work item.
UserButton db = this.Items.AddNew<UserButton>( "UserButton" );

//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 );
}
}
}

//SimpleView.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using Microsoft.Practices.CompositeUI.SmartParts;

namespace CABTestOne
{
[SmartPart] public partial class SimpleView : UserControl
{
public SimpleView( )
{
InitializeComponent( );
}
}
}
//SimpleView.Designer.cs
namespace CABTestOne
{
partial class SimpleView
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose( bool disposing )
{
if( disposing && ( components != null ) )
{
components.Dispose( );
}
base.Dispose( disposing );
}

#region Component Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent( )
{
this.smartPartPlaceholder2 = new Microsoft.Practices.CompositeUI.WinForms.SmartPartPlaceholder( );
this.SuspendLayout( );
//
// smartPartPlaceholder2
//
this.smartPartPlaceholder2.BackColor = System.Drawing.Color.Red;
this.smartPartPlaceholder2.Location = new System.Drawing.Point( 29, 28 );
this.smartPartPlaceholder2.Name = "smartPartPlaceholder2";
this.smartPartPlaceholder2.Size = new System.Drawing.Size( 75, 75 );
this.smartPartPlaceholder2.SmartPartName = "UserButton";
this.smartPartPlaceholder2.TabIndex = 1;
this.smartPartPlaceholder2.Text = "smartPartPlaceholder2";
//
// SimpleView
//
this.AutoScaleDimensions = new System.Drawing.SizeF( 6F, 13F );
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.Gainsboro;
this.Controls.Add( this.smartPartPlaceholder2 );
this.Name = "SimpleView";
this.Size = new System.Drawing.Size( 147, 150 );
this.ResumeLayout( false );

}

#endregion

private Microsoft.Practices.CompositeUI.WinForms.SmartPartPlaceholder smartPartPlaceholder2;
}
}


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.

Introduction to Digerati Illuminatus

This BLOG will contain my thoughts and experiences concerning software development and the related engineering practices as well as the management of the software development effort.

-Geoff