Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template field disappears

I have a gridview used to display records based on a selected value in a drop down.

Within the gridview, there are a dynamic number of bound field columns added in code behind, followed by a drop down list in a template field and a button in a second template field.

The problem is, when I click on the button, the template fields disappear.

GRID VIEW

<asp:UpdatePanel ID="upnlDetail" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
        <asp:GridView ID="gvDetails" runat="server"
            AutoGenerateColumns="false" 
            SkinID="gridviewGray" 
            CellPadding="3" 
            OnRowCommand="gvDetails_RowCommand" 
            OnRowDataBound="gvDetails_RowDataBound" 
            AllowSorting="true">
            <Columns>
                <asp:TemplateField>
                    <ItemTemplate>
                        <asp:DropDownList ID="ddlStatus" runat="server"
                            AutoPostBack="true" 
                            OnSelectedIndexChanged="ddlStatus_SelectedIndexChanged">
                        </asp:DropDownList>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField>
                    <ItemTemplate>
                        <asp:Button ID="btnSave" runat="server"
                            CommandName="Save" 
                            CommandArgument="<%# ((GridViewRow) Container).RowIndex %>" 
                            Text="Save" />
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>                                
    </ContentTemplate>
</asp:UpdatePanel>

CODE BEHIND

while (gvDetails.Columns.Count > 2)  //Don't remove the rightmost columns
{ 
    gvDetails.Columns.RemoveAt(0);
}

gvDetails.Columns.Insert(0, GridViewTools.CreateBoundField(
                                "Request.Amount", "Amount", "Money", 50));
gvDetails.Columns.Insert(0, GridViewTools.CreateBoundField(
                                "Request.CustomerName", "Customer", "comment", 200));
gvDetails.Columns.Insert(0, GridViewTools.CreateBoundField(
                                "Request.BillAccountFormatted", "Account", "text", 100));
gvDetails.Columns.Insert(0, GridViewTools.CreateBoundField(
                                "Request.Id", "Request", "int", 50));

gvDetails.DataSource = dt;
gvDetails.DataBind();

Method GridViewTools.CreateBoundField is a custom method that sets my default boundfield attributes and returns the BoundField object.

public static BoundField CreateBoundField(...){...}

In testing it, the gridview populates as desired on the initial load. However, the RowCommand event does not fire and both template fields disappear when the button is clicked. The next gridview.RowDataBound event then throws a null object error because the drop down list is no longer found in the row.

If I remove lines adding the bound fields [Columns.Insert], the RowCommand fires as expected when btnSave is clicked and the template fields persist. If I add even a single bound field column again, the RowCommand does not fire and the template fields disappear.

Any suggestions? Why would adding a new column to a gridview invalidate the RowCommand event for a pre-existing button and make the template fields disappear?

UPDATES I've removed the template fields from the Grid View declaration and added the controls to a bound field instead, but still get the same result. The show on the initial load, but disappear once clicked.

I've also looked at the grid view in the Page_Unload event, and it still contains the columns at that point, even though they don't show on screen. Is there any event I can capture after Page_Unload?

like image 676
Wes H Avatar asked Apr 09 '18 19:04

Wes H


2 Answers

Build everything in code behind. It is not a matter of preference. Please read Dynamic Web Server Controls and View State

WebForm:

<asp:GridView ID="gvDetails" runat="server"
    AutoGenerateColumns="false" 
    SkinID="gridviewGray" 
    CellPadding="3" 
    OnRowDataBound="OnRowDataBound"
    OnRowCommand="gvDetails_RowCommand"
    AllowSorting="true">
</asp:GridView>

Code behind:

public class GridViewDropDownListTemplate : ITemplate
{
    public void InstantiateIn(Control container)
    {
        DropDownList ddlStatus = new DropDownList();
        ddlStatus.ID = "ddlStatus";
        ddlStatus.Items.Add(new ListItem("Status 1"));
        ddlStatus.Items.Add(new ListItem("Status 2"));
        ddlStatus.Items.Add(new ListItem("Status 3"));
        ddlStatus.Items.Add(new ListItem("Status 4"));
        ddlStatus.AutoPostBack = true;
        ddlStatus.SelectedIndexChanged += ddlStatus_SelectedIndexChanged;
        container.Controls.Add(ddlStatus);
    }

    public void ddlStatus_SelectedIndexChanged(object sender, EventArgs e)
    {
    }
}

public class GridViewButtonTemplate : ITemplate
{
    public void InstantiateIn(Control container)
    {
        Button btnSave = new Button();
        btnSave.ID = "btnSave";
        btnSave.Text = "Save";
        container.Controls.Add(btnSave);
    }
}

public partial class Default : System.Web.UI.Page
{
    object[] data = new[]
    {
        new { Amount = 12500.00, Account = "1234-567-89" },
        new { Amount = 87000.00, Account = "0000-999-88" }
    };

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            BuildBoundFields();
        }

        BuildTemplateFields();
        BindData();
    }

    protected void BuildBoundFields()
    {
        // Amount bound field
        BoundField boundFieldAmount = new BoundField();
        boundFieldAmount.DataField = "Amount";
        boundFieldAmount.HeaderText = "Amount";
        boundFieldAmount.SortExpression = "Amount";
        boundFieldAmount.ItemStyle.Width = Unit.Pixel(100);
        gvDetails.Columns.Add(boundFieldAmount);

        // Account bould field
        BoundField boundFieldAccount = new BoundField();
        boundFieldAccount.DataField = "Account";
        boundFieldAccount.HeaderText = "Account";
        boundFieldAccount.SortExpression = "Account";
        boundFieldAccount.ItemStyle.Width = Unit.Pixel(250);
        gvDetails.Columns.Add(boundFieldAccount);

        // ...
    }

    protected void BuildTemplateFields()
    { 
        // Status template field
        TemplateField statusTemplateField = new TemplateField();
        statusTemplateField.ItemTemplate = new GridViewDropDownListTemplate();
        gvDetails.Columns.Add(statusTemplateField);

        // Save template field
        TemplateField saveTemplateField = new TemplateField();
        saveTemplateField.ItemTemplate = new GridViewButtonTemplate();
        gvDetails.Columns.Add(saveTemplateField);
    }

    protected void BindData()
    {
        gvDetails.DataSource = data;
        gvDetails.DataBind();
    }

    protected void OnRowDataBound(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            Button btnSave = (Button)e.Row.FindControl("btnSave");
            if (btnSave != null)
            {
                btnSave.CommandArgument = e.Row.RowIndex.ToString();
            }
        }
    }

    protected void gvDetails_RowCommand(object sender, GridViewCommandEventArgs e)
    {
    }
}
like image 190
dpant Avatar answered Oct 02 '22 19:10

dpant


This is interesting. It would appear to happen only when the BoundFields are Inserted. When adding BoundFields to the end of the Grid, it does work.

gvDetails.Columns.Add(GridViewTools.CreateBoundField("Request.Amount", "Amount", "Money", 50));

But you probably want the DropDown and the Button at the end of the GridView. So you could make those dynamic as well, instead of just adding data to the DropDownList. So the first thing to do is add 2 extra dummy BoundField columns to hold the Button and DropDownList.

gvDetails.Columns.Add(GridViewTools.CreateBoundField("", "DropDownList", "int", 50));
gvDetails.Columns.Add(GridViewTools.CreateBoundField("", "Button", "int", 50));

Now change the RowDataBound event to add the controls.

protected void gvDetails_RowDataBound(object sender, GridViewRowEventArgs e)
{
    //check if the row is a datarow
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        //create a dropdownlist and add some data
        DropDownList ddl = new DropDownList();
        ddl.ID = "ddlStatus";
        ddl.Items.Add(new ListItem() { Text = "Option 1", Value = "1" });
        ddl.Items.Add(new ListItem() { Text = "Option 2", Value = "2" });
        ddl.Items.Add(new ListItem() { Text = "Option 3", Value = "3" });

        //create a save button
        Button btn = new Button();
        btn.ID = "btnSave";
        btn.CommandName = "save";
        btn.Text = "Save";
        btn.CommandArgument = e.Row.RowIndex.ToString();

        //add the controls to the last 2 cells
        e.Row.Cells[e.Row.Cells.Count - 2].Controls.Add(ddl);
        e.Row.Cells[e.Row.Cells.Count - 1].Controls.Add(btn);
    }
}

But for this to work you need to place the DataBind() of the GridView outside the IsPostBack check, but keep the adding of the BoundFields inside it.

protected void Page_Load(object sender, EventArgs e)
{
    if (IsPostBack == false)
    {
         //add the boundfields here
    }

    //bind gridview on every postback
    gvDetails.DataSource = dt;
    gvDetails.DataBind();
}

The ASPX now looks like this

<asp:GridView ID="gvDetails" runat="server" AutoGenerateColumns="false" 
  OnRowDataBound="gvDetails_RowDataBound" OnRowCommand="gvDetails_RowCommand"></asp:GridView>   

Well, you now have a fully dynamically created GridView.

like image 33
VDWWD Avatar answered Oct 02 '22 17:10

VDWWD