A generic light weight workspace for CAB

If you used to Workspaces in CAB, you will find that CAB Workspaces have two problems:

1. They unnecessary consume a Win32 HWND handle.

2. There is limited support for the kind of containers that you are allowed to use. Let’s say that you purchased a "FooBar" WinForms container, this new container will not be an IWorkspace.

In this article I am exploring an alternative way for a generic Workspace that does not require a Win32 HWND and that is also capable of converting any WinForms control container into a usable CAB Workspace.

The first problem we must resolve is the fact that any .NET Component hosted inside a Windows Forms is kept under a different collection than the standard ControlCollection collection. Every Form or UserControl in .NET that host components stores them in a private field named “components”. This private field is created and maintained by the WinForms designer. Because of this, when a CAB View or SmartPart is added to a WorkItem, all sub controls of such form are added to the WorkItem by one of the builder strategies in CAB. However, none of the components in the form are added. This will create a problem for our generic work item, because it will be impossible to find it later on.

The way I resolve the problem was by creating a “Component Strategy” for the object builder. Here is the source code for it:

public class ComponentStrategy: BuilderStrategy

{

private static WorkItem GetWorkItem(IBuilderContext context,object item)

{

if (item is WorkItem)

return item as WorkItem;

return context.Locator.Get<WorkItem>(new DependencyResolutionLocatorKey(typeof(WorkItem), null)); }

private static IContainer GetContainer(IBuilderContext context, Type typeToBuild, object existing)

{

BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.GetField | BindingFlags.NonPublic;

FieldInfo componentField = typeToBuild.GetField("components",bindingFlags);

if (componentField != null && componentField.FieldType == typeof(IContainer))

{

return (IContainer)componentField.GetValue(existing);

}

return null;

}

public override object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)

{

IContainer container = GetContainer(context, typeToBuild, existing);

WorkItem workItem = GetWorkItem(context, existing);

if (container != null && workItem!=null )

{

foreach (object component in container.Components)

{

if (component is Workspace)

{

workItem.Items.Add(component, ((Workspace)component).Name);

}

else

{

workItem.Items.Add(component);

}

}

}

return base.BuildUp(context, typeToBuild, existing, idToBuild);

}

public override object TearDown(IBuilderContext context, object item)

{

IContainer container = GetContainer(context, item.GetType(), item);

WorkItem workItem = GetWorkItem(context, item);

if (container != null && workItem != null)

{

foreach (object component in container.Components)

{

workItem.Items.Remove(component);

}

}

return base.TearDown(context,item);

}

}

Now, don’t forget to register the strategy with your application:

protected override void AddBuilderStrategies(Builder builder)

{

base.AddBuilderStrategies(builder);

builder.Strategies.AddNew<ComponentStrategy>(BuilderStage.Initialization);

}

OK, so we are ready for the generic Workspace:

public class Workspace : Component, IComposableWorkspace<Control, SmartPartInfo>

{

private WorkspaceComposer<Control, SmartPartInfo> _composer;

private Control _controlContainer;

private bool _isDisposing = false;

private string _name;

public Workspace(IContainer container)

{

_composer = new WorkspaceComposer<Control, SmartPartInfo>(this);

container.Add(this);

}

public string Name

{

get { return _name; }

set { _name = value; }

}

public Control ContainerControl

{

get

{

return _controlContainer;

}

set

{

_controlContainer = value;

}

}

protected override void Dispose(bool disposing)

{

_isDisposing = disposing;

base.Dispose(disposing);

}

#region IComposableWorkspace Members

SmartPartInfo IComposableWorkspace.ConvertFrom(ISmartPartInfo source)

{

return SmartPartInfo.ConvertTo<SmartPartInfo>(source);

}

void IComposableWorkspace.OnActivate(Control smartPart)

{

smartPart.BringToFront();

smartPart.Visible = true;

smartPart.Show();

}

void IComposableWorkspace.OnApplySmartPartInfo(Control smartPart, SmartPartInfo smartPartInfo)

{

// No op.

}

void IComposableWorkspace.OnClose(Control smartPart)

{

if (smartPart.Parent != null && smartPart.Parent is TabPage)

{

TabPage tabPage = smartPart.Parent as TabPage;

tabPage.Controls.Remove(smartPart);

_controlContainer.Controls.Remove(tabPage);

}

else

{

_controlContainer.Controls.Remove(smartPart);

}

smartPart.Disposed -= ControlDisposed;

ActivateTopmost();

}

private void ControlDisposed(object sender, EventArgs e)

{

Control control = sender as Control;

if (_isDisposing == false && control != null)

{

_composer.ForceClose(control);

}

}

private void ActivateTopmost()

{

if (_controlContainer.Controls.Count != 0)

{

_composer.Activate(_controlContainer.Controls[0]);

}

}

void IComposableWorkspace.OnHide(Control smartPart)

{

smartPart.Visible = false;

smartPart.SendToBack();

ActivateTopmost();

}

void IComposableWorkspace.OnShow(Control smartPart, SmartPartInfo smartPartInfo)

{

smartPart.Dock = DockStyle.Fill;

if (_controlContainer is TabControl)

{

TabPage tabPage = new TabPage(smartPartInfo.Title);

tabPage.Controls.Add(smartPart);

_controlContainer.Controls.Add(tabPage);

}

else

{

_controlContainer.Controls.Add(smartPart);

}

smartPart.Disposed += ControlDisposed;

_composer.Activate(smartPart);

}

void IComposableWorkspace.RaiseSmartPartActivated(WorkspaceEventArgs e)

{

if (SmartPartActivated != null)

{

SmartPartActivated(this, e);

}

}

void IComposableWorkspace.RaiseSmartPartClosing(WorkspaceCancelEventArgs e)

{

if (SmartPartClosing != null)

{

SmartPartClosing(this, e);

}

}

#endregion

#region IWorkspace Members

void IWorkspace.Activate(object smartPart)

{

_composer.Activate(smartPart);

}

object IWorkspace.ActiveSmartPart

{

get { return _composer.ActiveSmartPart; }

}

void IWorkspace.ApplySmartPartInfo(object smartPart, ISmartPartInfo smartPartInfo)

{

_composer.ApplySmartPartInfo(smartPart,smartPartInfo);

}

void IWorkspace.Close(object smartPart)

{

_composer.Close(smartPart);

}

void IWorkspace.Hide(object smartPart)

{

_composer.Hide(smartPart);

}

void IWorkspace.Show(object smartPart)

{

_composer.Show(smartPart);

}

void IWorkspace.Show(object smartPart, ISmartPartInfo smartPartInfo)

{

_composer.Show(smartPart,smartPartInfo);

}

public event EventHandler<WorkspaceEventArgs> SmartPartActivated;

public event EventHandler<WorkspaceCancelEventArgs> SmartPartClosing;

ReadOnlyCollection<object> IWorkspace.SmartParts

{

get { return _composer.SmartParts; }

}

}

The generic workspace is basically a Component that have a ControlContainer property. That’s it, just drag the Workspace component to your WinForm and set the ControlContainer property to your “FooBar” control container, and you will get yourself your own Workspace for almost any kind of container.