首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

ASP.NET GridView 列選中高亮、滑鼠懸停變色與點擊事件完整實作

     編輯:ASP.NET 基礎

需求與互動設計

  • 滑鼠懸停:列背景變色、游標變手型,離開還原。
  • 列點選:背景加深色標示「已選中」,單選模式 (點選新列取消舊列)。
  • 觸發伺服器事件:點選後可執行後端邏輯 (如顯示明細、刪除、導向)。
  • 相容性:支援分頁、排序、UpdatePanel 非同步回發。

完整實作方案:純前端 CSS + JS + 隱藏欄位傳遞 Key

**核心概念**:GridView 渲染為 `

`,我們在 `RowDataBound` 註冊客戶端屬性,JS 維護「目前選中列」狀態,點選時將主鍵寫入隱藏 ``,再 `__doPostBack` 觸發伺服器端事件。

前端 .aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="GridViewSelectDemo.aspx.cs" Inherits="Demo.GridViewSelectDemo" %>
<!DOCTYPE html>
<html><head><title>GridView 列選中互動</title>
<style>
    .gv { border-collapse: collapse; width: 100%; font-family: 'Microsoft JhengHei', sans-serif; }
    .gv th { background: #007bff; color: white; padding: 10px; text-align: left; cursor: pointer; }
    .gv td { padding: 8px 10px; border-bottom: 1px solid #eee; vertical-align: middle; }
    .gv tbody tr { cursor: pointer; transition: background 0.2s; }
    .gv tbody tr:hover { background: #e3f2fd !important; } /* 懸停最高優先級 */
    .gv tbody tr.selected { background: #cce5ff !important; font-weight: bold; } /* 選中狀態 */
    .hidden-col { display: none; } /* 隱藏主鍵欄 */
</style></head><body>
<form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />

    <h2>員工資料列表 (點選列查看明細)</h2>
    
    <!-- 隱藏欄位:儲存目前選中的 EmployeeID -->
    <asp:HiddenField ID="hfSelectedId" runat="server" OnValueChanged="hfSelectedId_ValueChanged" />

    <asp:UpdatePanel ID="upGrid" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <asp:GridView ID="gvEmployees" runat="server" 
                AutoGenerateColumns="False" 
                DataKeyNames="EmployeeID" 
                CssClass="gv"
                OnRowDataBound="gvEmployees_RowDataBound"
                AllowPaging="True" PageSize="10"
                OnPageIndexChanging="gvEmployees_PageIndexChanging"
                AllowSorting="True"
                OnSorting="gvEmployees_Sorting">
                
                <Columns>
                    <asp:BoundField DataField="EmployeeID" HeaderText="編號" ItemStyle-CssClass="hidden-col" HeaderStyle-CssClass="hidden-col" />
                    <asp:BoundField DataField="LastName" HeaderText="姓氏" />
                    <asp:BoundField DataField="FirstName" HeaderText="名字" />
                    <asp:BoundField DataField="Title" HeaderText="職稱" />
                    <asp:BoundField DataField="HireDate" HeaderText="到職日" DataFormatString="{0:yyyy/MM/dd}" HtmlEncode="False" />
                    <asp:BoundField DataField="City" HeaderText="城市" />
                </Columns>
                
                <PagerSettings Mode="NumericFirstLast" PageButtonCount="5" />
                <PagerStyle CssClass="pagination" HorizontalAlign="Center" />
            </asp:GridView>
        </ContentTemplate>
        <Triggers>
            <asp:AsyncPostBackTrigger ControlID="gvEmployees" EventName="PageIndexChanging" />
            <asp:AsyncPostBackTrigger ControlID="gvEmployees" EventName="Sorting" />
        </Triggers>
    </asp:UpdatePanel>

    <div style="margin-top:20px; padding:15px; background:#f8f9fa; border:1px solid #dee2e6;">
        <h4>選中員工明細</h4>
        <asp:DetailsView ID="dvDetail" runat="server" AutoGenerateRows="True" CssClass="table table-bordered" />
    </div>
</form>

<script>
    (function () {
        var gv = document.getElementById('<%= gvEmployees.ClientID %>');
        var hf = document.getElementById('<%= hfSelectedId.ClientID %>');
        var lastTr = null;

        if (!gv) return;

        // 事件委派:針對 tbody tr
        gv.querySelector('tbody').addEventListener('click', function (e) {
            var tr = e.target.closest('tr');
            if (!tr || tr === gv.querySelector('thead tr')) return;

            // 取得 DataKey (EmployeeID) - 從隱藏欄位或 data 屬性
            var key = tr.cells[0].innerText.trim(); // 第 0 欄為 EmployeeID (隱藏但存在 DOM)
            // 或使用 data-key: tr.dataset.key (見 RowDataBound 註冊)

            // 切換 selected class
            if (lastTr) lastTr.classList.remove('selected');
            tr.classList.add('selected');
            lastTr = tr;

            // 更新隱藏欄位並觸發伺服器端 ValueChanged
            if (hf.value !== key) {
                hf.value = key;
                // 手動觸發 change 事件 (ASP.NET HiddenField 需此才引發 OnValueChanged)
                if ('onpropertychange' in hf) hf.fireEvent('onpropertychange'); // IE
                else {
                    var evt = document.createEvent('HTMLEvents');
                    evt.initEvent('change', true, false);
                    hf.dispatchEvent(evt);
                }
            }
        });

        // 分頁/排序後重新套用選中狀態 (UpdatePanel 非同步更新後)
        var prm = Sys.WebForms.PageRequestManager.getInstance();
        prm.add_endRequest(function () {
            gv = document.getElementById('<%= gvEmployees.ClientID %>'); // 重新取得
            if (gv && hf.value) {
                var rows = gv.querySelectorAll('tbody tr');
                rows.forEach(function (tr) {
                    if (tr.cells[0].innerText.trim() === hf.value) {
                        tr.classList.add('selected');
                        lastTr = tr;
                    }
                });
            }
        });
    })();
</script>

後端 .aspx.cs

using System;
using System.Data;
using System.Web.UI.WebControls;

namespace Demo
{
    public partial class GridViewSelectDemo : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                BindGrid();
            }
        }

        private void BindGrid()
        {
            // 模擬資料來源 (實務從 DB / EF / Service 取得)
            DataTable dt = GetEmployeeData();
            gvEmployees.DataSource = dt;
            gvEmployees.DataBind();
        }

        // 核心:RowDataBound 註冊 data-key 與滑鼠樣式
        protected void gvEmployees_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                // 1. 取得主鍵 (DataKeyNames 設定的欄位)
                string empId = gvEmployees.DataKeys[e.Row.RowIndex].Value.ToString();

                // 2. 註冊 data-key 供 JS 讀取 (比解析 innerText 更可靠)
                e.Row.Attributes["data-key"] = empId;

                // 3. 滑鼠懸停樣式 (CSS :hover 已處理,此處可加強制游標)
                e.Row.Style["cursor"] = "pointer";

                // 4. 若為目前選中列,加上 selected class (配合 endRequest 重新套用)
                if (hfSelectedId.Value == empId)
                {
                    e.Row.CssClass = "selected";
                }
            }
        }

        // 隱藏欄位值改變 → 觸發明細查詢
        protected void hfSelectedId_ValueChanged(object sender, EventArgs e)
        {
            if (int.TryParse(hfSelectedId.Value, out int empId))
            {
                LoadDetail(empId);
            }
        }

        private void LoadDetail(int empId)
        {
            DataTable dt = GetEmployeeDetail(empId); // 查詢單筆明細
            dvDetail.DataSource = dt;
            dvDetail.DataBind();
        }

        // 分頁
        protected void gvEmployees_PageIndexChanging(object sender, GridViewPageEventArgs e)
        {
            gvEmployees.PageIndex = e.NewPageIndex;
            BindGrid();
        }

        // 排序
        protected void gvEmployees_Sorting(object sender, GridViewSortEventArgs e)
        {
            // 簡易排序實作 (實務建議在資料層排序)
            DataTable dt = GetEmployeeData();
            dt.DefaultView.Sort = e.SortExpression + (e.SortDirection == SortDirection.Ascending ? " ASC" : " DESC");
            gvEmployees.DataSource = dt;
            gvEmployees.DataBind();
        }

        // --- 模擬資料存取 ---
        private DataTable GetEmployeeData()
        {
            var dt = new DataTable();
            dt.Columns.Add("EmployeeID", typeof(int));
            dt.Columns.Add("LastName", typeof(string));
            dt.Columns.Add("FirstName", typeof(string));
            dt.Columns.Add("Title", typeof(string));
            dt.Columns.Add("HireDate", typeof(DateTime));
            dt.Columns.Add("City", typeof(string));

            dt.Rows.Add(1, "Davolio", "Nancy", "Sales Representative", new DateTime(1992, 5, 1), "Seattle");
            dt.Rows.Add(2, "Fuller", "Andrew", "Vice President, Sales", new DateTime(1993, 8, 14), "Tacoma");
            dt.Rows.Add(3, "Leverling", "Janet", "Sales Representative", new DateTime(1993, 4, 1), "Kirkland");
            dt.Rows.Add(4, "Peacock", "Margaret", "Sales Representative", new DateTime(1993, 5, 3), "Redmond");
            dt.Rows.Add(5, "Buchanan", "Steven", "Sales Manager", new DateTime(1993, 10, 17), "London");
            dt.Rows.Add(6, "Suyama", "Michael", "Sales Representative", new DateTime(1993, 10, 17), "London");
            dt.Rows.Add(7, "King", "Robert", "Sales Representative", new DateTime(1994, 1, 2), "London");
            dt.Rows.Add(8, "Callahan", "Laura", "Inside Sales Coordinator", new DateTime(1994, 3, 5), "Seattle");
            dt.Rows.Add(9, "Dodsworth", "Anne", "Sales Representative", new DateTime(1994, 11, 15), "London");
            return dt;
        }

        private DataTable GetEmployeeDetail(int empId)
        {
            var dt = new DataTable();
            dt.Columns.Add("Field");
            dt.Columns.Add("Value");

            // 實務查詢 DB
            var row = GetEmployeeData().Select($"EmployeeID = {empId}")[0];
            foreach (DataColumn col in row.Table.Columns)
            {
                dt.Rows.Add(col.ColumnName, row[col]);
            }
            return dt;
        }
    }
}

關鍵技術點解析

技術點實作方式優勢
主鍵傳遞 DataKeyNames="EmployeeID" + RowDataBound 寫入 data-key 屬性 不暴露於可見欄位、JS 精準讀取、支援複合主鍵
選中狀態持久化 HiddenField + OnValueChanged 觸發後端 跨回發保持狀態、UpdatePanel 非同步更新後可透過 JS 還原
樣式隔離 CSS :hover 處理懸停,.selected class 處理選中 無需 JS 監聽 mouseover/mouseout、效能最佳、無閃爍
分頁/排序後還原選中 PageRequestManager.add_endRequest 重掃 DOM 套用 class UpdatePanel 局部更新不會遺失選中視覺狀態
伺服器端事件觸發 HiddenField.ValueChanged → LoadDetail 符合 Web Forms 事件模型、可直接存取伺服器控件

進階擴充:多選 (Checkbox) + 批次操作

<Columns>
    <asp:TemplateField HeaderText="選取" ItemStyle-Width="40px">
        <ItemTemplate>
            <asp:CheckBox ID="chkSelect" runat="server" 
                value='<%# Eval("EmployeeID") %>' 
                onclick="toggleRowSelect(this)" />
        </ItemTemplate>
    </asp:TemplateField>
    ...
</Columns>

<script>
function toggleRowSelect(cb) {
    var tr = cb.closest('tr');
    tr.classList.toggle('selected', cb.checked);
    // 收集所有勾選的 ID 送至 HiddenField 或直接 AJAX 批次刪除
}
</script>

常見問題與解決

  • 點選列無反應:確認 GridView 已渲染 ``、JS 選取器正確、無其他元素攔截 click (如按鈕、連結需 `e.stopPropagation()`)。
  • 選中樣式被 :hover 覆蓋:CSS 優先順序 `.selected` 需高於 `:hover`,或用 `!important` (如範例)。
  • UpdatePanel 更新後 JS 失效:必須在 `endRequest` 重新綁定事件或採用事件委派 (範例已用委派)。
  • DataKey 取值錯誤:`DataKeyNames` 必須與資料來源欄位名稱完全一致、區分大小寫。

現代化替代:前端表格元件 (DataTables, AG Grid, TanStack Table)

若專案允許引入前端框架,強烈建議棄用 GridView,改用:

  • DataTables.net:jQuery 套件,支援分頁、排序、搜尋、選中、匯出、響應式,零後端渲染。
  • AG Grid (Community):企業級功能、虛擬捲動、Row Selection、Range Selection、Excel 匯出。
  • TanStack Table (React/Vue/Solid):Headless UI、TypeScript 優先、完全可控渲染。

後端只提供 GET /api/employees?page=1&size=10&sort=name 回傳 JSON,前端完全掌控互動與狀態。

總結

GridView 列選中高亮的最佳實踐:**CSS 處理視覺狀態 (hover/selected)** + **data-key 屬性攜帶主鍵** + **HiddenField 傳遞選中值觸發後端事件** + **endRequest 還原視覺狀態**。此方案零 ViewState 擴充、相容分頁排序、程式碼清晰可維護。長期專案請規劃遷移至前端表格元件 + Web API 架構。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved