1

I've done some searching prior to asking, and although this post is close, it doesn't work for my scenario.

What I find is that the "delete" button in my template field seems to fire, but the click event does not trigger. Yet the second time you click the button it works as expected.

So, breaking the code down, I am binding my data to a GridView, using a `SqlDataSource.

My page load event starts as follows:

if (!Page.IsPostBack)  
{  
    externalUserDataSource.ConnectionString = "some connection string";  
}

My data source is as follows:

<asp:SqlDataSource ID="externalUserDataSource" runat="server" 
    ConflictDetection="CompareAllValues" SelectCommand="uspGetExternalUsersByTeam"
SelectCommandType="StoredProcedure" ProviderName="System.Data.SqlClient">
<SelectParameters>
    <asp:SessionParameter Name="TeamID" SessionField="TeamID" Type="Int32" />
</SelectParameters>
</asp:SqlDataSource>

And this is my GridView markup:

<asp:GridView ID="gridView" runat="server" AutoGenerateColumns="False" 
    BackColor="White" BorderColor="#3366CC" BorderStyle="None" BorderWidth="1px" 
    CellPadding="4" DataKeyNames="LoginID" DataSourceID="externalUserDataSource" 
    EnableModelValidation="True" OnRowDataBound="GridViewRowDataBound" TabIndex="3">
        <HeaderStyle BackColor="#003399" Font-Bold="True" ForeColor="White" />
        <FooterStyle BackColor="#99CCCC" ForeColor="#003399" />
        <PagerStyle BackColor="#99CCCC" ForeColor="#003399" HorizontalAlign="Left" />
        <RowStyle BackColor="LightGoldenrodYellow" />
        <SelectedRowStyle BackColor="#009999" Font-Bold="True" ForeColor="#CCFF99" />
        <Columns>
            <asp:BoundField DataField="RowID" HeaderText="Row ID" ReadOnly="True" 
                SortExpression="RowID" Visible="False" />
            <asp:BoundField DataField="LoginID" HeaderText="Login ID" ReadOnly="True" 
                SortExpression="LoginID" Visible="False" />
            <asp:BoundField DataField="EmailAddress" HeaderText="Email Address" 
                ItemStyle-VerticalAlign="Bottom" ReadOnly="True" SortExpression="AssociateName"/>
            <asp:BoundField DataField="TeamID" HeaderText="Team ID" ReadOnly="True" 
                SortExpression="TeamID" Visible="False" />
            <asp:CheckBoxField DataField="HasFIAccess" 
                 HeaderText="Has Access to&lt;br /&gt;Funding&lt;br/&gt;Illustrator" 
                 ItemStyle-HorizontalAlign="Center" ItemStyle-VerticalAlign="Bottom" 
                 ReadOnly="True"/>
            <asp:CheckBoxField DataField="HasALTAccess" 
                 HeaderText="Has Access to&lt;br /&gt;Asset Liability&lt;br/&gt;Tracker" 
                 ItemStyle-HorizontalAlign="Center" ItemStyle-VerticalAlign="Bottom"
                 ReadOnly="True"/>
            <asp:CheckBoxField DataField="HasFIAAccess" 
                 HeaderText="Has Access to&lt;br /&gt;Funding&lt;br/&gt;Illustrator App" 
                 ItemStyle-HorizontalAlign="Center" ItemStyle-VerticalAlign="Bottom"
                 ReadOnly="True"/>                    
            <asp:TemplateField>
            <ItemTemplate>
                <asp:Button runat="server" CssClass="additionsRow" ID="btnDeleteExternalUser" OnClick="DeleteExtUserButtonClick" 
                    CausesValidation="False" Text="Delete" 
                    CommandArgument='<%#Eval("TeamID") + "," + Eval("LoginID") + "," + Eval("EmailAddress") + "," + Eval("HasALTAccess")%>'/>                            
            </ItemTemplate>
            </asp:TemplateField>
        </Columns>
    </asp:GridView>

So, you can see that I am passing across some information in the button that is used in the event to ensure the correct data is deleted (which is why I cannot use the ButtonField, as suggested in the link above).

The last part to the puzzle is the GridView's databound event:

        protected void GridViewRowDataBound(object sender, 
                                        GridViewRowEventArgs e)
    {
        // if rowtype is not data row...
        if (e.Row.RowType != DataControlRowType.DataRow)
        {
            // exit with no further processing...
            return;
        }

        // get the ID for the selected record...
        var selectedId = DataBinder.Eval(e.Row.DataItem, "RowID").ToString();

        // create unique row ID...
        e.Row.ID = string.Format("ExtUserRow{0}", selectedId);

        // find the button delete for the selected row...
        var deleteButton = (Button)e.Row.FindControl("btnDeleteExtUser");

        // get the email address for the selected record...
        var selectedUser = DataBinder.Eval(e.Row.DataItem, "EmailAddress").ToString();

        // define the message text...
        var messageText = string.Format("OK to delete {0}?",
                                        selectedUser.Replace("'", "\\'")); 

        // add attribute to row delete action...
        this.AddConfirmMessage(deleteButton, messageText);
    }  

Where AddConfirmMessage simply assigns an onclick attribute to the control to ensure the user has to confirm the deletion.

Now, in every case the message pops up 'OK to delete abc@xyz.com?', but as stated earlier, the event assigned to the "delete" button does not trigger until the button is clicked a second time.

Strangely enough, I took this code from another page and modified accordingly, though it doesn't have this issue there:

        protected void DeleteExtUserButtonClick(object sender, 
                                            EventArgs e)
    {
        // get the buton which was clicked...
        var button = (Button)sender;

        // break the delimited array up...
        string[] argumentArray = button.CommandArgument.Split(',');

        // store the items from the array...
        string teamId = argumentArray[0];
        string loginId = argumentArray[1];
        string emailAddress = argumentArray[2];
        string hasAltAccess = argumentArray[3];

        using (var conn = new SqlConnection(Utils.GetConnectionString()))
        {
            // create database command...
            using (var cmd = new SqlCommand())
            {
                // set the command type...
                cmd.CommandType = CommandType.StoredProcedure;

                // set the name of the stored procedure to call...
                cmd.CommandText = "uspDeleteExternalUser";

                // create and add parameter to the collection...
                cmd.Parameters.Add(new SqlParameter("@TeamId", SqlDbType.Int));

                // assign the search value to the parameter...
                cmd.Parameters["@TeamId"].Value = teamId;

                // create and add parameter to the collection...
                cmd.Parameters.Add(new SqlParameter("@LoginId", SqlDbType.VarChar, 50));

                // assign the search value to the parameter...
                cmd.Parameters["@LoginId"].Value = loginId;

                // set the command connection...
                cmd.Connection = conn;

                // open the connection...
                conn.Open();

                // perform deletion of user...
                cmd.ExecuteNonQuery();
            }
        }

        // bind control to refresh content...
        ExtUsersGrid.DataBind();
    }

Have I missed something obvious? I am happy to modify if there are better ways to do this.

Edit 1: Following on from the discussions below, I have modified the following:

  • Removed the Onclick event property of the ButtonItem;
  • Set the CommandName and CommandArgument as suggested below, and updated the DataKeyNames to use RowID which is a unique ID from the data;
  • Assigned a RowCommand event for the GridView;
  • Assigned the delete code to the RowCommand event.

Following these changes, it still fires the row event code on the second click.

Edit 2: FYI - I've now removed the SqlDataSource and the associated code/references, and created a procedure to fill the dataset, which is called on Page_Load (inside the !Page.IsPostBack brackets). I started making the changes below to use the RowCommand event, but they still caused the same issue (i.e. the button will only fire on the second click). As using RowCommand meant converting the BoundFields to ItemTemplates, I reverted back to the button click event as it seemed pointless making all those changes for no gain. If anyone else can help me understand why it only triggers on the second click, would appreciate your input.

Community
  • 1
  • 1
Martin S
  • 211
  • 1
  • 4
  • 18
  • It seems you are not adding an eventhandler to the deleteButton, like in the example. – MrFox Mar 04 '13 at 11:24
  • Yes, sorry, I noticed that and updated my code - I was trying to add the event in code and forgot to add that back in before posting my question. The code is now up to date. – Martin S Mar 04 '13 at 11:30
  • It might be useful if I share my delete button event code as well, so will add an answer below. – Martin S Mar 04 '13 at 12:53
  • So where are you adding the eventhandler? Maybe I'm nitpicking here but it seems you still din't add the DeleteExtUserButtonClick to the button. – MrFox Mar 04 '13 at 15:42
  • In the Button field code is has `OnClick="DeleteExtUserButtonClick"` But actually, that has now gone as I've gone over to the method suggested by Praveen by initiating the RowCommand event, populating the grid as first postback, but I'm still having the same issue. and it's irrespective of which button, so I can Delete in row 1, and you see what looks like a postback but no code is triggered, and then click Delete in row 2, and you get the same. It's not until the second click that the code for the RowCommand fires. So whilst I have much better code, it's still giving me the same issue. – Martin S Mar 04 '13 at 16:05
  • Can I start a new question as the code has evolved so much but am still getting the same issue? – Martin S Mar 04 '13 at 16:15

2 Answers2

3

OK, frustratingly this was due to some code that for reasons still unknown, works elsewhere.
In the DataBound event, there was two lines of code:

        // get the associate name for the selected record...
        var selectedId = DataBinder.Eval(e.Row.DataItem, "RowID").ToString();
        // create unique row ID...
        e.Row.ID = string.Format("ExtUserRow{0}", selectedId);  

The process of applying an ID to the rows programatically seems to break the connection between the data and the events.
By removing these two lines of code, it works as expected.

Martin S
  • 211
  • 1
  • 4
  • 18
-1

Well instead you can do something like this.

Add a CommandName property to your GridView like this. Also note the changes in the CommandArgument property:

<asp:TemplateField>
    <ItemTemplate>
        <asp:Button runat="server" CssClass="additionsRow" ID="btnDeleteExtUser"  
                    CausesValidation="False" Text="Delete" 
                    CommandName="OnDelete"  
                    CommandArgument='<%# Container.DisplayIndex %>" '                              
</ItemTemplate>
</asp:TemplateField>

Code behind will look something like this. Note that I am using the RowCommand event of Gridview.

protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
    if(e.CommandName == "OnDelete")
    {
        int rowIndex = Convert.ToInt32(e.CommandArgument);
        // now you got the rowindex, write your deleting logic here.
        int ID = Convert.ToInt32(myGrid.DataKeys[rowIndex]["ID"].Value);
        // id will return you the id of the row you want to delete 
        TextBox tb = (TextBox)GridView1.Rows[rowIndex].FindControl("textboxid");
        string text = tb.Text;
        // This way you can find and fetch the properties of controls inside a `GridView`. Just that it should be within `ItemTemplate`.
    }
} 

Note: mention DataKeyNames="ID" inside your GridView. The "ID" is the primary key column of your table.

Are you binding the GridView on pageload? If so, then move it to !IsPostBack block as show below:

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        GridView1.DataSource = yourDataSource;
        GridView1.DataBind();           
    } 
}
Praveen Nambiar
  • 4,852
  • 1
  • 22
  • 31
  • OK thanks...but what is Container.DisplayIndex and how do I populate it with the data I need? I presume the databound event is still required to set up the attributes and row IDs? – Martin S Mar 04 '13 at 12:28
  • Also, my delete event is running another stored procedure on the database to delete the user, which is why I pass across the data in the commandargument property. I guess when I understand what the Container.DisplayIndex is, it might fall into place. Thanks. – Martin S Mar 04 '13 at 12:36
  • The GridView obviously will have many rows. So Container.DisplayIndex set on CommandArgument will give the rownumber/rowindex of the button event clicked on a row. This can be further used to fetch the ID of the row you want to delete. – Praveen Nambiar Mar 04 '13 at 12:43
  • OK, but I can't see how this will help me? The data is populating correctly, and, although second time only, the event fires and deletes the correct data from the system, so finding the row is of no benefit - it's trying to understand why the button event doesn't fire. Or am I missing something (which wouldn't surprise me!)? – Martin S Mar 04 '13 at 12:59
  • Well like i said in my answer, this is an alternate way of doing what you did above. And m sure will fix the issue that you are facing. Cuz i think calling a button click event from inside a template field of a gridview in an unncessary overhead when asp.net gridview has some good features like CommandName and CommandArgument. – Praveen Nambiar Mar 04 '13 at 13:03
  • Also i have updated the answer where the ID of the row is also returned. Just in case you give my answer a shot later. – Praveen Nambiar Mar 04 '13 at 13:15
  • I'm still at a loss. I have removed the event code from the button, and followed your suggestion re: using the rowcommand event which contains a modified version of the deletion code, but this still only fires the command event on the second click of the button. – Martin S Mar 04 '13 at 13:23
  • Hi @Praveen Nambiar. I have updated my original post to show the changes I've made. Even if it would fire on the first click, I am now stuck on how to access the dataitems for the selected row! – Martin S Mar 04 '13 at 13:42
  • Its really simple. You can use the FindControl property of the Gridview to access the dataitems. Well, how did u fix the twice click issue? M curious now :) – Praveen Nambiar Mar 04 '13 at 13:45
  • `DataKeyNames="RowID" OnRowDataBound="GridViewRowDataBound" OnRowCommand="GridViewRowCommand"` – Martin S Mar 04 '13 at 13:47
  • No, I haven't fixed it at all. With the changes you suggested, as listed above, the rowcommand event now fires on the second click, as it did with the button event previously. So sadly I'm no further along than I was. How do you use FindControl to find DataItem properties? I've used it in my DataBound event (see my code) to find the Delete button for that row, but that syntax doesn't work with the RowCommand event: `var selectedId = DataBinder.Eval(e.Row.DataItem, "RowID").ToString();` – Martin S Mar 04 '13 at 13:53
  • I didn't negative anything. But to answer your questionre: databinding, I am not doing a databind at all - it populates the data as soon as I set the sqldatasource connection string, and the databound event fires correctly. – Martin S Mar 04 '13 at 13:58
  • @MartinS..Find my updated answer on how to use FindControl inside RowCommand event. – Praveen Nambiar Mar 04 '13 at 14:24
  • Thanks...but does this work for bound fields? I only have one control in my grid, which is the delete button. The rest are bound fileds that are populated from the datasource. – Martin S Mar 04 '13 at 14:28
  • Nope it wouldnt work for boundfields. The coulmns you want to edit - you will have to convert them to TemplateFields. – Praveen Nambiar Mar 04 '13 at 14:29
  • OK thanks, though still not sure if this will fix my having to click twice issue! – Martin S Mar 04 '13 at 14:31
  • I've gone back to a slightly improved version of my original code that uses the button event as after making all those changes it still fired on the second click only. There is obviously something else about the control or page that is causing this to happen. I have commented out the rowcommand event code as that would make more work having to convert them to controls and dynamically populate them. So am still at a loss as to why this doesn't work. – Martin S Mar 04 '13 at 14:44