« Buying for the house - Selecting products | Main | Backups »

Finding the Component Container

Ever wonder how to create a Component (a non-visual entity) which can automatically obtain a reference to the containing control when it is created? The ErrorProvider component does this. As soon as it is placed on a Windows Form (or more accurately the component tray), the ContainerControl property is set to the Form.

Although it's not hard for developers to manually select the containing control from the dropdown that would be provided, its simple to provide it automatically. Once you see how easy it is to add this support, you'll never go back to the days of manually setting the property again (or forgetting to set it!).

I'll walk you through the basics of creating the component. All of the source you need to make this work is here (in C#).

First, you'll need a component:

public class Demonstration : System.ComponentModel.Component
{ ...

Next, for convenience and to save some keystrokes, you'll want to add a couple of namespaces to your class file:

using System.ComponentModel.Design;
using System.Windows.Forms;

Add a private field which stores the ContainerControl reference:

private ContainerControl _containerControl = null;

Add a property so that users of the component can review or change the value:

public ContainerControl ContainerControl
{
  get { return _containerControl; }
  set { _containerControl = value; }
}

Finally, add an overridden implementation of the Site property:

public override ISite Site
{
  get { return base.Site; }
  set
  {
    base.Site = value;
    if (value == null)
    {
      return;
    }

IDesignerHost host = value.GetService(
typeof(IDesignerHost)) as IDesignerHost;
IComponent componentHost = host.RootComponent;
if (componentHost is ContainerControl)
{
ContainerControl = componentHost as ContainerControl;
}
}
}


So, what is this really doing? If you read in MSDN about this property and its interface type, there's a good chance you'll be scratching your head in confusion. The example in MSDN is of little help.


The containing Form (which has an IContainer in this instance) sets the Site property upon creation of the component. The ISite interface derives from IServiceProvider, which contains the method called above (GetService). None of the methods on ISite provide access to the containing control, so a different technique is needed.


Using the GetService method, an object (service) implementing the IDesignerHost interface is retrieved. Once this object is available, we're almost there! Using the IDesignerHost interface, the RootComponent is accessed and cast to an IComponent. The RootComponent is the WinForm for the active designer. If the root component is a ContainerControl, we cast it and store it away in our property.


Once you have compiled this new component (from a separate assembly to make using it within Visual Studio .NET more convenient) and added it to the toolbox, the component is ready for its first test. Place an instance onto a WinForm (or UserControl) (either drag and drop or double-click the component). Check the properties: the ContainerControl property is set to the host!


If you look at the other ISite interface methods, you may wonder why none of those were used. In this case, the values retrieved from those methods represent objects that are owned by the form, not the actual instance of the form (it has the values/interfaces, not "is a" value/interface).


That's it. Enjoy!

Comments

Thanks for this tidbit--I have not seen it anywhere else.
Have you updated it for 2005? I am trying to inherit from BindingSource and get a ref to the Form. This code does not seem to work. Am I missing something?
Dave

Hey Dave - it works fine in VS 2005. I just created a new project per my steps above and the control properly obtains the container control.

Help support my web site by searching and buying through Amazon.com (in assocation with Amazon.com).