views:

825

answers:

2

I have a TemplateField that is dynamically added to a custom GridView.

void ITemplate.InstantiateIn(System.Web.UI.Control container)
    {
        switch (_templateType)
        {
            case ListItemType.Header:
                if (this.ParentGridView.ShowDeleteHeaderImage)
                {
                    Image hImg = new Image();
                    hImg.ImageUrl = this.ParentGridView.DeleteHeaderImageUrl;
                    hImg.AlternateText = "Mark for Deletion";
                    container.Controls.Add(hImg);
                }
                else
                {
                    Label l = new Label();
                    l.Text = "Del";
                    container.Controls.Add(l);
                }
                break;
            case ListItemType.Item:
                container.Controls.Add(new CheckBox());
                break;
            case ListItemType.EditItem:
                break;
            case ListItemType.Footer:
                QLImageButton deleteButton = new QLImageButton();
                deleteButton.Settings.ImageId = "cmdQLGVDelete";
                deleteButton.Settings.ImageUrl = this.ParentGridView.DeleteImageUrl;
                deleteButton.CommandName = "Delete";
                container.Controls.Add(deleteButton);
                break;
        }
    }

In response to a grid Command (insert/update/delete), a method called GetRowControls is called which iterates through the columns in the particular gridrow, and adds each of its controls to a Dictionary.

Dictionary<string, WebControl> GetRowControls(GridViewRow row)
...

rowControls.Add(ctrl.ID, (WebControl)ctrl);

...

So this works fine for both template field and bound controls added declaratively, as well as dynamic-non template fields added programatically.

However when the control is a TemplateField control added dynamically ctrl.ID is always null and therefore the statement above throws an exception.

I've looked into this with Reflector because I found that when I examined the variable in the immediate window in VS 2005 i.e. ?ctrl, ctrl.ID WOULD list a value. I've since established that this is because in listing ?ctrl in the immediate window, the proprty ClientID is called and ClientID calls EnsureId(), which in turn sets ID.

public virtual string ClientID
{
    get
    {
        this.EnsureID();
        string uniqueID = this.UniqueID;
        if ((uniqueID != null) && (uniqueID.IndexOf(this.IdSeparator) >= 0))
        {
            return uniqueID.Replace(this.IdSeparator, '_');
        }
        return uniqueID;
    }
}

So I'm assuming that ClientID, UniqueId and ID are all null - although as above just reading the first two will trigger all to be set. Also note that NamingContainer is not null. It has been set.

So the work around for this is quite simple i.e. check for ctrl.ID==null and if so simply read ctrl.ClientID. And thats what I've done because time wise I've really got to get a wriggle on. But I'm still interested in the answer if anyone knows it off the top of their heads.

Why is the ID value of a child control, of a dynamically added TemplateField, set at a different time from that of other controls?

+2  A: 

It is not that they behave differently, but that almost always when you add a control declaratively you set the ID right away. Try adding a label with no ID to a page and browse the control collection and check its ID, it will be null (make sure not to show its clientID since it would get the ID filled):

<asp:Label runat="server">something</asp:Label>

Also note that if you run it like that you get an span with no ID.

eglasius
The answer is so blindingly obvious I feel foolish 4 having asked the question in the 1st place. How could i not see the implications of container.Controls.Add(new CheckBox()); In my defense I'd like to point out that due to a new "health kick" I stupidly decided 2 stop drinking caffeine on Monday;)
rism
+2  A: 

Freddy is correct.

Your are responsible for setting IDs inside the InstantiateIn method. And it makes sense that ClientID auto-generates them if not specified otherwise.

The declarative controls get their IDs assigned by a page builder during compilation of a page. If you were to look at one of temp .cs files generated in the "Temporary ASP.NET Files folder", you'd find something like this (pragmas stripped):

//creating a template field, where CopiledBindableTemplateBuilder is the ITemplate
//and its InstantiateIn = @__BuildControl__control9
@__ctrl.ItemTemplate = new System.Web.UI.CompiledBindableTemplateBuilder(
    new System.Web.UI.BuildTemplateMethod(this.@__BuildControl__control9),
    new System.Web.UI.ExtractTemplateValuesMethod(this.@__ExtractValues__control9));

//and @__BuildControl__control9 calling @__BuildControlButton1
private global::System.Web.UI.WebControls.Button @__BuildControlButton1()
{
    global::System.Web.UI.WebControls.Button @__ctrl;

    @__ctrl = new global::System.Web.UI.WebControls.Button();
    this.Button1 = @__ctrl;
    @__ctrl.ApplyStyleSheetSkin(this);
    @__ctrl.ID = "Button1"; //<-- here it gets an ID
    @__ctrl.Text = "Button";
    return @__ctrl;
}
Ruslan