建立自訂的排序使用者介面 (VB)

作者:Scott Mitchell

下載 PDF

顯示已排序數據的完整清單時,藉由引進分隔符數據列來分組相關數據會很有説明。 在本教學課程中,我們將瞭解如何建立這類排序使用者介面。

簡介

當顯示排序數據列中只有少數不同值的已排序數據長清單時,使用者可能會發現難以辨識確切發生差異界限的位置。 例如,資料庫中有81個產品,但只有九個不同的類別選擇 (八個唯一類別加上 NULL 選項) 。 請考慮有興趣檢查屬於[訂用帳戶] 類別下之產品的使用者案例。 從列出單一 GridView 中所有 產品的頁面中,使用者可能會決定最佳選擇是依類別排序結果,這會將所有「檔案」產品群組在一起。 依類別排序之後,使用者接著必須搜尋清單,以尋找群組於何處開始和結束產品。 由於結果依類別名稱依字母順序排序,而尋找「聖地」產品的類別名稱並不困難,但仍需要仔細掃描方格中的項目清單。

為了協助醒目提示排序群組之間的界限,許多網站都會採用使用者介面,在這類群組之間新增分隔符。 如圖 1 所示的分隔符可讓使用者更快速地尋找特定群組並識別其界限,以及確定數據中存在哪些不同群組。

每個類別群組都是清楚識別的

圖 1:每個類別群組都是清楚識別 (按兩下即可檢視大小完整的影像)

在本教學課程中,我們將瞭解如何建立這類排序使用者介面。

步驟 1:建立標準、可排序的 GridView

在探索如何增強 GridView 以提供增強的排序介面之前,讓我們先建立列出產品的標準可排序 GridView。 首先, CustomSortingUI.aspx 開啟資料夾中的頁面 PagingAndSorting 。 將 GridView 新增至頁面、將其 ID 屬性設定為 ProductList,並將其系結至新的 ObjectDataSource。 設定 ObjectDataSource 以使用 ProductsBLL 類別 s GetProducts() 方法來選取記錄。

接下來,設定 GridView,使其只包含 ProductNameCategoryNameSupplierName和 BoundFields 和 UnitPrice 已停止的 CheckBoxField。 最後,將 GridView 設定為支援排序,方法是核取 GridView 智慧標記中的 [啟用排序] 複選框 (,或將其 AllowSorting 屬性設定為 true) 。 在加入 CustomSortingUI.aspx 頁面之後,宣告式標記看起來應該類似下列內容:

<asp:GridView ID="ProductList" runat="server" AllowSorting="True"
    AutoGenerateColumns="False" DataKeyNames="ProductID"
    DataSourceID="ObjectDataSource1" EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category"
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier"
            ReadOnly="True" SortExpression="SupplierName" />
        <asp:BoundField DataField="UnitPrice" DataFormatString="{0:C}"
            HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
            SortExpression="Discontinued" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts"
    TypeName="ProductsBLL"></asp:ObjectDataSource>

請花一點時間在瀏覽器中檢視到目前為止的進度。 圖 2 顯示可排序的 GridView,其數據依依字母順序排序時。

可排序的 GridView 資料依類別排序

圖 2:可排序的 GridView 數據依類別排序 (按鍵即可檢視大小完整的影像)

步驟 2:探索新增分隔符數據列的技術

在泛型、可排序的 GridView 完成之後,所有保留專案都是能夠在每個唯一排序群組之前,在 GridView 中新增分隔符數據列。 但如何將這類數據列插入 GridView 中? 基本上,我們需要逐一查看 GridView 的數據列、判斷排序數據行中值之間的差異,然後新增適當的分隔符數據列。 在思考這個問題時,解決方案似乎自然地位於 GridView RowDataBound 事件處理程式的某處。 如我們根據 數據自定義格式化 教學課程中所討論,根據數據列的數據套用數據列層級格式時,通常會使用此事件處理程式。 不過, RowDataBound 事件處理程式不是這裡的解決方案,因為無法以程序設計方式從這個事件處理程式將數據列新增至 GridView。 GridView 的 Rows 集合事實上是唯讀的。

若要將其他數據列新增至 GridView,我們有三個選項:

  • 將這些元數據分隔列新增至系結至 GridView 的實際數據
  • 在 GridView 系結至數據之後,將其他 TableRow 實例新增至 GridView 控件集合
  • 建立可擴充 GridView 控件的自定義伺服器控件,並覆寫負責建構 GridView 結構的方法

如果許多網頁或多個網站都需要這項功能,建立自定義伺服器控件是最佳方法。 不過,這需要相當多的程序代碼,並徹底探索 GridView 內部工作的深度。 因此,我們不會針對本教學課程考慮該選項。

其他兩個選項會將分隔符數據列新增至系結至 GridView 的實際數據,並在其系結後操作 GridView 控件集合 - 以不同方式攻擊問題,並值得討論。

將數據列新增至系結至 GridView 的數據

當 GridView 系結至數據源時,它會為數據源傳回的每個記錄建立 。GridViewRow 因此,我們可以先將分隔符記錄加入數據源,再將其系結至 GridView,以插入所需的分隔符數據列。 圖 3 說明此概念。

其中一種技術牽涉到將分隔符數據列新增至數據源

圖 3:一種技術涉及將分隔符數據列新增至數據源

我使用引號中的分隔符記錄,因為沒有特殊分隔符記錄;相反地,我們必須將數據源中的特定記錄標示為分隔符,而不是一般數據列。 在我們的範例中,我們會將 實例系結 ProductsDataTable 至 GridView,由 組成 ProductRows。 我們可以將記錄標示為分隔符數據列,方法是將其 CategoryID 屬性設定為 -1 (,因為這類值通常無法) 存在。

若要利用這項技術,我們需要執行下列步驟:

  1. 以程序設計方式擷取要系結至 GridView 的數據, (ProductsDataTable 實例)
  2. 根據 GridView 和SortExpressionSortDirection屬性排序數據
  3. 逐一查看 ProductsRows 中的 ProductsDataTable,尋找排序數據行的差異所在位置
  4. 在每個群組界限上,將分隔符記錄 ProductsRow 實例插入 DataTable,其中一個實例已設定 CategoryID-1 (,或決定將記錄標示為分隔符記錄的任何指定,)
  5. 插入分隔符數據列之後,以程序設計方式將數據系結至 GridView

除了這五個步驟之外,我們也需要提供 GridView s RowDataBound 事件的事件處理程式。 在這裡,我們會檢查每個 DataRow 數據列,並判斷它是否為分隔符數據列,其中一個 CategoryID 設定為 -1。 若是如此,我們可能會想要調整其格式設定,或單元格中顯示的文字 () 。

使用這項技術來插入排序群組界限需要比上述還要多的工作,因為您也需要為 GridView 事件 Sorting 提供事件處理程式,並追蹤 SortExpressionSortDirection 值。

在數據系結之後操作 GridView 的控件集合

除了將數據系結至 GridView 之前傳訊數據,我們可以在數據系結至 GridView 之後 新增分隔符數據列。 數據系結的程式會建置 GridView 的控制階層,實際上只是 Table 由一組數據列組成的實例,每一個都是由單元格集合所組成。 具體來說,GridView 控件集合包含 Table 位於其根目錄的物件、 GridViewRow 衍生自 TableRow 系結至 GridView 中 DataSource 每個記錄之類別) 的 (,以及 TableCell 中每個實例中每個 GridViewRow 數據欄位的物件 DataSource

若要在每個排序群組之間新增分隔符數據列,我們可以在建立此控件階層之後直接操作此控件階層。 我們可以確信在頁面轉譯時,已建立 GridView 的控件階層。 因此,此方法會 Page 覆寫 類別 s Render 方法,此時 GridView 的最終控件階層會更新為包含所需的分隔符數據列。 圖 4 說明此程式。

替代技術會操作 GridView 的控件階層

圖 4:替代技術會操作 GridView 的控件階層 (按兩下即可檢視大小完整的影像)

在本教學課程中,我們將使用此後者方法來自定義排序用戶體驗。

注意

我在本教學課程中呈現的程序代碼是以 Teemu Keiski 部落格文章中提供的範例為基礎,使用 GridView 排序群組播放 Bit

步驟 3:將分隔符數據列新增至 GridView 的控件階層

由於我們只想要在其控件階層建立並建立於該頁面流覽的最後一次之後,將分隔符數據列新增至 GridView 控件階層,因此我們想要在頁面生命週期結束時執行這項新增作業,但在實際 GridView 控件階層轉譯為 HTML 之前。 我們可以完成此 Page 作業的最新可能點是類別事件 Render ,我們可以使用下列方法簽章覆寫程式代碼後置類別:

Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
   ' Add code to manipulate the GridView control hierarchy
   MyBase.Render(writer)
End Sub

Page當叫用類別的原始Render方法base.Render(writer)時,頁面中的每個控件都會轉譯,並根據其控件階層產生標記。 因此,我們必須呼叫 base.Render(writer),以便轉譯頁面,並在呼叫 base.Render(writer)之前操作 GridView 的控件階層,以便在轉譯之前將分隔符數據列新增至 GridView 控件階層。

若要插入排序群組標頭,我們必須先確定使用者已要求排序數據。 根據預設,GridView 的內容不會排序,因此我們不需要輸入任何群組排序標頭。

注意

如果您想要在第一次載入頁面時依特定數據行排序 GridView,請在第一頁上呼叫 GridView 的 Sort 方法,流覽 (,但不會在後續回傳) 。 若要完成這項作業,請在條件式內的事件處理程式中Page_Loadif (!Page.IsPostBack)新增此呼叫。 如需方法的詳細資訊,請參閱 分頁和排序報表數據 教學 Sort 課程資訊。

假設數據已排序,下一個工作就是判斷數據排序依據的數據行,然後掃描數據列,以尋找該數據行值的差異。 下列程式代碼可確保資料已排序,並尋找已排序數據的數據行:

Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
    ' Only add the sorting UI if the GridView is sorted
    If Not String.IsNullOrEmpty(ProductList.SortExpression) Then
        ' Determine the index and HeaderText of the column that
        'the data is sorted by
        Dim sortColumnIndex As Integer = -1
        Dim sortColumnHeaderText As String = String.Empty
        For i As Integer = 0 To ProductList.Columns.Count - 1
            If ProductList.Columns(i).SortExpression.CompareTo( _
                ProductList.SortExpression) = 0 Then
                sortColumnIndex = i
                sortColumnHeaderText = ProductList.Columns(i).HeaderText
                Exit For
            End If
        Next
        ' TODO: Scan the rows for differences in the sorted column�s values
End Sub

如果 GridView 尚未排序,則 GridView 的 SortExpression 屬性將不會設定。 因此,只有當這個屬性有一些值時,我們才想要加入分隔符數據列。 如果這樣做,我們接下來必須判斷數據排序依據的數據行索引。 這是透過迴圈查看 GridView 集合Columns來完成的,搜尋其屬性等於 GridView s SortExpression 屬性的數據行SortExpression。 除了數據行的索引之外,我們也會擷取 HeaderText 屬性,在顯示分隔符數據列時會使用此屬性。

使用排序數據的數據行索引,最後一個步驟是列舉 GridView 的數據列。 針對每個數據列,我們需要判斷排序的數據行值是否與前一個數據列的排序數據行值不同。 如果是,我們需要將新的 GridViewRow 實例插入控件階層。 這可透過下列程式代碼來完成:

Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
    ' Only add the sorting UI if the GridView is sorted
    If Not String.IsNullOrEmpty(ProductList.SortExpression) Then
        ' ... Code for finding the sorted column index removed for brevity ...
        ' Reference the Table the GridView has been rendered into
        Dim gridTable As Table = CType(ProductList.Controls(0), Table)
        ' Enumerate each TableRow, adding a sorting UI header if
        ' the sorted value has changed
        Dim lastValue As String = String.Empty
        For Each gvr As GridViewRow In ProductList.Rows
            Dim currentValue As String = gvr.Cells(sortColumnIndex).Text
            If lastValue.CompareTo(currentValue) <> 0 Then
                ' there's been a change in value in the sorted column
                Dim rowIndex As Integer = gridTable.Rows.GetRowIndex(gvr)
                ' Add a new sort header row
                Dim sortRow As New GridViewRow(rowIndex, rowIndex, _
                    DataControlRowType.DataRow, DataControlRowState.Normal)
                Dim sortCell As New TableCell()
                sortCell.ColumnSpan = ProductList.Columns.Count
                sortCell.Text = String.Format("{0}: {1}", _
                    sortColumnHeaderText, currentValue)
                sortCell.CssClass = "SortHeaderRowStyle"
                ' Add sortCell to sortRow, and sortRow to gridTable
                sortRow.Cells.Add(sortCell)
                gridTable.Controls.AddAt(rowIndex, sortRow)
                ' Update lastValue
                lastValue = currentValue
            End If
        Next
    End If
    MyBase.Render(writer)
End Sub

此程式代碼會以程式設計方式參考 Table GridView 控件階層根目錄找到的物件,並建立名為 lastValue的字串變數。 lastValue 用來比較目前數據列的排序數據行值與前一個數據列的值。 接下來,會列舉 GridView 的 Rows 集合,並針對每個數據列,排序數據行的值會儲存在變數中 currentValue

注意

若要判斷特定數據列排序數據行的值,我使用儲存格的 Text 屬性。 這適用於 BoundFields,但不適用於 TemplateFields、CheckBoxFields 等等。 我們將稍後探討如何考慮替代 GridView 欄位。

然後會 currentValue 比較和 lastValue 變數。 如果它們不同,我們需要將新的分隔符數據列新增至控制階層。 這可藉由判斷 物件Rows集合中的 Table 索引GridViewRow、建立新的 GridViewRowTableCell 實例,然後將 和 GridViewRow 加入TableCell至控件階層來完成。

請注意,分隔符數據列的 TableCell 長度會格式化,使其跨越 GridView 的整個寬度、使用 SortHeaderRowStyle CSS 類別進行格式化,並具有其 Text 屬性,讓其同時顯示排序組名 (,例如 Category ) ,以及群組的值 (,例如「) 」。 最後, lastValue 會更新為的值 currentValue

用來格式化排序群組標頭數據列 SortHeaderRowStyle 的 CSS 類別必須在檔案中 Styles.css 指定。 請隨意使用任何樣式設定吸引您;我使用了下列專案:

.SortHeaderRowStyle
{
    background-color: #c00;
    text-align: left;
    font-weight: bold;
    color: White;
}

使用目前的程式代碼時,排序介面會在依任何 BoundField 排序時新增排序群組標頭 (請參閱圖 5,其中顯示依供貨商排序時) 的螢幕快照。 不過,依任何其他字段類型排序時, (例如 CheckBoxField 或 TemplateField) ,則找不到排序群組標頭 (請參閱圖 6) 。

排序介面包含依 BoundFields 排序時的排序群組標頭

圖 5:排序依 BoundFields 排序時,排序介面包含排序群組標頭, (按兩下即可檢視大小完整的影像)

排序 CheckBoxField 時遺漏排序群組標頭

圖 6:排序 CheckBoxField 時遺漏排序群組標頭 (按兩下即可檢視大小完整的影像)

依 CheckBoxField 排序時遺漏排序群組標頭的原因是程式代碼目前只 TableCell 使用 s Text 屬性來判斷每個數據列排序數據行的值。 若為 CheckBoxFields,s TableCellText 屬性是空字串,而是可透過位於 集合Controls內的 TableCell CheckBox Web 控制項取得此值。

若要處理 BoundFields 以外的欄位類型,我們需要增強指派變數以檢查集合中 TableCellControls CheckBox 是否存在的程式代碼currentValue。 不要使用 currentValue = gvr.Cells(sortColumnIndex).Text,請將此程式代碼取代為下列專案:

Dim currentValue As String = String.Empty
If gvr.Cells(sortColumnIndex).Controls.Count > 0 Then
    If TypeOf gvr.Cells(sortColumnIndex).Controls(0) Is CheckBox Then
        If CType(gvr.Cells(sortColumnIndex).Controls(0), CheckBox).Checked Then
            currentValue = "Yes"
        Else
            currentValue = "No"
        End If
        ' ... Add other checks here if using columns with other
        '      Web controls in them (Calendars, DropDownLists, etc.) ...
    End If
Else
    currentValue = gvr.Cells(sortColumnIndex).Text
End If

此程式代碼會檢查目前數據列的已排序 TableCell 數據行,以判斷集合中 Controls 是否有任何控件。 如果有,而第一個控件是 CheckBox,則 currentValue 變數會根據 CheckBox 的 Checked 屬性設定為 [是] 或 [否]。 否則,值會取自 TableCell s Text 屬性。 您可以復寫此邏輯,以處理任何可能存在於 GridView 中的 TemplateFields 排序。

新增上述程式代碼之後,排序群組標頭現在會在依已停止的 CheckBoxField 排序時出現 (請參閱圖 7) 。

排序 CheckBoxField 時,現在會出現排序群組標頭

圖 7:排序 CheckBoxField 時,現在會出現排序群組標頭 (按兩下即可檢視大小完整的影像)

注意

如果您有具有 NULLSupplierIDUnitPrice 欄位資料庫值CategoryID的產品,則這些值預設會在 GridView 中顯示為空字串,這表示具有值之產品的NULL分隔符數據列文字會如 Category: (即,類別目錄之後沒有名稱:例如 Category: 一般類別: [類別] ) 。 如果您想要在此處顯示的值,您可以將 BoundFields NullDisplayText 屬性 設定為所要顯示的文字,或者將 指派 currentValue 給分隔符列 s Text 屬性時,可以在 Render 方法中新增條件語句。

摘要

GridView 不包含許多用於自定義排序介面的內建選項。 不過,使用一些低階程序代碼,可以調整 GridView 的控制階層,以建立更自定義的介面。 在本教學課程中,我們已瞭解如何為可排序的 GridView 新增排序群組分隔符數據列,以更輕鬆地識別不同的群組和這些群組界限。 如需自定義排序介面的其他範例,請參閱 Scott Guthrie s A Few ASP.NET 2.0 GridView Sorting Tips and Tricks 部落格文章。

快樂的程序設計!

關於作者

Scott Mitchell 是七份 ASP/ASP.NET 書籍的作者,以及 1998 年以來與 Microsoft Web 技術合作的 4GuysFromRolla.com 作者。 Scott 是獨立顧問、訓練員和作者。 他的最新書籍是 Sams 在 24 小時內自行 ASP.NET 2.0。 您可以透過mitchell@4GuysFromRolla.com部落格來連線到 ,您可以在 找到http://ScottOnWriting.NET