How to get a reference to the parent form from the compontent container
-
Wednesday, October 17, 2007 8:01 PM
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.
All Replies
-
Wednesday, October 17, 2007 11:02 PM
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:36 PMModerator
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();
}
}
} -
Thursday, October 18, 2007 12:31 PM
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 5:25 PMModeratorThe 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 7:36 PM
Wow, nobugz, that is amazing! I really didn't think that this was possible before.
-
Thursday, October 18, 2007 8:56 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 9:11 PMModeratorUgh, 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. -
Friday, October 19, 2007 3:24 AM
Wonderful sample, I see the value too and will mark it :-)
-
Friday, October 19, 2007 1:32 PM
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 3:20 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.
-
Thursday, November 01, 2007 1:17 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.
-
Wednesday, November 18, 2009 7:14 PMI 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?


