:顯示查詢結果與列格式化)
簡介面向 MFC 開發(fā)者的 DataGrid 控件用法詳解文檔重點解決在 VC 對話框程序中網格化展示與操作數(shù)據的問題適合需要快速上手 ADO 數(shù)據綁定、列控制與格式化的讀者。文檔以可運行工程 TestGrid 為基礎系統(tǒng)講解將查詢結果通過 SetRefDataSource 綁定到控件、利用 CColumns/CColumn 調整列寬、使用 SetNumberFormat 將小數(shù)顯示為百分數(shù)、設置行高實現(xiàn)多行顯示以及通過 HeadClick 消息進行排序等關鍵用法并配有 VC6.0 下的調試說明使讀者能夠邊讀邊練、直接對照實踐。資源為單份 doc 文檔壓縮包大小 226KB內容集中、輕量精煉適合作為 MFC 界面開發(fā)中網格控件的案頭參考目前已有 849 人學習下載是入門與進階 DataGrid 控件時不可多得的實用資料。1. MFC DataGrid控件老項目里最省事的網格方案先搞懂再動手接觸過VC6.0時代MFC項目的工程師大概率都跟DataGrid控件打過照面。這玩意是微軟自家提供的ActiveX網格控件用來顯示數(shù)據庫查詢結果非常順手當年很多桌面管理系統(tǒng)里的表格界面都是它撐起來的。它的優(yōu)勢在于和Windows系統(tǒng)兼容性極好你不需要自己實現(xiàn)排序、列寬調整、單元格格式化這些功能只要把數(shù)據源接上界面就出來了。網上雖然有不少第三方網格控件功能看著更花哨但用微軟自家的東西有個實際好處——它隨系統(tǒng)升級你的代碼幾乎不用大改就能繼續(xù)跑。這篇筆記我把實際拆過的工程VC6.0 Windows XP工程名TestGrid里的用法整理出來覆蓋三塊核心操作顯示查詢結果、控制列寬、格式化單元格內容。適合正在維護老MFC項目、或者被迫用VC6.0干活的朋友新手按步驟走也能跑通。2. 準備工作插入控件與類封裝別在這步偷懶2.1 找到并插入Microsoft DataGrid控件先建立一個基于對話框的MFC工程然后在對話框編輯器里右鍵選擇插入ActiveX控件。在彈出的列表里找到Microsoft DataGrid Control, version 6.0 (OLEDB)確定插入。注意這里要選OLEDB版本不是ODBC那個老版本因為OLEDB的連接方式更靈活配合ADO用起來最順手。插入后對話框上會出現(xiàn)一個空的網格默認帶一個示例列。這時候先別急著寫代碼右鍵這個控件選擇建立類。VC6.0會彈出一個確認類名的對話框這里需要手動選擇要封裝的類。一般情況下至少選三個CDataGrid控件主類、CColumns列集合類、CColumn單列類。提示這幾個類是對ActiveX控件COM接口的封裝本質上是調用了控件的COM查詢接口。所以你看不到控件內部源碼只能通過方法名推斷功能。這也是為什么很多新手一開始覺得它像個黑匣子——其實方法名起得很直白見名知意。2.2 認識三個核心類的關系CDataGrid是控件本身負責整體顯示和數(shù)據源掛接。CColumns是列集合通過CDataGrid.GetColumns()拿到。CColumn是單個列對象通過CColumns.GetItem()拿到。這個關系一定要理清楚。打個比方CDataGrid是一張表CColumns是所有列的目錄CColumn是目錄里某一列的詳細檔案。你要改列寬、改格式、改標題最終操作的都是CColumn對象。還有個容易忽略的點GetItem()的參數(shù)是VARIANT類型傳索引時要小心類型轉換。常見做法是構造一個VARIANT變量賦值vt.iVal 列索引vt.vt VT_I4。直接傳整數(shù)在某些編譯器版本會出類型不匹配的警告。// 正確獲取列對象的方式 CColumns cols m_ctrlDG.GetColumns(); VARIANT vt; vt.vt VT_I4; vt.iVal 0; // 第0列 CColumn col cols.GetItem(vt);這段代碼先拿到列集合再通過VARIANT類型的索引取出具體列。參數(shù)說明vt是VARIANT結構體必須顯式設置vt類型為VT_I44字節(jié)整數(shù)不然COM調用可能失敗。3. 顯示查詢結果ADO連接數(shù)據庫把Recordset綁定給控件3.1 建立數(shù)據庫連接和執(zhí)行查詢原文用的是ACCESS數(shù)據庫文件note.mdb連接字符串用Jet OLEDB 4.0 Provider。這個Provider在32位系統(tǒng)上很成熟但64位系統(tǒng)上經常翻車——后面避坑章節(jié)會詳細說。CADODatabase m_DBCn; CADORecordset m_Rs; CDataGrid m_ctrlDG; CString strConnection; strConnection.Format(_T(ProviderMicrosoft.Jet.OLEDB.4.0;) _T(Data Sourcenote.mdb)); m_DBCn.Open((LPCTSTR)strConnection); m_Rs.SetDatabase(m_DBCn); m_Rs.Open(_T(select * from test;)); m_ctrlDG.SetRefDataSource((LPUNKNOWN)m_Rs.GetRecordset());注意這段代碼里的ADO封裝類CADODatabase、CADORecordset不是MFC自帶的需要你自己引入一個ADO封裝類或者直接用原生ADO方式。邏輯說明前三行聲明對象然后連接字符串指定數(shù)據庫文件路徑打開連接把記錄集關聯(lián)到數(shù)據庫執(zhí)行查詢最后把記錄集的IUnknown接口傳給DataGrid控件做數(shù)據源。這里有個關鍵點SetRefDataSource的參數(shù)是LPUNKNOWN類型也就是COM接口指針。如果你用的是ADO的RecordsetPtr記得用GetInterfacePtr()獲取原始接口指針而不是直接傳智能指針。3.2 數(shù)據刷新時機與釋放問題我第一次做的時候踩過一個坑查詢執(zhí)行完畢后立即SetRefDataSource網格確實顯示了數(shù)據但關閉記錄集后網格又變空了。原因在于DataGrid控件內部持有的是記錄集的COM引用記錄集一旦釋放數(shù)據源就失效了。正確的做法是數(shù)據源連接和記錄集的生命周期要覆蓋DataGrid的使用周期。一般情況下m_Rs作為成員變量在對話框銷毀時才關閉。如果需要在同一控件上切換不同查詢結果可以用m_ctrlDG.Refresh()來刷新顯示。// 刷新數(shù)據源的通用寫法 m_Rs.Close(); m_Rs.Open(_T(select * from table2;)); m_ctrlDG.Refresh();數(shù)據源切換時先關閉舊的記錄集再開新的然后調Refresh讓控件重新取數(shù)據。參數(shù)說明Close后記錄集處于關閉狀態(tài)必須重新Open才能再次作為數(shù)據源。4. 控制顯示與格式化列寬、標題、百分數(shù)格式實操4.1 設置列寬和列標題之前已經講過怎么用CColumns和CColumn獲取列對象拿到列之后的控制手段才是重點。CColumns cols m_ctrlDG.GetColumns(); VARIANT vt; vt.vt VT_I4; vt.iVal 0; CColumn col cols.GetItem(vt); col.SetWidth(100); // 設置列寬為100像素 col.SetCaption(_T(學號)); // 設置列標題 col.SetVisible(FALSE); // 隱藏該列列的常用屬性就這幾個寬度、標題、可見性。SetWidth的單位是像素twips不這里是像素老版本VC里可能會有單位換算的問題——有次我設了個50結果列窄得看不見后來發(fā)現(xiàn)是單位不對。實際上MSDN的說明里寬度單位是OLE_XFORM_PIXELS但有些舊文檔說是緹twip實踐下來用像素最靠譜。4.2 數(shù)字格式化成百分數(shù)這個是DataGrid比較實用的功能之一。如果你的數(shù)據源里存的是0.85這樣的小數(shù)又想顯示成85.0%不用自己在SQL里做字符串拼接直接在列上設置格式就行。col.SetNumberFormat(_T(0.0%));這個格式串的含義和Excel里是一樣的0是數(shù)字占位.0表示保留一位小數(shù)%表示乘以100并加百分號。同理如果你要顯示成兩位小數(shù)的普通數(shù)字用_(0.00)要加千分位分隔符用_(#,##0.00)。提示SetNumberFormat對整列生效如果你想在同一列里混用不同格式做不到。數(shù)據格式顯示的統(tǒng)一性在設計表格時就要考慮好。這里有個細節(jié)值得注意格式串里的百分號在CString里沒有任何特殊含義直接傳字符串就行。但如果是通過資源文件加載字符串別忘了百分號在格式化輸出時的轉義問題。4.3 行高與多行顯示原文提到了多行顯示對應到代碼就是設置行高。m_ctrlDG.SetRowHeight(30);行高的單位為像素默認值大約是20左右。設成30后每行高度增加適合顯示內容比較長的單元格也支持自動換行。不過要注意行高只影響數(shù)據顯示區(qū)域不改變表頭高度。4.4 列排序功能用HeadClick消息實現(xiàn)DataGrid控件有個HeadClick事件點擊列標題時觸發(fā)。利用這個事件可以做點擊列名排序。// 在頭文件中聲明消息處理函數(shù) afx_msg void OnHeadClick(short ColIndex); // 消息映射 ON_EVENT(CDataGrid, 0, 27, OnHeadClick, VTS_I2)消息映射里的事件ID27是HeadClick在DataGrid控件的dispatch ID。不同版本控件可能不一樣如果消息沒響應可以用OLE/COM Object Viewer查一下控件的類型庫。排序邏輯可以用ADO記錄集的Sort屬性實現(xiàn)void CMyDlg::OnHeadClick(short ColIndex) { CString strSort; // 根據列索引構造排序字段名 CColumns cols m_ctrlDG.GetColumns(); VARIANT vt; vt.vt VT_I4; vt.iVal ColIndex; CColumn col cols.GetItem(vt); CString strField col.GetCaption(); m_Rs.Sort _T(學號 ASC); m_ctrlDG.Refresh(); }注意ADO的Sort屬性只在客戶端游標模式下有效。如果記錄集用的是服務器端游標Sort屬性會失效具體表現(xiàn)就是點標題沒反應。5. 避坑指南DataGrid常見問題與排查記錄5.1 現(xiàn)象64位系統(tǒng)上無法注冊控件或者運行時崩潰原因Microsoft DataGrid 6.0是32位ActiveX控件在64位系統(tǒng)上需要注冊到SysWOW64目錄而且運行時需要通過32位進程加載。如果你的程序編譯成64位直接使用這個控件會失敗。解決一種辦法是強制編譯成32位程序VC6.0本身只能產出32位程序所以這個問題主要在把舊代碼遷移到VS2010以上版本時出現(xiàn)。另一種辦法是用regsvr32注冊32位版本的控件CD C:\Windows\SysWOW64然后regsvr32 msdatgrd.ocx。5.2 現(xiàn)象SetRefDataSource后網格顯示空白數(shù)據沒出來原因記錄集沒有處于打開狀態(tài)或者記錄集的游標類型不支持綁定。DataGrid要求數(shù)據源是客戶端游標adUseClient如果使用adUseServer控件拿不到數(shù)據。解決打開記錄集時顯式指定客戶端游標——在ADO封裝類里調用Open之前設置CursorLocation參數(shù)或者在Open語句里加上adCmdText和adUseClient。5.3 現(xiàn)象列寬設置不起作用或者寬度值不對原因SetWidth接收的實際是緹twip值不是像素值。1像素等于15緹按96DPI計算所以如果直接傳像素值列寬會縮小15倍。解決把像素值乘以15再傳給SetWidth。想省事的話用屏幕DC的GetDeviceCaps(LOGPIXELSX)把DPI取出來然后換算。舊代碼里如果看到SetWidth(1500)這種值基本就是這個原因。5.4 現(xiàn)象點擊列標題排序沒反應原因事件映射ID不對或者記錄集游標類型不對。HeadClick的dispatch ID在不同版本控件里可能有差異用錯ID事件不會觸發(fā)。解決先用OLE/COM Object Viewer打開msdatgrd.ocx的類型庫找到HeadClick的dispatch ID確認是你要用的版本。然后檢查記錄集的CursorLocation確保是adUseClient。5.5 現(xiàn)象程序退出時崩潰報錯在DataGrid釋放階段原因DataGrid控件和記錄集的釋放順序不對。如果先釋放記錄集再銷毀控件控件內部還持有已失效的COM引用釋放時就會崩潰。解決在對話框的OnDestroy里先銷毀控件或者調用m_ctrlDG.SetRefDataSource(NULL)斷開數(shù)據源然后再關閉記錄集。釋放順序是先斷開數(shù)據源關閉記錄集再銷毀控件。6. 進階玩法DataGrid控件的邊界與替代方案把原工程跑通之后有幾個點值得繼續(xù)深入。DataGrid 6.0最明顯的限制是它依賴OLEDB無法直接綁定自定義數(shù)據結構比如數(shù)組或C容器。如果不想走數(shù)據庫可以考慮的替代方案是MSFlexGrid但MSFlexGrid的格式化能力又差一些。對于還在維護老項目的場景我的習慣做法是把DataGrid封裝成一個自繪控件類內部統(tǒng)一處理數(shù)據源綁定、列格式設置、排序邏輯。這樣即使以后換控件界面層的改動也能控制在一個類里面。另一個比較實用的技巧是動態(tài)生成列。默認情況下DataGrid會根據數(shù)據源的字段自動生成列。如果只想顯示部分字段可以在綁定之前先清除默認列然后手動添加需要的列m_ctrlDG.GetColumns().Clear(); // 重新添加列 CColumn col1 m_ctrlDG.GetColumns().Add(0); col1.SetCaption(_T(ID)); col1.SetDataField(_T(id));列集合的Add方法需要傳入列索引返回新的CColumn對象。然后用SetDataField指定這個列綁定到記錄集的字段名。這個方法適合做動態(tài)報表用戶選哪些字段就顯示哪些列。關于單元格合并、背景色設置、字體控制這些高級功能DataGrid 6.0支持得不好。真有這些需求建議跳槽到更現(xiàn)代的控件——但如果是老系統(tǒng)維護能用DataGrid把數(shù)據展示好、格式調對、排序做出來功能上已經夠業(yè)務用了。從那以后我每次接DataGrid相關的活兒都會強制先跑一遍注冊控件、連數(shù)據庫、綁數(shù)據源、調列寬格式、點排序把基礎五步走完再談別的。這套流程走下來能過濾掉大部分低級問題。希望幫到你。本文還有配套的精品資源點擊獲取