Hosting CAB in a UserControl - In depth
February 26th, 2006
In the previous post,
I’ve described an approach to host a CAB application in Outlook, Word,
or any other .net-compatible application. In this post, I will analyze
the internals of CAB as well as the code I had to write to implement
that.
In a normal CAB application (FormShellApplication) the
Run method initialize the whole CAB infrastructure, the root work item and the
modules.
Every CAB Application inherits from the CabApplication
class. This is the “heart†of CAB, where all the initialization takes place.
The Run method of CabApplication could
be separated in three stages:
- These lines of code will add the ObjectBuilder builder
strategies, the required services, will build up and initialize the root
workitem and finally it will run it - The second part will execute the Start method. This method
is abstract and must be overridden in derived applications. The FormShellApplication
will override it in order to start the Winforms application using the
Shell as the main formprotected override void Start()
{
Application.Run(Shell);
}
This line of code will start
the message pump and the winforms application will start listening UI messages
until the shell is closed. When that happens, part 3 is executed - The disposal of the root workitem and any visualizer
configured happens here.
What happens when you want to host CAB in a UserControl
and not in a regular Form?
The problem is that the message pump has already been
created by the host application: either a Winforms (not designed with CAB), an
Outlook addin, a Word Document, etc. That means that the Start method
cannot execute Application.Run(Shell). However, if we don’t execute Application.Run,
the thread will keep executing and will dispose the workitem (part 3)
How this could be solved?
We need to delay the disposal of the root work item until we
decide it is ok to dipose it. To achieve this, a new workitem need to be
created. This root workitem will override the base Dispose(bool disposing)
method in order to have control over when the Dispose needs to be executed.
public class UserControlWorkItem
: WorkItem
{
private bool
_dispose = false;
public
void
DoDispose()
{
_dispose = true;
Dispose(true);
GC
.SuppressFinalize(this);
}
protected override
void Dispose(bool
disposing) {
if (_dispose) {
base.Dispose(disposing);
}
}
}
Then, the UserControlShellApplication will be defined
with the following code. Note the TWorkItem being of type UserControlWorkItem
and the TUserControlShell of type UserControl.
public class UserControlShellApplication : WindowsFormsApplication
where TWorkItem : UserControlWorkItem,
new()
where TUserControlShell : UserControl, new()
{
protected override
void Start()
{
}
public
void
Dispose() {
RootWorkItem.DoDispose();
}
public TUserControlShell UserControlShell {
get { return base.Shell; }
}
}
Finally, we need to host the CAB application in a
UserControl. This works just as a container and it is different from the
UserControl that will act as a Shell. The UserControl will host an instance of UserControlShellApplication
and will execute the Run method. Also, when the UserControlHost
is disposed it will also dispose the UserControlShellApplication that
will finally dispose the UserControlWorkItem.
public partial class UserControlHost : UserControl
where TUserControlShellApplication : UserControlShellApplication,
new()
where TUserControlShell : UserControl, new()
{
protected
virtual void Run()
{
_cabApp = new TUserControlShellApplication();
_cabApp.Run();
_cabApp.UserControlShell.Dock = DockStyle.Fill;
this.Controls.Add(_cabApp.UserControlShell);
OnStarted(EventArgs.Empty);
}
protected override void
Dispose(bool disposing)
{
if
(disposing) {
_cabApp.Dispose();
}
base.Dispose(disposing);
}
…
}
Note: as shown in part 3 the Visualizer is also
disposed. The same approach applies to the visualizer.
Leave a Reply