Friday, December 2, 2011

Working with Buttons inside a GridView Control

Working with Buttons inside a GridView Control

This post is as much for my benefit as anyone elses. Every few months I find myself scratching my head, wondering how I can find all the pieces of information I need inside the handler for a button click on a grid view. Since I just had to do this again yesterday, this time, I'm going to record my findings here, so that next time I can come right to this page.

So here is the scenario: there is a GridView control on a page, bound to data in a database. It has multiple columns of read only data, but the last column contains a textbox and a button. When that button is pushed, I need to get the value of the text box, the orderid and the primary key of the current row in order to update a record in the database. I have found at least three ways to do this without getting too fancy.

      <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ID" DataSourceID="SqlDataSource1">
            <Columns>
                <asp:BoundField DataField="ID" HeaderText="ID" InsertVisible="False" ReadOnly="True"  SortExpression="ID" />
                <asp:BoundField DataField="NAME" HeaderText="NAME" SortExpression="NAME" />
                <asp:BoundField DataField="ORDERID" HeaderText="ORDERID" SortExpression="ORDERID" />
                <asp:BoundField DataField="CITY" HeaderText="CITY" SortExpression="CITY" />
                <asp:TemplateField>
                    <ItemTemplate>
                        <asp:TextBox" ID="TextBox1" runat="server" Text='<%# Eval("PHONE") %>'
                        <asp:LinkButton ID="LinkButton1" runat="server" OnClick="LinkButton1_Click">Get Row Info</asp:LinkButton>
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>

The first way I'm going to explore may be considered rather "ghetto" in the sense that while it works beautifully it does bend some rules so anyone who tries to adhere to common patterns and practices would probably frown upon it. However, as you'll see, it does have a number of advantages over some of the more official techniques so don't rule it out right away! To use this ghetto code, add a RowDataBound event to your gridview and use it to find each instance of your button control and add some new attributes to it containing the information you'll need for this row:

protected void gv_RowDataBound(object sender, System.Web.UI.WebControls.GridViewRowEventArgs e)

    {

        GridViewRow gvr = (GridViewRow)e.Row;



        if (e.Row.RowType == DataControlRowType.DataRow)

        {

            // Find your controls

            LinkButton LinkButton1 = (LinkButton)gvr.FindControl("LinkButton1");



            // this is ghetto, but you can, add orderid and other data to attributes so we can grab them later          

            int orderId = Convert.ToInt32(DataBinder.Eval(e.Row.DataItem, "OrderID"));



            LinkButton1.Attributes.Add("OrderID", orderId);         

        } // end if this is a real row

    }



Obviously, if you choose to do this your linkbutton will be rendered with an OrderID attribute <a href="..." OrderID="123" ... /> which you can grab later when posting back. For example, if you add OnClick="LinkButton_Click" to your linkbutton inside the gridview, then inside LinkButton_Click you can get the orderID like so:



    protected void LinkButton1_Click(object sender, EventArgs e)

    {

        LinkButton lb = (LinkButton)sender;

        int orderID = int.Parse(lb.Attributes["OrderID"]);

        // ...

    }



It's quick, it works and while it doesn't seem like the "Microsoft approved" method to me, it is more scalable than the counting the cells (If your OrderID is rendered as a label in the third column, you might use Convert.ToInt32(row.Cells(2).Text); to obtain the orderid). But what happens when you or someone else adds an additional column to the gridview in front of orderid? Suddenly your orderid might be something else entirely and if it still compiles and runs perhaps no one will even notice!


Let's explore two more quick examples that adhere to what Microsoft had in mind. The first, again assumes you add OnClick="LinkButton_Click" to your linkbutton inside the gridview and that you need not only the orderid, but also the Primary Key and the value of TextBox1:



    protected void LinkButton1_Click(object sender, EventArgs e)

    {

        LinkButton lb = (LinkButton)sender;

        GridViewRow row = (GridViewRow)lb.NamingContainer;



        // get the value of the textbox

        TextBox txt1 = row.Cells[4].FindControl("TextBox1") as TextBox;

        string phoneNumber = txt1.Text;



        // get the Primary Key Value

        int ID = GridView1.DataKeys[row.RowIndex].Value;



        // ... Do something with these values like update a row in a database

    }



It is unfortunate that you cannot address a cell by anything other than its Index as if you later need to add another column in front of this one, your application will break. You might think this is no big deal, but consider what happens if you have this sort of code applied to two or three columns, and each one addresses multiple cells. Then your boss asks you to add a new column. Trust me, you will groan, as you now have to recalculate the index of all cells addressed in this fashion. It can turn a 2 minute task into a 20 minute task.


As an alternative to the OnClick Event applied to an individual button, you can instead use the RowCommand Event of the GridView. This allows you to store some data inside the ComandArgument property of your button, which is arguably similar to the custom attribute method discussed at the beginning of this article, but since you can only set one command argument, if you need three pieces of information from it you are faced with a choice of either combining the information through concatenation - setting your command argument up as a string you plan to split apart later e.g. "OrderID=123|ProductID=456" or you are back to counting cells. And what's more, despite what this MSDN article implies, the CommandArgument does NOT contain the RowIndex by default, you would need to add it there yourself which can be done declaratively like so:

<asp:LinkButton ID="LinkButton1" runat="server" CommandArgument='<%# gv.Rows.Count.ToString() %>' CommandName="UpdatePhone">Get Row Info</asp:LinkButton>

Then your RowCommand Handler would look something like this:



       protected void GridView1_RowCommand(Object sender, GridViewCommandEventArgs e)

       {

            if (e.CommandName == "UpdatePhone")

            {





                // Convert the row index stored in the CommandArgument

                // property to an Integer.

                int index = Convert.ToInt32(e.CommandArgument);





                // Retrieve the row that contains the button clicked

                // by the user from the Rows collection.

                GridViewRow row = GridView1.Rows[index];



                // get the value of the textbox

                TextBox txt1 = row.Cells[4].FindControl("TextBox1") as TextBox;

                string phoneNumber = txt1.Text;



                // get the Primary Key Value

                int ID = GridView1.DataKeys[row.RowIndex].Value;



                // ... Do something with these values like update a row in a database

            }

        }

No comments:

Post a Comment