2

I have a page and a user control — we'll call them Detail.aspx and Selector.ascx.

Let's say the page shows the details of individual records in a database. The user control basically consists of a DropDownList control and some associated HTML. The DropDownList displays a list of other records to switch to at any time.

When the DropDownList fires its SelectedIndexChanged event, I'd like the parent page, Detail.aspx in this case, to handle it. After all, he'll need to know what was selected so that he can appropriately change the URL and the details shown, etc.

To do that, I've done what I usually do, which is also what the top answer says to do in this StackOverflow question:

public event EventHandler DropDownSelectedIndexChanged
{
    add
    {
        MyDropDownList.SelectedIndexChanged += value;
    }
    remove
    {
        MyDropDownList.SelectedIndexChanged -= value;
    }
}

The above code appears in the Selector.ascx.cs codebehind file.

As a result, on Detail.aspx, I can use it like so:

<cc1:RecordSelector ID="RecordSelector1" runat="server"
OnDropDownSelectedIndexChanged="RecordSelector1_DropDownSelectedIndexChanged" />

So far nothing fancy or surprising.

Here is my problem:

This causes a NullReferenceException when the browser hits Detail.aspx.

Debugging the problem shows that when the page is first hit, the public event I've shown above tries to add the event, but MyDropDownList is null, thus throwing the exception. From what I can tell, the events are added (or attempted to be added) before the Selector user control's Load event fires and thus also before the DropDownList's Load event fires.

Curiously, if I omit the OnDropDownSelectedIndexChanged attribute from Detail.aspx and instead put the following in the Page_Load event in Detail.aspx.cs:

protected void Page_Load(object sender, EventArgs e)
{
    RecordSelector1.DropDownSelectedIndexChanged += new EventHandler(RecordSelector1_DropDownSelectedIndexChanged);
}

It works exactly as expected. The events are attached and handled just fine. No problems.

But this means several bad things:

  1. I have to remember not to use the designer to add said event onto my user control
  2. I have to remember not to add the event via attributes when working in source view
  3. Worst of all, as the control's author I need to make sure everybody else using my control knows 1 and 2

So what am I doing wrong? Every example I've seen thus far shows similar usage of exposing child controls' events through a user control.

Community
  • 1
  • 1
Sean Hanley
  • 5,677
  • 7
  • 42
  • 53

1 Answers1

1

The reason this works:

protected void Page_Load(object sender, EventArgs e)
{
    RecordSelector1.DropDownSelectedIndexChanged 
        += new EventHandler(RecordSelector1_DropDownSelectedIndexChanged);
}

and this does not:

<cc1:RecordSelector ID="RecordSelector1" runat="server"
OnDropDownSelectedIndexChanged="RecordSelector1_DropDownSelectedIndexChanged" />

is because the first one adds the handler after the control has been initialized (via the page's Init). The second example gets parsed much earlier and as such the page is attempting to add the handler before the control has initialized.

Due to the nature of the page's life cycle I think you may have to live with adding the event handler in the code-behind. There will be no way to add the handler before the control is initialized because that control will always be null prior to initialization.

Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
  • Is there a way to do it that doesn't have this pitfall? A "Scott Gu"-recommended approach to exposing child controls' events in a user control? – Sean Hanley Aug 05 '09 at 15:25
  • I wish I could recommend a different approach but I think that exposing the event via the user control is your best option as any consumer of the event should be responsible to wire it up in a way that works for them. – Andrew Hare Aug 05 '09 at 16:15