共用方式為


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

作者 :Scott Mitchell

下載 PDF

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

簡介

顯示排序數據的完整清單時,排序數據列中只有少數不同的值,終端使用者可能會發現很難分辨出差異界限的確切位置。 例如,資料庫中有81個產品,但只有九個不同的類別選擇 (八個唯一類別加上 NULL 選項) 。 請考慮有興趣檢查屬於 [快取] 類別之產品的使用者案例。 從列出單一 GridView 中 所有 產品的頁面中,使用者可能會決定最佳選擇是依類別排序結果,這會將所有「新式」產品群組在一起。 依類別排序之後,用戶接著需要搜尋清單,以尋找群組的元素產品開始和結束的位置。 由於結果會依尋找「新月」產品的類別名稱依字母順序排序,但仍然需要仔細掃描方格中的項目清單。

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

已清楚識別每個類別群組

圖 1:每個類別群組在 單擊即可檢視全大小影像 ()

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

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

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

接下來,設定 GridView,使其只包含 ProductNameCategoryNameSupplierNameUnitPrice BoundFields 和已停止的 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 事件的 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 排序群組播放位

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

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

protected override void Render(HtmlTextWriter writer)
{
    // Add code to manipulate the GridView control hierarchy
    base.Render(writer);
}

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

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

注意

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

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

protected override void Render(HtmlTextWriter writer)
{
    // Only add the sorting UI if the GridView is sorted
    if (!string.IsNullOrEmpty(ProductList.SortExpression))
    {
        // Determine the index and HeaderText of the column that
        //the data is sorted by
        int sortColumnIndex = -1;
        string sortColumnHeaderText = string.Empty;
        for (int i = 0; i < ProductList.Columns.Count; i++)
        {
            if (ProductList.Columns[i].SortExpression.CompareTo(ProductList.SortExpression)
                == 0)
            {
                sortColumnIndex = i;
                sortColumnHeaderText = ProductList.Columns[i].HeaderText;
                break;
            }
        }
        // TODO: Scan the rows for differences in the sorted column�s values
}

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

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

protected override void Render(HtmlTextWriter writer)
{
    // Only add the sorting UI if the GridView is sorted
    if (!string.IsNullOrEmpty(ProductList.SortExpression))
    {
        // ... Code for finding the sorted column index removed for brevity ...
        // Reference the Table the GridView has been rendered into
        Table gridTable = (Table)ProductList.Controls[0];
        // Enumerate each TableRow, adding a sorting UI header if
        // the sorted value has changed
        string lastValue = string.Empty;
        foreach (GridViewRow gvr in ProductList.Rows)
        {
            string currentValue = gvr.Cells[sortColumnIndex].Text;
            if (lastValue.CompareTo(currentValue) != 0)
            {
                // there's been a change in value in the sorted column
                int rowIndex = gridTable.Rows.GetRowIndex(gvr);
                // Add a new sort header row
                GridViewRow sortRow = new GridViewRow(rowIndex, rowIndex,
                    DataControlRowType.DataRow, DataControlRowState.Normal);
                TableCell sortCell = 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;
            }
        }
    }
    base.Render(writer);
}

此程式代碼會以程式設計方式參考 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,請將此程式代碼取代為下列專案:

string currentValue = string.Empty;
if (gvr.Cells[sortColumnIndex].Controls.Count > 0)
{
    if (gvr.Cells[sortColumnIndex].Controls[0] is CheckBox)
    {
        if (((CheckBox)gvr.Cells[sortColumnIndex].Controls[0]).Checked)
            currentValue = "Yes";
        else
            currentValue = "No";
    }
    // ... Add other checks here if using columns with other
    //      Web controls in them (Calendars, DropDownLists, etc.) ...
}
else
    currentValue = gvr.Cells[sortColumnIndex].Text;

此程式代碼會檢查目前數據列的已排序 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