Welcome to the third installment of my series on the SPGridView.

I had planned to tackle menus and the MenuField control in this installment but I got sidetracked by a very interesting problem posted by Josh as a comment to Part 1 of this series. His problem was that for some reason the RowCommand event failed to fire on a button click when this button was created inside a TemplateField. A quick search on Google indicated that he was by no means the only one to experience this problem. So I figured I’d try to figure out what was happening and how to solve this.

To start, let’s talk about TemplateFields. Most of you who have worked with the ASP.NET GridView control or derivatives thereof have heard about them. In markup, you can define a TemplateField as follows:

<asp:TemplateField>
  <ItemTemplate>
    <asp:Label ID="Label1" runat="server" Text="A label" />
    <asp:Label ID="Label2" runat="server" Text="A second label" />
  </ItemTemplate>
</asp:TemplateField>

In this way, you can make a column in your grid contain just about any control, which is very powerful indeed.
In our ASPGridView control we do not have any markup. So how do we achieve the same in code? When using a code-only approach, what we need to do is create a class that implements the ITemplate interface. In this example, we’ll create a template that contains a single Button control:

public class TemplatedButtonTemplate : ITemplate
{
  private string text;
  private string commandName;
 
  public TemplatedButtonTemplate( string text, string commandName )
  {
    this.text = text;
    this.commandName = commandName;
  }
 
  public void InstantiateIn( Control container )
  {
    LinkButton button = new LinkButton();
    button.Text = this.text;
    button.DataBinding += new EventHandler( button_DataBinding );
    container.Controls.Add( button );
  }
 
  void button_DataBinding( object sender, EventArgs e )
  {
    // Do nothing yet
  }
}

We then assign an instance of this class to the ItemTemplate property of the TemplateField control:

private void GenerateColumns()
{
  // Creation of other columns left out for brevity
 
  TemplateField templatedButton = new TemplateField();
  templatedButton.ItemTemplate = new TemplatedButtonTemplate( "Edit Row", "Edit" );
  grid.Columns.Add( templatedButton );
}

Notice that you can pass any argument you want to the class constructor. In our example, we pass the text for the Button and the name of the command we’d like to send to our RowCommand event.

To find out what was happening in Josh’ situation I extended the ASPGridView from the first part of the series to include two additional columns; one bog-standard ButtonField and one TemplateField containing a single LinkButton control. I set the Text and the CommandName properties of both Buttons to the same values, expecting them to work identically. But if that were the case there would be no reason for me to write this article, would there? The standard ButtonField worked as expected, setting the row to Edit mode. But the LinkButton didn’t. This might seem disappointing but reproducing the problem is usually about 70 percent of solving it. So I was actually quite pleased.

But I had to dig a little deeper to get to the root of the problem. I used Google Chrome’s Inspect method to see what was going on at the HTML markup level. What I noticed was that both Buttons generated a __doPostBack JavaScript method for their OnClick event. There were two differences though. The ButtonField’s method contained a value for the event argument parameter where the LinkButton’s method left this blank. Also, the event target identifiers were different. The first is used in the RowCommand event to indicate which command should be processed (in our case, the Edit command). The latter is used by ASP.NET to determine which control should handle the PostBack event.

The solution turns out to be surprisingly simple. In the DataBinding event of the TemplatedButtonTemplate, I override the href attribute of the LinkButton with the correct value of the __doPostBack method, using the unique identifier of the SPGridView control and the command name we passed in the constructor. The complete code for our template control is as follows:

public class TemplatedButtonTemplate : ITemplate
{
  private string text;
  private string commandName;
  private string containingGridClientId;
 
  public TemplatedButtonTemplate( string text, string commandName, string containingGridClientId )
  {
    this.text = text;
    this.commandName = commandName;
    this.containingGridClientId = containingGridClientId;
  }
 
  public void InstantiateIn( Control container )
  {
    LinkButton button = new LinkButton();
    button.Text = this.text;
    button.DataBinding += new EventHandler( button_DataBinding );
    container.Controls.Add( button );
  }
 
  void button_DataBinding( object sender, EventArgs e )
  {
    LinkButton button = sender as LinkButton;
 
    int rowId = ((SPGridViewRow)button.NamingContainer).RowIndex;
    string command = String.Format( "{0}${1}", this.commandName, rowId );
    button.Attributes["href"] = String.Format("javascript:__doPostBack('{0}','{1}')",
                                                     this.containingGridClientId, command );
  }
}

And the instantiation code becomes:

templatedButton.ItemTemplate = new TemplatedButtonTemplate( "Edit Row", "Edit", grid.UniqueID );

Josh pointed out an alternative method to retrieve the unique identifier by digging a little deeper into the DataBinding event arguments which simplifies the constructor a bit:

void button_DataBinding( object sender, EventArgs e )
{
  LinkButton button = sender as LinkButton;
  SPGridViewRow row = (SPGridViewRow)button.NamingContainer;
  SPGridView grid = (SPGridView)row.NamingContainer;
 
  int rowId = row.RowIndex;
  string command = String.Format( "{0}${1}", this.commandName, rowId );
  button.Attributes["href"] = String.Format("javascript:__doPostBack('{0}','{1}')",
                                                   grid.UniqueID, command );
}

Using this code both columns behave in exactly the same way and the RowCommand event of the SPGridView fires as expected.

I hope this post was useful to you. If so, let me know. If you find that there are things missing, I’d love to hear from you, too.

Erik

If you liked this post, please click on one of the advertisements below. Thanks!


8 Responses to “Building A SPGridView Control – Intermezzo: TemplateFields and the RowCommand Event”

  1. 1 Josh

    Well, it was obviously useful to me. So thanks again!
    And of course my best wishes for your new career …

  2. 2 Zeta

    Great post!

    one question: how can add one more button into every templatefield row?

    Looking forward to your answer!

  3. 3 Erik Burger

    Hi Zeta,

    Thanks! I am glad you found the post useful.

    To add another button to a TemplateField row you can just add another instance of a button to the control in the InstantiateIn method. So something like this:

    LinkButton button = new LinkButton();
    button.Text = "One button";
    button.DataBinding += new EventHandler( button_DataBinding );
    container.Controls.Add( button );
     
    // Add another button
    LinkButton button2 = new LinkButton();
    button2.Text = "Another button";
    button2.DataBinding += new EventHandler( button2_DataBinding );
    container.Controls.Add( button2 );

    Does this help? If I misunderstood your question please let me know. You can also drop me a line at eburger at reversealchemy dot net.

    Regards,

    Erik

  4. 4 Shail

    Hi Erik,

    First of all its and awesome post. And it worked as expected.
    Now I would like to move directly to my problem as I am bit restless now.

    I have a SPGridView embedded on a web part with following config:
    1. Has got paging, sorting and data displayed from a DB view. All these things are configured in CreateChildControl function.
    2. Data binding happens on Render() function of teh webpart.
    3. A checkbox template column on the beginning of spgrid

    I want my uses to be able to select multiple entried from the grid and click a button on the webpart. Then the grid will perform action on selected entried in the DB.

    But, when i click any button embedded on the webpart, postback happens as expacted. when I try to surf the grid in the buttons click event handler, surprisingly I come to know that, the grid has got zero rows…

    I tried everything like binding the grid again before checking the row count in button’s event handler, But leads to fill recreation of the grid and all the changes I made to grid(checking the check boxes) are lost.

    I need a place where I can see the status of checkboxes (after they are modified by user)and use them to call Stored procedure etc.

    Any idea whats happenning with me ?
    I can post you the code if you would like to have a look on the same.

    Million Thanks in advance.
    Shail.

  5. 5 Erik Burger

    Hi Shail,

    Thank you very much for your compliment! In my experience there can be a lot of reasons why your scenario is not (yet) working. So yes, please send me your code and I will take a look at it as soon as I can. My e-mail address is eburger at reversealchemy dot net.

    Cheers,

    Erik

  6. 6 Shail

    Hey Erik,

    I have sent you the code. Please check your inbox and have a look on the Spam folder if not found in the inbox 🙂

    Thanks Mate.

  7. 7 Vlad

    Hi,
    I have the same problem Shail does: SPGridView.Rows.Count=0 in the event handler even though everything displays correctly on the page. Did you guys resolve this issue?
    Thanks

  8. 8 Erik Burger

    Hi Vlad,

    Yes, Shail resolved his issue by moving the DataBind() method our of the Render method and into CreateChildControls. See if that works for you. I will give you the same comment I did Shail (after congratulating him, of course ;)) and advice that you try to move the DataBind() method ahead in the lifecycle one method at a time. This because I am unsure as to whether CreateChildControls is the best place for it.

    Let us know how you do!

    Best of regards,

    Erik

Leave a Reply


*