asp.netvb.neteventsdatagridimagebutton

ASP.NET utilising click event of imagebutton within datagrid template column


I am struggling to get a datagrid working nicely for me. I realise there may be other controls that can do what I'm planning but i'm a way down the line with the datagrid so would like to see if this is possible. Essentially I have the following datagrid which is being bound to a SQL connection:

<asp:datagrid ID="dginventory" AutoGenerateColumns="false" runat="server" Width="99.4%" AllowPaging="True" AllowSorting="true" PageSize="20" OnPageIndexChanged="ChangePage" OnSortCommand="SortGrid" OnEditCommand="dginventory_oneditcommand" OnDeleteCommand="dginventory_ondeletecommand" >
            <HeaderStyle CssClass="datagridheader"/>
            <AlternatingItemStyle CssClass="datagridalternating"/>
            <ItemStyle cssClass="datagriditem"/>
            <Columns>
                <asp:BoundColumn DataField="PurchaseYear" ItemStyle-HorizontalAlign="Center" HeaderStyle-HorizontalAlign="center" HeaderText="YOP" ItemStyle-Width="4%" SortExpression="PurchaseYear ASC" />
                <asp:BoundColumn DataField="PurchaseDate" ItemStyle-HorizontalAlign="Center" HeaderText="Purchased" ItemStyle-Width="7%" DataFormatString="{0:d}" SortExpression="PurchaseDate ASC" />
                <asp:BoundColumn DataField="Number" HeaderText="Number" ItemStyle-Width="5%"  SortExpression="Number ASC" />
                <asp:BoundColumn DataField="Theme" HeaderText="Theme" ItemStyle-Width="10%" SortExpression="Theme ASC" />
                <asp:BoundColumn DataField="Name" HeaderText="Name" ItemStyle-Width="30%" SortExpression="Name ASC" />
                <asp:BoundColumn DataField="RRP" ItemStyle-HorizontalAlign="Left" HeaderText="RRP" ItemStyle-Width="4%" DataFormatString="{0:C}" />
                <asp:BoundColumn DataField="PurchasePrice" ItemStyle-HorizontalAlign="Left" HeaderText="Cost" ItemStyle-Width="4%" DataFormatString="{0:C}" />
                <asp:BoundColumn DataField="Discount" ItemStyle-HorizontalAlign="Left" HeaderText="Dis." ItemStyle-Width="4%" DataFormatString="{0:P1}" />
                <asp:BoundColumn DataField="ReleaseDate" ItemStyle-HorizontalAlign="Center" HeaderStyle-HorizontalAlign="center" HeaderText="Rel. Date" ItemStyle-Width="7%" DataFormatString="{0:d}" SortExpression="ReleaseDate ASC" />
                <asp:BoundColumn DataField="RetireDate" ItemStyle-HorizontalAlign="Center" HeaderStyle-HorizontalAlign="center" HeaderText="Ret. Date" ItemStyle-Width="7%" DataFormatString="{0:d}" SortExpression="RetireDate ASC" />
                <asp:BoundColumn DataField="Retailer" HeaderText="Retailer" ItemStyle-Width="10%" SortExpression="Retailer ASC" />
                <asp:BoundColumn DataField="StorageLocation" ItemStyle-HorizontalAlign="Center" HeaderStyle-HorizontalAlign="center" HeaderText="Box" ItemStyle-Width="3%" SortExpression="StorageLocation ASC" />
                <asp:TemplateColumn HeaderText="" ItemStyle-HorizontalAlign="left" ItemStyle-VerticalAlign="Middle" HeaderStyle-HorizontalAlign="center" ItemStyle-Width="2%">
                    <ItemTemplate>
                        <asp:ImageButton src="Images/edit-alt-regular-24.png" ID="btneditset" alt="Edit" class="imagebutton" CommandName="Edit" runat="server" />
                    </ItemTemplate>
                </asp:TemplateColumn>
                <asp:TemplateColumn HeaderText="" ItemStyle-HorizontalAlign="left" ItemStyle-VerticalAlign="Middle" HeaderStyle-HorizontalAlign="center" ItemStyle-Width="2%">
                    <ItemTemplate>
                        <asp:ImageButton src="Images/trash-regular-24.png" ID="btndeleteset" class="imagebutton" CommandName="Delete" runat="server"/>
                    </ItemTemplate>
                </asp:TemplateColumn>
            </Columns>
                <PagerStyle NextPageText="Next --&gt;" Font-Bold="True" PrevPageText="&amp;lt-- Prev" HorizontalAlign="Center" ForeColor="White" BackColor="Red" Mode="NumericPages" />
        </asp:datagrid>

As you can see I have two template columns, each containing an image. One for delete and one for Edit. I cannot seem to catch the click events of these image buttons no matter what I try. If I replace one of them with a ButtonColumn then that works fine however that means that I can't use an image.

In the code behind I've setup these:

Sub dginventory_oneditcommand(ByVal sender As Object, ByVal e As DataGridCommandEventArgs)

    lblfilters.Text = "edit"

End Sub


Sub dginventory_ondeletecommand(ByVal sender As Object, ByVal e As DataGridCommandEventArgs)

    lblfilters.Text = "delete"

End Sub

Neither one of them seems to actually catch anything. I've also tried using onitemcommand which again doesn't seem to fire. I did try adding an onclick to the two imagebuttons too but again no joy.

Hopefully I've described this well enough. I am using vb.net for the code behind in case that wasn't clear.

Here's hoping....


Solution

  • Ok, the easiest way to do this when dropping in a button, ImageButton, or even a combo box for a row?

    Ignore the row command approach, and use a regular click event for the given control.

    The only "trick" here is that because the control is nested (in a Repeater, ListView, GridView, DataGrid etc.), then you don't have a property sheet for the given control, and thus can't use the property sheet (or a double click) on the control to create the click event.

    However, you are 100% free to add, use and enjoy a click event by typing in markup directly. (and intel-sense will still help you).

    I should also point out that a DataGrid is the perhaps the oldest control, and in fact you often not see DataGrid in the tool box.

    So, I suggest in most cases to use a GridView. (they are very similar).

    And if you "grid" of data has more then a few custom controls, then I suggest using a ListView, as it is far more markup friendly. No worry, and using DataGrid is still ok despite being an older control.

    So, so say we have a simple DataGrid like this:

    (and note the 2 image buttons).

    So, then this:

      <asp:DataGrid ID="GHotels" runat="server" AutoGenerateColumns="false"
         DataKeyField="ID" CssClass="table table-hover" Width="45%" >
    
          <Columns>
            <asp:BoundColumn HeaderText="First" DataField="FirstName" runat="server" />
            <asp:BoundColumn HeaderText="Last" DataField="LastName" runat="server" />
            <asp:BoundColumn HeaderText="City" DataField="City" runat="server" />
            <asp:BoundColumn HeaderText="HotelName" DataField="HotelName" runat="server" />
            <asp:BoundColumn HeaderText="Description" DataField="Description" runat="server" />
    
            <asp:TemplateColumn HeaderText="Edit">
                <ItemTemplate>
                    <asp:ImageButton ID="cmdEdit" runat="server"
                        ImageUrl="~/Content/edit2.png" Width="48"
                        />
                </ItemTemplate>
            </asp:TemplateColumn>
    
            <asp:TemplateColumn HeaderText="Delete">
                <ItemTemplate>
                    <asp:ImageButton ID="cmdDelete" runat="server"
                        ImageUrl="~/Content/trashcan1.png" width="48"
                        OnClick="cmdDelete_Click"
    
                        />
    
                </ItemTemplate>
            </asp:TemplateColumn>
    
          </Columns>
      </asp:DataGrid>
    

    And code to load the grid looks like this:

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    
        If IsPostBack = False Then
            LoadGrid()
        End If
    
    
    End Sub
    
    Sub LoadGrid()
    
        Dim strSQL As String =
            "SELECT * FROM tblHotelsA
            ORDER BY HotelName"
    
        GHotels.DataSource = MyRst(strSQL)
        GHotels.DataBind()
    
    End Sub
    

    And the result is this:

    enter image description here

    Ok, so let's add a delete click event to the image delete button in above.

    So, it looks like this from the editor:

    enter image description here

    So, as above shows, we are allowed and can with ease use standard click events (or other events) for that given control. This means VERY little reason exists to bother with and trying to use the row command. And bonus is we thus have a separate click event for each control and they are not all mumbled up into one event with a bunch of case statements.

    In the code behind, we can then pick up the row information like this:

    Protected Sub cmdDelete_Click(sender As Object, e As ImageClickEventArgs)
    
        Dim cmdDel As ImageButton = sender
        Dim gRow As DataGridItem = cmdDel.NamingContainer
    
        Dim PK As Integer = GHotels.DataKeys(gRow.ItemIndex)
    
        Dim strSQL As String =
            "DELETE FROM tblHotelsA WHERE ID = @ID"
    
        Dim cmdSQL As New SqlCommand(strSQL)
        cmdSQL.Parameters.Add("@ID", SqlDbType.Int).Value = PK
        ' MyRstPE(cmdSQL)
    
        Debug.Print($"Row index click = {gRow.ItemIndex}")
        Debug.Print($"Database PK for row = {PK}")
    
    End Sub
    

    In above, I show how you get the current row (DataGridItem).

    In above, I show how you get the database PK value.

    In above, I show how you get the row index.

    Note the use of "naming" container. This is supported for ALL data bound controls, and returns the current row item.

    So, when I click on a given row button, then I have this in the output/debug window:

    enter image description here

    So, you are free to use regular click events for controls placed in a GridView, ListView, DataGrid etc.

    And the above advice also extends to say dropping in a combo box, or other controls. Their events can also be used, but you have to "type in" the event setting directly into the markup since the control is nested, and thus no property sheet is available.

    And for completeness, it fast becomes tiring to type code over and over to pull a data table with supplied SQL, so I have some helper routines I used in above, and use throughout the application.

    Public Function MyRst(strSQL As String, ByVal Optional strCon As String = "") As DataTable
    
        If strCon = "" Then
            strCon = My.Settings.TEST4
        End If
    
        Dim rstData As New DataTable
        Using conn As New SqlConnection(strCon)
            Using cmdSQL As New SqlCommand(strSQL, conn)
                conn.Open()
                rstData.Load(cmdSQL.ExecuteReader)
                rstData.TableName = strSQL
            End Using
        End Using
        Return rstData
    End Function
    
    Public Function MyRstP(cmdSQL As SqlCommand, ByVal Optional strCon As String = "") As DataTable
    
        If strCon = "" Then
            strCon = My.Settings.TEST4
        End If
    
        Dim rstData As New DataTable
        Using conn As New SqlConnection(strCon)
            Using (cmdSQL)
                cmdSQL.Connection = conn
                conn.Open()
                rstData.Load(cmdSQL.ExecuteReader)
            End Using
        End Using
    
        Return rstData
    
    End Function
    
    Public Sub MyRstPE(cmdSQL As SqlCommand, ByVal Optional strCon As String = "")
    
        If strCon = "" Then
            strCon = My.Settings.TEST4
        End If
    
        Dim rstData As New DataTable
        Using conn As New SqlConnection(strCon)
            Using (cmdSQL)
                cmdSQL.Connection = conn
                conn.Open()
                cmdSQL.ExecuteNonQuery()
            End Using
        End Using
    
    End Sub
    

    Edit: Introducing a confirm dialog for the delete button

    Of course, a delete button is rather dangerous without a confirm prompt.

    For a standard button, or say an image button, then such buttons have support for both a client side click event, and a server-side event we are using above.

    And if the client side (JavaScript) client event for that given button returns false, then the server-side event does not run. And if the client-side JavaScript code returns true, then the server-side event runs.

    However, one extra issue arises when attempting to return true or false from JavaScript.

    Most, if not all "cool" or "nice" dialog prompt libraries and utilities in JavaScript (jQuery.UI, sweet Alert etc.) run asynchronous. In other words, most JavaScript code is now non blocking. This means such code does not wait.

    So, the simplest approach is to use the JavaScript confirm dialog. This is one of the VERY few features in JavaScript that does halt code.

    So, we could for example add this client side click event to the ImageButton:

            <asp:TemplateColumn HeaderText="Delete">
                <ItemTemplate>
                    <asp:ImageButton ID="cmdDelete" runat="server"
                        ImageUrl="~/Content/trashcan1.png" width="48"
                        OnClick="cmdDelete_Click"
                        OnClientClick="return confirm('Delete this record');"
                        />
    
                </ItemTemplate>
            </asp:TemplateColumn>
    

    In fact, looking at above, we did not even have to write a separate JavaScript routine, but used the JavaScript confirm() dialog. This works with ease since it halts code.

    However, it looks rather poor, does not give any formatting options, and a bonus would be if the dialog appeared right beside the button we clicked on.

    Hence this:

    enter image description here

    So, I suggest adopting a really nice dialog utility. They have 100's of uses, and for say some pop dialog to edit a row from a Grid, for deleting confirms, and in fact for just about any kind of confirm type of use case, then adopting a great dialog system is most useful, and something that you can use for years to come.

    My current favorite choice is the jQuery.UI dialog. In fact, I turned the whole control into a user control, and now I can just drag + drop that control into a form and use it.

    So, the jQuery.UI dialog allows you to place content in a div, and then pop that div. (and it by-passes all known popup blockers, another good reason to use such dialogs).

    So, assuming we have jQuery.UI (and jQuery) available in the current page, then our ImageButton (client-side event) becomes this:

                    <asp:ImageButton ID="cmdDelete" runat="server"
                        ImageUrl="~/Content/trashcan1.png" width="48"
                        OnClick="cmdDelete_Click"
                        OnClientClick="return mydelprompt(this);"
                        />
    

    And thus, below the data grid, we thus have this JavaScript code:

            <div id="mydeldiv">
                <h4>Delete This hotel</h4>
                <h4><i>(this can't be undone)</i></h4>
            </div>
    
            <script>
                mydelpromptok = false
                function mydelprompt(btn) {
    
                    if (mydelpromptok) {
                        mydelprompt = false
                        return true
                    }
    
                    myDialog = $("#mydeldiv")
                    myDialog.dialog({
                        title: "Delete Hotel",
                        modal: true,
                        appendTo: "form",
                        dialogClass: "dialogWithDropShadow",
                        position: { my: "right top", at: "left bottom", of: btn },
                        dialogClass: "dialogWithDropShadow",
                        buttons: {
                            Delete: function () {
                                myDialog.dialog('close')
                                mydelprompt = true
                                btn.click()
                            },
                            cancel: function () {
                                myDialog.dialog('close')
                            }
                        }
                    })
                    return false
                }
            </script>
    

    So, when you click on the button, the dialog displays, code continues to run (asynchronous), and it returns false to the button. Now the dialog is displayed. If we hit ok, then we close the dialog, set our flag, and THEN click the button again!!!

    This re-triggers the function, but it now returns true, and hence the server-side code stub will run. As noted, the above trick is a way to get around the fact of most nice JavaScript dialog systems are non blocking and they run asynchronous.

    The result of the above now is:

    enter image description here