Controls in ItemTemplate can't be called in Code Behind

David Tunnell picture David Tunnell · Nov 7, 2013 · Viewed 8.9k times · Source

I have server controls such as a popup with a gridview inside template fields in another gridview:

  <asp:TemplateField HeaderText="Actions">
                <ItemTemplate>
                    <asp:Button ID="viewHoursButton" runat="server" Text="View Hours" OnClick="viewHoursButton_OnClick" />
                    <ajaxToolkit:ModalPopupExtender ID="viewHoursPopup" runat="server"
                        TargetControlID="viewHoursButton"
                        PopupControlID="viewHoursPanel"
                        CancelControlID="closeInfoPanelButton2"
                        DropShadow="true">
                    </ajaxToolkit:ModalPopupExtender>
                    <asp:Panel ID="viewHoursPanel" runat="server" CssClass="infoPanel">
                        <asp:Button ID="closeInfoPanelButton2" runat="server" Text="X" CssClass="closeInfoPanelButton"  />
                        <asp:Label ID="viewHoursLabel" runat="server" Text="Label"></asp:Label>
                        <asp:GridView ID="viewHoursGridView" runat="server" AllowPaging="True" AutoGenerateColumns="False"
                            DataSourceID="SqlDataSource6" DataKeyNames="NonScrumStoryId,PK_DailyTaskHours"
                            BackColor="#DEBA84" BorderColor="#DEBA84" BorderStyle="None" BorderWidth="1px"
                            CellPadding="3" CellSpacing="2" Width="94%" OnRowDeleting="viewHoursGridView_OnRowDeleting"
                            OnRowDataBound="viewHoursGridView_OnRowDataBound" CssClass="centerGridView">
                            <Columns>
                                <asp:BoundField DataField="ActivityDate" HeaderText="Activity Date" SortExpression="ActivityDate"
                                    DataFormatString="{0:MM/dd/yyyy}" />
                                <asp:BoundField DataField="Hours" HeaderText="Hours" SortExpression="Hours" />
                                <asp:BoundField DataField="Notes" HeaderText="Notes" SortExpression="Notes" />
                                <asp:BoundField DataField="CreateDate" HeaderText="Created Date" SortExpression="CreateDate"
                                    Visible="false" />
                                <asp:CommandField ShowDeleteButton="True" />
                            </Columns>
                            <FooterStyle BackColor="#F7DFB5" ForeColor="#8C4510" />
                            <HeaderStyle BackColor="#7fc041" Font-Bold="True" ForeColor="White" />
                            <PagerStyle ForeColor="#8C4510" HorizontalAlign="Center" />
                            <RowStyle BackColor="#FFF7E7" ForeColor="#8C4510" />
                            <SortedAscendingCellStyle BackColor="#FFF1D4" />
                            <SortedAscendingHeaderStyle BackColor="#B95C30" />
                            <SortedDescendingCellStyle BackColor="#F1E5CE" />
                            <SortedDescendingHeaderStyle BackColor="#93451F" />
                        </asp:GridView>
                        <asp:SqlDataSource ID="SqlDataSource6" runat="server" ConnectionString="<%$ ConnectionStrings:ApplicationServices %>"
                            SelectCommand="
                           SELECT [DailyTaskHours].[PK_DailyTaskHours]
                                ,[DailyTaskHours].[NonScrumStoryId]
                                ,[DailyTaskHours].[Hours]
                                ,[DailyTaskHours].[Notes]
                                ,[DailyTaskHours].[ActivityDate]
                                ,[DailyTaskHours].[CreateDate]
                            FROM [NonScrumStory]
                                ,[DailyTaskHours]
                            WHERE [DailyTaskHours].[NonScrumStoryId] = @nonScrumStoryId
                                AND [NonScrumStory].[PK_NonScrumStory] = @nonScrumStoryId
                            ORDER BY [ActivityDate] DESC
                            "
                            DeleteCommand="
                            DELETE
                            FROM [DailyTaskHours]
                            WHERE ([PK_DailyTaskHours] = @setDailyPKDeleteParam)
                            ">
                            <SelectParameters>
                                <asp:QueryStringParameter Name="nonScrumStoryId" Type="String" />
                            </SelectParameters>
                            <DeleteParameters>
                                <asp:QueryStringParameter Name="setDailyPKDeleteParam" Type="Int32" />
                            </DeleteParameters>
                        </asp:SqlDataSource>
                    </asp:Panel>
                    <asp:Button ID="addHoursButton" runat="server" Text="Add Hours" OnClick="addHoursButton_OnClick" />
                    <asp:Button ID="editButton" runat="server" Text="Edit" OnClick="editButton_OnClick" />
                    <asp:Button ID="deleteButton" runat="server" Text="Delete" OnClick="deleteButton_OnClick" />
                </ItemTemplate>
            </asp:TemplateField>

I cant move them out of the gridview because it is necessary for the call to the popup to work correctly.

However my code behind is not recognizing some of the ID's within the ItemTemplate:

protected void viewHoursButton_OnClick(object sender, EventArgs e)
{
    viewHoursPopup.Show();
    viewHoursGridView.DataBind();
}

For example, the _OnClick method work even though the button is inside the ItemTemplate but the two method calls are not recognized:

enter image description here

How do I fix this issue?

Answer

danludwig picture danludwig · Nov 7, 2013
protected void viewHoursButton_OnClick(object sender, EventArgs e)
{
    var viewHoursPopup = parentGridView.FindControl("viewHoursPopup")
        as WebControl;
    var viewHoursGridView = parentGridView.FindControl("viewHoursGridView");
    if (viewHoursPopup != null && viewHoursGriView != null)
    {
         viewHoursPopup.Show();
        viewHoursGridView.DataBind();
    }
}

...or...

protected void viewHoursButton_OnClick(object sender, EventArgs e)
{
    var viewHoursButton = (Button)sender;
    var viewHoursPopup = viewHoursButton.Parent.FindControl("viewHoursPopup")
        as WebControl;
    var viewHoursGridView = viewHoursButton.Parent.FindControl("viewHoursGridView");
    if (viewHoursPopup != null && viewHoursGriView != null)
    {
         viewHoursPopup.Show();
        viewHoursGridView.DataBind();
    }
}

Why?

ItemTemplates, RowTemplates, EditTemplates, etc, are all nested in what webforms calls NamingContainers. You cannot access these controls because they are injected dynamically at runtime, their control ID's are not automatically mapped by the codebehind's partial auto-generated class.

There are 2 solutions:

  1. Wrap the content of your Template into a UserControl. That UserControl's codebehind will have compile-time access to any controls that are not further nested into other Templates. To enable interaction between the UserControl and the parent Template that contains it, expose events and public properties on the UserControl

  2. Use .FindControl(string controlID) to "search" the containing control's current Templates to find the control you are looking for. Just be sure to check them against null before trying to invoke anything on them, and that they are cast to the appropriate type based on what methods you intend to invoke on them.