透過手寫程式了解GridView的運作

這一篇是強迫不使用DataSource(SqlDatasouce,ObjectDataSouce,AccessDataSource,...)的情況下,透過ADO的存取資料庫,以及GridView的各個事件,來理解GridView的一些運作狀況。小喵會在這篇中,寫下有關GridView的資料繫結、編輯、修改、刪除、排序、分頁等功能的程式碼。

緣起:

小喵接觸ASP.NET是從ASP.NET 2.0開始(VS2005),而GridView這個控制項也是從這個時候開始出現,由於有DataSouce的輔助,讓我們在使用上非常的方便。只要拖拉一下,設定一下,資料就可以透過GridView顯示在網頁上了。不過方便的結果,可能用了一段時間,開發了幾套系統,卻還不知道這GridView到底是怎麼運作的(因為通通包裝的好好的)。

這一篇是強迫不使用DataSource(SqlDatasouce,ObjectDataSouce,AccessDataSource,...)的情況下,透過ADO的存取資料庫,以及GridView的各個事件,來理解GridView的一些運作狀況。小喵會在這篇中,寫下有關GridView的資料繫結、編輯、修改、刪除、排序、分頁等功能的程式碼。

準備工作:

此篇範例照慣例,使用北風資料庫當做範例。用最簡單的資料表【Region】來當作範例,當然大家在練習時,可以自己去改掉Connection String與Table名稱。

畫面:

畫面上安排很簡單,就是安排一個GridView,然後隨便選一個樣式(可以分辨編輯實的顏色變化),另外加入一個Templete Fields,用來放置【編輯、刪除、維護、取消】的按鈕。另外再安排一個Button,用來第一次繫結資料。相關程式碼如下:

 

        <asp:Label ID="lblMsg" runat="server" Text=""></asp:Label>
        <br />
        <asp:Button ID="Button1" runat="server" Text="Button" />
        <asp:GridView ID="GridView1" runat="server" AllowSorting="True" 
            BackColor="White" BorderColor="#E7E7FF" BorderStyle="None" BorderWidth="1px" 
            CellPadding="3" GridLines="Horizontal" AllowPaging="True" PageSize="3">
            <RowStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" />
            <Columns>
                <asp:TemplateField>
                    <EditItemTemplate>
                        <asp:Button ID="btnUpdate" runat="server" Text="維護" CommandName="Update" />
                        <asp:Button ID="btnCancel" runat="server" Text="取消" CommandName="Cancel" />
                    </EditItemTemplate>
                    <ItemTemplate>
                        <asp:Button ID="btnEdit" runat="server" Text="編輯" CommandName="Edit" />
                        <asp:Button ID="btnDel" runat="server" Text="刪除" CommandName="Delete" OnClientClick="return confirm('您確定要刪除此筆資料嗎??');" />
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
            <FooterStyle BackColor="#B5C7DE" ForeColor="#4A3C8C" />
            <PagerStyle BackColor="#E7E7FF" ForeColor="#4A3C8C" HorizontalAlign="Right" />
            <SelectedRowStyle BackColor="#738A9C" Font-Bold="True" ForeColor="#F7F7F7" />
            <HeaderStyle BackColor="#4A3C8C" Font-Bold="True" ForeColor="#F7F7F7" />
            <AlternatingRowStyle BackColor="#F7F7F7" />
        </asp:GridView>

後置程式碼:

接著就開始來撰寫程式碼的部分。首先,要手動寫程式了,當然要Imports相關的NameSpance

Imports System.Data
Imports System.Data.SqlClient

並且設定一下Connection String

Private ConnStr As String = "Data Source=.\sqlexpress;Initial Catalog=Northwind;Integrated Security=True"

接著撰寫【查詢、修改、刪除】資料的Function,用來【讀取、修改、刪除】Region資料表,讀取的Function傳回DataTable。

    Private Function GetData() As DataTable
        Dim Dt As New DataTable
        Try
            Using Conn As New SqlConnection(ConnStr)
                Dim SqlTxt As String = ""
                SqlTxt += " SELECT TOP 50 * "
                SqlTxt += " FROM [Region] (NOLOCK) "
                SqlTxt += "  "
                Dim Cmmd As New SqlCommand(SqlTxt, Conn)
                Dim Da As New SqlDataAdapter(Cmmd)
                Da.Fill(Dt)

            End Using

        Catch ex As Exception
            Me.lblMsg.Text = ex.Message

        End Try
        Return Dt
    End Function

    Private Function DeleteData(ByVal RegionID As Integer) As String
        Dim rc As String = ""
        Try
            Using Conn As New SqlConnection(ConnStr)
                Conn.Open()
                Dim SqlTxt As String = ""
                SqlTxt += " DELETE Region "
                SqlTxt += " WHERE RegionID = @RegionID "
                SqlTxt += "  "
                Using Cmmd As New SqlCommand(SqlTxt, Conn)
                    Cmmd.Parameters.AddWithValue("@RegionID", RegionID)
                    Cmmd.ExecuteNonQuery()
                End Using

                rc = "Success"
            End Using

        Catch ex As Exception
            rc = "False"
            Me.lblMsg.Text = ex.Message

        End Try
        Return rc
    End Function

    Private Function UpdData(ByVal RegionID As Integer, ByVal RegionDescription As String) As String
        Dim Rc As String = ""
        Try
            Using Conn As New SqlConnection(ConnStr)
                Conn.Open()
                Dim SqlTxt As String = ""
                SqlTxt += " UPDATE Region "
                SqlTxt += " SET RegionDescription = @RegionDescription "
                SqlTxt += " WHERE RegionID = @RegionID "
                SqlTxt += "  "
                Using Cmmd As New SqlCommand(SqlTxt, Conn)
                    Cmmd.Parameters.AddWithValue("@RegionDescription", RegionDescription)
                    Cmmd.Parameters.AddWithValue("@RegionID", RegionID)
                    Cmmd.ExecuteNonQuery()
                    Rc = "Success"
                End Using
            End Using

        Catch ex As Exception
            Rc = "False"
            Me.lblMsg.Text = ex.Message
        End Try
        Return Rc
    End Function

接著陸續來看查詢、編輯、修改、刪除、分頁、排序的各個程式碼:

查詢:當按鈕按下去後,將資料繫結給GridView,因此先寫一個將資料繫結的Sub GVGetData()來處理,未來會有很多地方都要呼叫這一個Sub作繫結資料處理

    '抓取資料並繫結GridView
    Private Sub GVGetData()
        Try
                Dim Dt As DataTable = GetData()
                Me.GridView1.DataSource = Dt
                Me.GridView1.DataBind()

        Catch ex As Exception
            Me.lblMsg.Text = ex.Message

        End Try
    End Sub

接著,按鈕按下去的時候,只需要呼叫他就可以了

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
        '呼叫資料繫結
        GVGetData()
    End Sub

接著,再來看分頁如何做。分頁時,要設定GridView的AllowPaging=True,另外,由於這個資料表的資料不多,所以改一下,一頁的筆數預設是10筆,改為3筆(PageSize="3")。此時就會有分頁的功能,不過當換頁時,會觸發【PageIndexChanging】與【PageIndexChanged】這兩個事件。程式碼如下:

    Protected Sub GridView1_PageIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles GridView1.PageIndexChanged
        '顯示PageIndexChanged事件被呼叫到
        Response.Write("PageIndexChanged!!")
    End Sub

    Protected Sub GridView1_PageIndexChanging(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewPageEventArgs) Handles GridView1.PageIndexChanging
        '設定分頁停在第幾頁
        Me.GridView1.PageIndex = e.NewPageIndex
        '繫結資料
        GVGetData()
    End Sub

排序:查詢、分頁有了,接著就是排序。排序的時候,會觸發Sorting與Sorted這兩個事件。不過這個部分稍微麻煩,經測試後發現,在Sorting的e.SortDirection並沒有記住這次順排(Ascending)→下次就逆排(Descending)的狀況,每次取得的e.SortDirection通通都是順排(Ascending)。為了達到第一次點順排,再點一次是逆排,因此要透過ViewState來記錄上次的方式,在加上判斷。

另外,本來的繫結資料時,並沒有排序,只有直接把取得的DataTable給GridView。現在要加上排序的功能了,那麼就要拿DataTable的資料來做排序的動作,這個部分要借用【DataView】的【Sort】來設定,而且DataView也可以當作GridView的資料來源。因此我們改寫一下繫結資料的部分先,然後再寫Sorting與Sorted這兩個事件

繫結資料的部分:

透過多型,希望Sorting的時候要繫結資料呼叫GVGetData的另一個型態

    '有指定排序的抓資料並繫結GridView
    Private Sub GVGetData(ByVal pSortDirection As SortDirection, ByVal pSortExpression As String)
        Try
            Dim Dt As DataTable = GetData()
            '設定排序的語法
            Dim strSort As String = ""

            If pSortDirection = SortDirection.Ascending Then
                '如果是順排(A~Z)
                strSort = pSortExpression
            Else
                '逆排的時候(Z~A),加上DESC
                strSort = pSortExpression & " DESC"
            End If

            '使用DataView來做GridView的資料來源
            Dim Dv As DataView = Dt.DefaultView
            '設定DataView的排序方式
            Dv.Sort = strSort

            Me.GridView1.DataSource = Dv
            Me.GridView1.DataBind()

        Catch ex As Exception
            Me.lblMsg.Text = ex.Message

        End Try
    End Sub

接著是Sorting與Sorted的部分

    Protected Sub GridView1_Sorted(ByVal sender As Object, ByVal e As System.EventArgs) Handles GridView1.Sorted
        '顯示Sorted事件備觸發
        Response.Write("Sorted!!")
    End Sub

    Protected Sub GridView1_Sorting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewSortEventArgs) Handles GridView1.Sorting


        Dim NowSE As String = CType(ViewState("NowSE"), String) '現在的排序欄位
        Dim NowSD As SortDirection = CType(ViewState("NowSD"), SortDirection)   '目前的排序方向

        If NowSE Is Nothing Then
            '如果沒有ViewState,指定e.SortExpression與順排當作預設的欄位與方向
            NowSE = e.SortExpression
            NowSD = SortDirection.Ascending
        Else
            '有ViewState時
            If NowSE <> e.SortExpression Then
                '如果欄位與本來的不同
                '指定目前的欄位為e.SortExpression
                NowSE = e.SortExpression
                '指定目前的排序方向為順排
                NowSD = SortDirection.Ascending
            Else
                '如果欄位與本來相同
                If NowSD = SortDirection.Ascending Then
                    '當本來為順排→改為逆排
                    NowSD = SortDirection.Descending
                Else
                    '當本來違逆排→改為順排
                    NowSD = SortDirection.Ascending
                End If
            End If
        End If
        '將得到的欄位與方向,紀錄回ViewState
        ViewState("NowSD") = NowSD
        ViewState("NowSE") = NowSE

        '呼叫繫結資料,並指定排序欄位與方向
        GVGetData(NowSD, NowSE)
    End Sub

這樣子,排序就OK了。不過小喵發現,這樣子排序配合分頁的切換,由於本來的沒有考慮排序,所以每次分頁一切換,就會弄亂排序的狀況。因此要改一下資料繫結的部分,檢查ViewState中是否有設定排序的欄位與方向。改一下本來的GVGetData()

    '抓取資料並繫結GridView
    Private Sub GVGetData()
        Try
            '判斷是否有排序過
            If ViewState("NowSE") Is Nothing Then
                '沒有排序過,直接抓DataTable
                Dim Dt As DataTable = GetData()
                Me.GridView1.DataSource = Dt
                Me.GridView1.DataBind()
            Else
                '排序過,因此除了抓資料,還要排序
                Dim NowSE As String = CType(ViewState("NowSE"), String)
                Dim NowSD As SortDirection = CType(ViewState("NowSD"), SortDirection)
                GVGetData(NowSD, NowSE)
            End If

        Catch ex As Exception
            Me.lblMsg.Text = ex.Message

        End Try
    End Sub

這樣無論排序、分頁的部分都可以正常運作了。

接著,就是編輯、修改、刪除的程式碼。這部分小喵發現,在預期的狀況下,修改、刪除的時候,會觸發RowUpdating與RowUpdated / 或者 RowDeleting與RowDeleted。但是實際Step By Step的運作下,令人驚訝的發現,維護後的RowUpdated與刪除後的RowDeleted這兩個事件並不會被觸發。這與小喵以往的印象ing:處理中/ed:處理後的理解與期望不同。小喵特別發了一個討論在小舖中【手動寫程式處理GridView的維護,為何沒有觸發RowUpdated事件】,看來GridView沒有使用DataSourceID去繫結DataSource控制項的狀況下,不會觸發這兩個ed的事件

以下為編輯、修改、取消、刪除的程式碼:

刪除資料:

    Protected Sub GridView1_RowDeleted(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewDeletedEventArgs) Handles GridView1.RowDeleted
        '顯示RowDeleted備觸發→事實上用程式碼並不會觸發此事件!!
        Response.Write("RowDeleted")
        'GVGetData()
    End Sub

    Protected Sub GridView1_RowDeleting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewDeleteEventArgs) Handles GridView1.RowDeleting
        Try

            Dim RegionID As Integer '刪除的Key值
            '取得刪除的Key
            RegionID = CType(Me.GridView1.Rows(e.RowIndex).Cells(1).Text, Integer)
            '呼叫刪除的Function
            Dim rc As String = DeleteData(RegionID)
            If rc = "Success" Then
                Me.lblMsg.Text = "刪除成功!!"
                '呼叫繫結資料重新繫結
                GVGetData()
            End If

        Catch ex As Exception
            Me.lblMsg.Text = ex.Message
        End Try
    End Sub

編輯:

    Protected Sub GridView1_RowEditing(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewEditEventArgs) Handles GridView1.RowEditing
        '設定編輯的Index
        Me.GridView1.EditIndex = e.NewEditIndex
        '繫結資料
        GVGetData()
    End Sub

取消:

    Protected Sub GridView1_RowCancelingEdit(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCancelEditEventArgs) Handles GridView1.RowCancelingEdit
        '取消編輯模式→設定GridView的EditIndex = -1
        Me.GridView1.EditIndex = -1
        GVGetData()
    End Sub

修改:

    Protected Sub GridView1_RowUpdated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) Handles GridView1.RowUpdated
        '離開編輯模式
        'Me.GridView1.EditIndex = -1
        'GVGetData()

        Response.Write("RowUpdated")
    End Sub

    Protected Sub GridView1_RowUpdating(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs) Handles GridView1.RowUpdating
        Try

            Dim RegionID As Integer
            Dim RegionDescription As String

            '取得畫面中的資料
            Dim tGvRw As GridViewRow = Me.GridView1.Rows(e.RowIndex)
            RegionID = CType(CType(tGvRw.Cells(1).Controls(0), TextBox).Text, Integer)
            RegionDescription = CType(tGvRw.Cells(2).Controls(0), TextBox).Text

            '進行維護
            Dim Rc As String = UpdData(RegionID, RegionDescription)
            If Rc = "Success" Then
                Me.lblMsg.Text = "維護成功"
                '離開編輯模式
                Me.GridView1.EditIndex = -1
                GVGetData()
            End If

        Catch ex As Exception
            Me.lblMsg.Text = ex.Message
        End Try
    End Sub

後記

再經過這樣的練習後,對於GridView的一些運作,會有比較清楚了瞭解。據說這樣的能力在以前DataGrid的時代,是基本的能力,也就是大家都必須這麼寫。自從ASP.NET 2.0開始,GridView搭配DataSource控制項實在太好用了。簡單的使用SqlDataSource只需要拖拉、設定就可以通通達成。進一步需要透過程式處理,也可以寫成Class透過ObjectDataSource來達成。不過,當系統的需求越來越複雜,有時候就需要去透過GridView各項事件去處理一些狀況。透過這樣的練習可以來了解一下各個事件的用法。小喵因此將過程筆記下來,也提供大家參考。

 


以下是簽名:


Microsoft MVP
Visual Studio and Development Technologies
(2005~2019/6)