views:

877

answers:

3

I have a GridView with dynamically created image buttons that should fire command events when clicked. The event handling basically works, except for the very first time a button is clicked. Then, the postback is processed, but the event is not fired.

I have tried to debug this, and it seems to me, that the code executed before and after the first click is exactly the same as for any other clicks. (With the exception that in the first click, the event handler is not called.)

There is some peculiarity in that: The buttons which fire the event are created dynamically through databinding, i.e. databinding must be carried out twice in the page lifecycle: Once on load, in order to make the buttons exist (otherwise, events could not be handled at all), and once before rendering in order to display the new data after the events have been processed.

I have read these posts but they wouldn't match my situation: http://stackoverflow.com/questions/1953448/asp-net-linkbutton-onclick-event-is-not-working-on-home-page, http://stackoverflow.com/questions/96837/linkbutton-not-firing-on-production-server, http://stackoverflow.com/questions/953093/asp-net-click-event-doesnt-fire-on-second-postback

To the details: The GridView contains image buttons in each row. The images of the buttons are databound. The rows are generated by GridView.DataBind(). To achieve this, I have used the TemplateField with a custom ItemTemplate implementation. The ItemTemplate's InstantiateIn method creates the ImageButton and assigns it the according event handler. Further, the image's DataBinding event is assigned a handler that retrieves the appropriate image based on the respective row's data.

The GridView is placed on a UserControl. The UserControl defines the event handlers for the GridView's events. The code roughly looks as follows:

private DataTable dataTable = new DataTable();
protected SPGridView grid;

protected override void OnLoad(EventArgs e)
{
    DoDataBind(); // Creates the grid. This is essential in order for postback events to work.
}

protected override void Render(HtmlTextWriter writer)
{
    DoDataBind();
    base.Render(writer); // Renews the grid according to the latest changes
}

void ReadButton_Command(object sender, CommandEventArgs e)
{
    ImageButton button = (ImageButton)sender;
    GridViewRow viewRow = (GridViewRow)button.NamingContainer;
    int rowIndex = viewRow.RowIndex;

    // rowIndex is used to identify the row in which the button was clicked,
    // since the control.ID is equal for all rows.
    // [... some code to process the event ...]
}

private void DoDataBind()
{
    // [... Some code to fill the dataTable ...]

    grid.AutoGenerateColumns = false;

    grid.Columns.Clear();

    TemplateField templateField = new TemplateField();
    templateField.HeaderText = "";
    templateField.ItemTemplate = new MyItemTemplate(new CommandEventHandler(ReadButton_Command));
    grid.Columns.Add(templateField);

    grid.DataSource = this.dataTable.DefaultView;
    grid.DataBind();
}

private class MyItemTemplate : ITemplate
{
    private CommandEventHandler commandEventHandler;

    public MyItemTemplate(CommandEventHandler commandEventHandler)
    {
        this.commandEventHandler = commandEventHandler;
    }

    public void InstantiateIn(Control container)
    {
        ImageButton imageButton = new ImageButton();
        imageButton.ID = "btnRead";
        imageButton.Command += commandEventHandler;
        imageButton.DataBinding += new EventHandler(imageButton_DataBinding);
        container.Controls.Add(imageButton);
    }

    void imageButton_DataBinding(object sender, EventArgs e)
    {
        // Code to get image URL
    }
}

Just to repeat: At each lifecycle, first the OnLoad is executed, which generates the Grid with the ImageButtons. Then, the events are processed. Since the buttons are there, the events usually work. Afterwards, Render is called, which generates the Grid from scratch based upon the new data. This always works, except for the very first time the user clicks on an image button, although I have asserted that the grid and image buttons are also generated when the page is sent to the user for the first time.

Hope that someone can help me understand this or tell me a better solution for my situation.

+1  A: 

A couple problems here. Number one, there is no IsPostBack check, which means you're databinding on every load... this is bound to cause some problems, including events not firing. Second, you are calling DoDataBind() twice on every load because you're calling it in OnLoad and Render. Why?

Bind the data ONCE... and then again in reaction to events (if needed).

Other issue... don't bind events to ImageButton in the template fields. This is generally not going to work. Use the ItemCommand event and CommandName/CommandArgument values.

Finally... one last question for you... have you done a comparison (windiff or other tool) on the HTML rendered by the entire page on the first load, and then subsequent loads? Are they EXACTLY the same? Or is there a slight difference... in a control name or PostBack reference?

Bryan
Hello Bryan, thanks for the response. The reason why I bind it twice can be found above: First time in order for the controls being there so the events can be fired; second time in order to update the view according to changes made by the event handlers.If I do not databind the gridview after postback, it turns out to be empty (although viewstate is turned on) =>?? Therefore I have to databind the grid after every postback as well.
chiccodoro
The problem with CommandField is that I cannot databind the image (?). I have to define one and the same image for all rows. In my case, in contrast, I want the image displayed to depend on the data of the respective row. The BoundField in turn does not provide command events. So the Template is the only way I found. Please correct me if I'm wrong, I even hope that this is the case!
chiccodoro
Diff between the two HTML files showed differences (only) in __REQUESTDIGEST, __EVENTVALIDATION, and of course in __VIEWSTATE
chiccodoro
+1  A: 

Well I think the event dispatching happens after page load. In this case, its going to try to run against the controls created by your first data-binding attempt. This controls will have different IDs than when they are recreated later. I'd guess ASP.NET is trying to map the incoming events to a control, not finding a control, and then thats it.

I recommend taking captures of what is in the actual post.

ASP.NET is pretty crummy when it comes to event binding and dynamically created controls. Have fun.

Frank Schwieterman
Thank you, Frank. The funny thing is that it generally works this way. The problem only appears after the very first postback.
chiccodoro
I have found two other interesting details: - If I use normal **Buttons instead of ImageButtons** (in the exact same place, i.e. still using MyItemTemplate but instantiating Button instead of ImageButton in "InstantiateIn", **it works fine.** - Due to some logic in my code which is not listed here, there are cases where the "DoDataBind()" method in OnLoad is skipped. Still the control is rendered by the DoDataBind() call in Render(). But in the PostBack cycle *after* that, the user's click interaction will not be processed.
chiccodoro
I.e.: 1. OnLoad() -> DoDataBind skipped. 1. Render() -> DoDataBind executed, controls are rendered. 1. HTML sent to client. 1. Client clicks on ImageButton. 1. PostBack sent to server. 1. OnLoad() -> DoDataBind executed. 1. Event dispatching fails. 1. Render() -> DoDataBind executed. 1. HTML sent to client. 1. Client clicks on ImageButton. 1. PostBack sent to server. 1. OnLoad() -> DoDataBind executed. 1. Event is fired. ...=> When I assert that DoDataBind() is always executed twice before sending the content to the client, it works!?
chiccodoro
This double binding is typical. The first binding is needed to build the control tree so the events can be dispatched. Once the event is dispatched, a second databind is usually needed to account for the change. Pretty crummy huh? Read up on Control.EnsureChildControls() and the process will make a little more sense
Frank Schwieterman
A: 

Since in my opinion this is a partial answer, I re-post it this way:

  • If I use normal Buttons instead of ImageButtons (in the exact same place, i.e. still using MyItemTemplate but instantiating Button instead of ImageButton in "InstantiateIn", it works fine.

  • If I assert that DoDataBind() is always executed twice before sending the content to the client, it works fine with ImageButtons.

Still puzzled, but whatever...

chiccodoro