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.

8 comments:

Anonymous said...

Hey Good Job Man
Thanks for puttingup such a thing.It will give new learners a good start

Anonymous said...

Thanks for great tutorial!

My second day on mobile development :). I have a problem with the following using statement and do not know which assembly to reference:

using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.CompositeUI.WinForms;
using Microsoft.Practices.ObjectBuilder;

Currently, I reference to \Microsoft.Practices.Mobile.CompositeUI.WinForms
and
Microsoft.Practices.Mobile.UI.OrientationAware.

But it still complains.

Thanks for help.

Peter

Anonymous said...

Ok, finally I made it work.

using Microsoft.Practices.Mobile.CompositeUI;

instead of the four mentioned above.

Thanks,

Peter

Anonymous said...

I'm a beginner of CAB. This tutorial is very nice to start it. Thanks.

- Wendy

Anonymous said...

Thanks for the post, I found it late but clears a lot of concepts and yeah it takes a while to understand this thing and get a basic hello world running.

dinesh said...

this is too good tutor . some thing new type solution i got. my mail is dineshakhandeck@gmail.com

dinesh said...

i want to know how to load smart part load dynamically or unload it .....

Anonymous said...

I am a novice for CAB and your tutorial has been the place for me to start. Thanks a lot.