27

I have a non-visual component which manages other visual controls.

I need to have a reference to the form that the component is operating on, but i don't know how to get it.

I am unsure of adding a constructor with the parent specified as control, as i want the component to work by just being dropped into the designer.

The other thought i had was to have a Property of parent as a control, with the default value as 'Me'

any suggestions would be great

Edit:

To clarify, this is a component, not a control, see here :ComponentModel.Component

Pondidum
  • 11,457
  • 8
  • 50
  • 69

9 Answers9

28

[It is important to understand that the ISite technique below only works at design time. Because ContainerControl is public and gets assigned a value VisualStudio will write initialization code that sets it at run-time. Site is set at run-time, but you can't get ContainerControl from it]

Here's an article that describes how to do it for a non-visual component.

Basically you need to add a property ContainerControl to your component:

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

and override 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;
    if (host != null)
    {
        IComponent componentHost = host.RootComponent;
        if (componentHost is ContainerControl)
        {
            ContainerControl = componentHost as ContainerControl;
        }
    }
  }
}

If you do this, the ContainerControl will be initialized to reference the containing form by the designer. The linked article explains it in more detail.

A good way to see how to do things is to look at the implementation of Types in the .NET Framework that have behaviour similar to what you want with a tool such as Lutz Reflector. In this case, System.Windows.Forms.ErrorProvider is a good example to look at: a Component that needs to know its containing Form.

Michael Foukarakis
  • 39,737
  • 6
  • 87
  • 123
Joe
  • 122,218
  • 32
  • 205
  • 338
  • Thanks, this worked fine after a few minor tweaks (added some != null checks to it). – Pondidum Dec 16 '08 at 21:55
  • 1
    What exactly is service, it's not any member of the component. – Peymankh Jan 31 '10 at 19:09
  • I think `service` is actually supposed to be `host` - I switched it, and it's working for me so far. – dlras2 Jun 30 '10 at 16:33
  • 2
    How does this work at runtime. As far as i can see it is the designer that provides the IDesignerHost implementation, hence it only works in design mode. – Fedearne Feb 04 '11 at 11:34
  • It looks like this solution works when component is in separate assembly, not as part of the same project – volody Mar 20 '11 at 03:26
  • 1
    I'm so sorry. That code working correct after drop component to the from, designer generate intialization code to the ContainerControl property! So to use that solution you shoud redrop component to the from. – Viacheslav Smityukh Sep 10 '11 at 18:51
  • @JonathanWood - "Doesn't work for me. host is always null" - is this true at design-time, after adding the component to a form in the designer? – Joe Jun 27 '13 at 18:17
  • @Joe: Hmmm... I guess I didn't test that. I'd have to set it up again to verify but I don't recall any errors at design time. I was mostly just testing at run time. – Jonathan Wood Jun 27 '13 at 20:07
  • It seems hat I can not access parent form WindowState property with this example. – eomeroff Mar 22 '14 at 22:17
  • @eomeroff - I don't understand your comment: there's nothing in this example that attempts to access a parent form's WindowState property. If you have similar code that tries and fails to do so, and you don't understand why, maybe you could post a new question with your example code. – Joe Mar 30 '14 at 10:46
  • Hi. It will add ContainerControl property value after drop control on form. But the value is correctly assign only in design-time, in run-time is null. So, definitely is not the solution of this problem! – Vali Maties Oct 22 '21 at 10:28
9

I use a recursive call to walk up the control chain. Add this to your control.

public Form ParentForm
{
    get { return GetParentForm( this.Parent ); }
}

private Form GetParentForm( Control parent )
{
    Form form = parent as Form;
    if ( form != null )
    {
        return form;
    }
    if ( parent != null )
    {
        // Walk up the control hierarchy
        return GetParentForm( parent.Parent );
    }
    return null; // Control is not on a Form
}

Edit: I see you modified your question as I was typing this. If it is a component, the constructor of that component should take it's parent as a parameter and the parent should pass in this when constructed. Several other components do this such as the timer.

Save the parent control as a member and then use it in the ParentForm property I gave you above instead of this.

Rob Prouse
  • 22,161
  • 4
  • 69
  • 89
3

You will have to set the parent container some how. Your component is just a class, that resides in memory just like everything else. It has no true context of what created it unless something tells you that it did. Create a Parent control property and set it.

Or simply derive from control and use FindForm(). Not all controls must have a visible component

Brian Rudolph
  • 6,142
  • 2
  • 23
  • 19
2

I found this solution which does not need the input. For C# I implemented it this way:

public partial class RegistryManager : Component, ISupportInitialize
{

    private Form _parentForm;
    public Form ParentForm
    {
        get { return _parentForm;  }
        set { _parentForm = value; }
    }

    // Etc....

    #region ISupportInitialize
    public void BeginInit() {  }
    public void EndInit()
    {
        setUpParentForm();
    }
    private void setUpParentForm()
    {
        if (_parentForm != null) return; // do nothing if it is set
        IDesignerHost host;
        if (Site != null)
        {
            host = Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
            if (host != null)
            {
                if (host.RootComponent is Form)
                {
                    _parentForm = (Form)host.RootComponent;
                }
            }
        }
    }
    #endregion
}

This way allows the set ParentForm by user, but it is set by parent form as Default.

I hope it helps you.

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
2

If the componenet is managing other visual controls, then you should be able to get to the parent through them.

BFree
  • 102,548
  • 21
  • 159
  • 201
  • I had thought of this, while it works, it does seem a little 'hacky' – Pondidum Dec 16 '08 at 15:30
  • I don't disagree with you, however if this component will always be tied to the controls of the same form that it's on, then you have nothing to lose. – BFree Dec 16 '08 at 15:32
1

Try This ....

private Form GetParentForm(Control parent)
{
    if (parent is Form)
        return parent as Form;

    return parent.FindForm();
}

Call GetParentForm(this.Parent) from component

benka
  • 4,732
  • 35
  • 47
  • 58
0

I think you want to use the Site property of the IComponent. It's more or less an equivalent to the Parent property.

arul
  • 13,998
  • 1
  • 57
  • 77
  • any chance you could be a little more specific please? i have had a look at the site property while debugging, and there seems to be nothing that references the parent form. – Pondidum Dec 16 '08 at 15:20
-4

A improvement of above is:

public static Form ParentForm(this Control ctrl) => ctrl as Form ?? ctrl.FindForm();
GeoB
  • 53
  • 1
  • 4
-4

If the component related Form is the active Form you may get it by Form.ActiveForm.

Pollitzer
  • 1,580
  • 3
  • 18
  • 28