0

I have a funky problem that has a workaround, but I want to keep code as similar as possible. The issue centers on a particular variable in the base class for my user controls that may or may not be null, and it should never be null.

Basically I have a number of user controls with a single base class which grabs an instance of my main form window so the user control has access to main form properties and can call methods on the main form. Here is a snippet (this.frmParent is a public member):

    private void ucBase_Load( object sender, EventArgs e )
    {
        // Establish the link to the main form.
        this.frmParent = FindForm() as frmMain;
    }

Then each user control shares this base class:

public partial class ucLiberty : ucBase

Then in the main form, I'll call the user control like this:

                ucLiberty Liberty = new ucLiberty();
                IQDevicePath = Liberty.GetIQDrivePath();

For some reason, when I instantiate the user control (in this case it's in the main form), the frmParent variable in the base class may or may not be populated with a non null value.

I noticed that the load event in the user control was not firing. I found a method called CreateControl() which is supposed to force the creation of the control, and then my load event started fireing, however when I traced execution in the debugger and I got to the base class where it was trying to populate frmParent, FindForm() would not always return a non null value.

I have other user controls where I don't have this issue with, and the difference between them is that some user controls have child controls and some do not have child controls. The one's without child controls have this problem.

My workaround is to monitor which user control FindForm() fails in, and in that user control's load event, assign the value with a call to the main form's constructor, something like this:

this.frmParent = new frmMain();

However, I still have to have the call to CreateControl() for the load event to fire, and I don't like the idea of requiring future maintainers to have to have explicit knowledge of different behavioral imperatives. In other words, I'd like my user controls to all work the same way to keep maintenance simple.

I have torn my code apart and cannot figure out why sometimes the user control's load event may or may not fire, and why the call to FindForm() in the user control base class fails.

Does anyone have any ideas about how to solve these issues? Thanks.

Mike Malter
  • 1,018
  • 1
  • 14
  • 38

2 Answers2

2

You are committing a fairly serious OOP sin by making the user control aware of the form it is placed on. It is supposed to be a independent class that doesn't care about its container, you use events to let the container be aware of anything that happened in your class that the container might be interested in. A design principle that's followed to the letter by any of the standard controls in Winforms. A TextBox never cares what kind of form it is dropped on, for example.

That's the theory, practice isn't often that clean. The problem you are running into is that the OnLoad method (aka Load event) fires for a different reason. It runs when the native Windows handle gets created. Which normally happens when the form's window gets created, triggered by the Show() method call. Which is after the form's IntializeComponent() method.

If you have any code in your user control's constructor that requires that the Handle property has a value then Winforms obliges and creates the Windows handle for your control and fires the Load event. Too soon, before the form's InitializeComponent() method had a chance to call its Controls.Add() method. The Parent property doesn't yet refer to the form. Kaboom on FindForm().

It is easy to diagnose with the debugger. Set a breakpoint on the user control's OnLoad method. The stack trace will take you right to the statement that triggered the handle creation.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I don't have anything in the user control constructor other than what was put there by VS which is InitializeComponent(). There are no controls in the user control, just code. the load event does not fire when the control is instantiated, it is not called when it's first method is called, but is called when the second method is called and the FindForm() returns null. – Mike Malter Sep 06 '11 at 05:00
0

How many main form instances do you have? If you only have one - and ever only will have one, you can make it accessible as a singleton.

public class frmMain : Form
{
     private static frmMain s_Singleton;

     public static frmMain Singleton
     {
          get
          {
              if (s_Singleton == null) s_Singleton = new frmMain();
              return s_Singleton;
          }

     }
}

So instead of ever calling the constructor directly, call frmMain.Singleton for a reference (even in (especially in!) your Program.cs where the form is most likely initially constructed). Furthermore, you have a globally accessible reference to your main form, available by calling frmMain.Singleton in your user controls

As to the reason why your ucBase_Load does not load, my uneducated guess is that you also handle the event in the concrete user control and that it somehow stops the base handler from firing. If that is the case, add base.OnLoad() in your concrete user control's event handler.

And as to why FindForm does not work, it could be caused because the method is called before the user control's constructor is finished. This seems unlikely but it's hard to say for sure without seeing your code. The reason this might be the issue is that the control's parent etc is set up in the constructor. But since you're handling it in the Load-event, it seems unlikely. You could verify this theory by moving the logic to the OnParentChanged event.

By the way, your work around seems very dirty, it does not give you a reference to your main form, it creates a new main form instance (which is never shown).

havardhu
  • 3,576
  • 2
  • 30
  • 42