none
How to get a reference to the parent form from the compontent container

    Question

  • I have a custom component that inherits from and extends the BindingSource component.  I need to get a reference to the instance of the parent form for the component.  Does anyone know how?

     

    Components don't have a .Parent property like controls do.  They do have a container property, but it does not have a parent property either.  Sometimes casting the container as a ContainerControl works, but in this case it won't cast correctly.

     

    All Windows Designer forms add a container called "components" and all components are added to this container and show at the bottom of the screen in design mode.  From inside the component, how do I get a reference to the parent form of the container?

     

    Dave C.

    Wednesday, October 17, 2007 8:01 PM

Answers

  • I decided to sink my teeth into this one, I didn't think it was possible.  None of the toolbox components seem to interact with the form or its controls, other than through the IExtenderProvider interface.  Until I hit upon the ErrorProvider component.  It has a ContainerControl property and the designer somehow magically sets the property to "this" without any help of a custom designer.  It took me a while to figure out how it did that.  Here's an example of a silly component that updates the form's Text property at runtime:

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

    namespace WindowsApplication1 {
      public partial class MyComponent : Component {
        private ContainerControl parentControl;
        private Timer timer1;
        public MyComponent() {
          InitializeComponent();
          this.timer1 = new System.Windows.Forms.Timer(this.components);
          this.timer1.Enabled = true;
          this.timer1.Interval = 1000;
          this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
       }
        public MyComponent(IContainer container) : this() {
          container.Add(this);
        }
        public MyComponent(ContainerControl parentControl) : this() {
          this.parentControl = parentControl;
        }
        public ContainerControl ContainerControl {
          get { return this.parentControl; }
          set { this.parentControl = value; }
        }
        public override ISite Site {
          set {
            // Runs at design time, ensures designer initializes ContainerControl
            base.Site = value;
            if (value == null) return;
            IDesignerHost service = value.GetService(typeof(IDesignerHost)) as IDesignerHost;
            if (service == null) return;
            IComponent rootComponent = service.RootComponent;
            this.ContainerControl = rootComponent as ContainerControl;
          }
        }
        private void timer1_Tick(object sender, EventArgs e) {
          if (!this.DesignMode && this.ContainerControl != null)
            this.ContainerControl.Text = DateTime.Now.ToString();
        }
      }
    }
    Wednesday, October 17, 2007 11:36 PM
    Moderator
  • With a little more research, I found that you do not need to override the Site function, but just reference the Site property of the base component which is set when the component is instantiated.  Here is the code (coverted to VB.NET):

    Imports System.ComponentModel.Design

    Private _form As Form

    Private Sub GetFormInstance() ' called from constructor

        Dim _host As IDesignerHost = Nothing

        If MyBase.Site IsNot Nothing Then _host = _

            CType(MyBase.Site.GetService(GetType(IDesignerHost)), IDesignerHost)

        If _host IsNot Nothing Then _form = CType(_host.RootComponent, Form)

    End Sub

     

    Just add a call to this method from the Constructor of the component and the reference to the form will be set.

     

    This is such a significant advancement, I wrote a short article on it for Code Project. Here is the link:

    http://www.codeproject.com/useritems/VBNET__Windows_Forms_.asp

     

    Dave C.

    Friday, October 19, 2007 3:20 PM

All replies

  • Components, unlike Controls, do not have a reference to the Form.

     

    There is the "components" collection that you mention, but that does not have a reference to the Form either.

     

    It's as simple as that, you can't automatically get the Form from the Component.

     

    You can work around this by passing a reference to your custom Component manually, for example via a property.

    Wednesday, October 17, 2007 11:02 PM
  • I decided to sink my teeth into this one, I didn't think it was possible.  None of the toolbox components seem to interact with the form or its controls, other than through the IExtenderProvider interface.  Until I hit upon the ErrorProvider component.  It has a ContainerControl property and the designer somehow magically sets the property to "this" without any help of a custom designer.  It took me a while to figure out how it did that.  Here's an example of a silly component that updates the form's Text property at runtime:

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

    namespace WindowsApplication1 {
      public partial class MyComponent : Component {
        private ContainerControl parentControl;
        private Timer timer1;
        public MyComponent() {
          InitializeComponent();
          this.timer1 = new System.Windows.Forms.Timer(this.components);
          this.timer1.Enabled = true;
          this.timer1.Interval = 1000;
          this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
       }
        public MyComponent(IContainer container) : this() {
          container.Add(this);
        }
        public MyComponent(ContainerControl parentControl) : this() {
          this.parentControl = parentControl;
        }
        public ContainerControl ContainerControl {
          get { return this.parentControl; }
          set { this.parentControl = value; }
        }
        public override ISite Site {
          set {
            // Runs at design time, ensures designer initializes ContainerControl
            base.Site = value;
            if (value == null) return;
            IDesignerHost service = value.GetService(typeof(IDesignerHost)) as IDesignerHost;
            if (service == null) return;
            IComponent rootComponent = service.RootComponent;
            this.ContainerControl = rootComponent as ContainerControl;
          }
        }
        private void timer1_Tick(object sender, EventArgs e) {
          if (!this.DesignMode && this.ContainerControl != null)
            this.ContainerControl.Text = DateTime.Now.ToString();
        }
      }
    }
    Wednesday, October 17, 2007 11:36 PM
    Moderator
  • OK, this is making progress -- great work

    My component does fire the Site override, but it does not contain an IDesignerHost service. The cast always comes out null.  What am I missing?

     

    I am still not sure how interacting with the designer host contains a reference to the form, but if it works...

     

    Dave C

     

    Thursday, October 18, 2007 12:31 PM
  • The Site override is fired at design time, not runtime.  That's the secret.  Does the sample code I posted work for you?
    Thursday, October 18, 2007 5:25 PM
    Moderator
  • Wow, nobugz, that is amazing!  I really didn't think that this was possible before.

    Thursday, October 18, 2007 7:36 PM
  • I see how it works at design time. I was hoping for a runtime solution.  Since I am creating a Custom Component, it could be used on many different forms.  When the form loads, the component needs to update a reference to the form it is on.

     

    If there is no way to get a runtime reference, I will have to try something else.  Since I am extending the BindingSource component, I can get a reference to the form through the control that is bound to the BindingSource when the BindingComplete event fires.  The problem is that is an expensive event to intercept since it fires a lot and I only need it the first time it fires.

     

    I am appreciative of the solution you did dig up and will mark it as an answer if noone else comes up with a runtime solution.

     

    Dave C.

     

    Thursday, October 18, 2007 8:56 PM
  • Ugh, it *is* a runtime solution.  Do yourself a favor and try this code.  Note how the component is modifying the form's Text property.  Look closely at what happens when you run a form with the component placed on it and note that the window title is showing the time of day in one second increments.  The component is doing that, it is aware of the form it is placed on.  The design-time voodoo is there to properly initialize the reference to the form.

    If you don't see it, never mind.  Binary sees the value, somebody else will Google it too.
    Thursday, October 18, 2007 9:11 PM
    Moderator
  • Wonderful sample, I see the value too and will mark it :-)

    Friday, October 19, 2007 3:24 AM
  • My appologies for doubting you -- it does work in my application as well.  The reason I thought is was not working was that I was trying to single step through it and when you do, the Site event fails--propably because like you said, it does its voodoo in the design phase and you won't see it happen at run time.  This is new ground for me, so please forgive my slowness to catch on.

     

    In your original post, you implied there was something special about having the property ContainerControl like having that property would make it take on the charateristics of a System.Windows.Forms.ContainerControl type.  My experments show that you can call it anything you like and it does not even need to be an exposed property (I use a private field to store the reference to the parent form).  All of the magic happens in the overridden Site event.

     

    Thanks again for your timely help.  As my Aussie friends say, "Good on ya mate!"

     

    Dave C

     

    Friday, October 19, 2007 1:32 PM
  • With a little more research, I found that you do not need to override the Site function, but just reference the Site property of the base component which is set when the component is instantiated.  Here is the code (coverted to VB.NET):

    Imports System.ComponentModel.Design

    Private _form As Form

    Private Sub GetFormInstance() ' called from constructor

        Dim _host As IDesignerHost = Nothing

        If MyBase.Site IsNot Nothing Then _host = _

            CType(MyBase.Site.GetService(GetType(IDesignerHost)), IDesignerHost)

        If _host IsNot Nothing Then _form = CType(_host.RootComponent, Form)

    End Sub

     

    Just add a call to this method from the Constructor of the component and the reference to the form will be set.

     

    This is such a significant advancement, I wrote a short article on it for Code Project. Here is the link:

    http://www.codeproject.com/useritems/VBNET__Windows_Forms_.asp

     

    Dave C.

    Friday, October 19, 2007 3:20 PM
  • Wow, what's with the hostility from "nobugz"? After he admitted he himself had to "sink his teeth" into this problem, a little more explanation might have been appropriate anyway. The fact is - this does APPEAR to be a design-time solution on first review of the code. And so it's confusing - but yes it does work at runtime. Thanks for the followup article Dave.

     

    Thursday, November 01, 2007 1:17 PM
  • I see that this form works fine for a "Form" but what if the container is a Panel?  I am not able to get this code to work?  Why is that?
    Wednesday, November 18, 2009 7:14 PM