WTL 数据库应用程序中的自定义网格控件

在开发数据库应用程序时,经常需要展示和编辑大量的数据。WTL(Windows Template Library)提供了一套轻量级的模板库,用于创建Windows应用程序。然而,WTL并没有提供足够灵活的网格控件来满足所有需求。因此,本文将介绍如何使用WTL创建一个自定义的网格控件,以满足特定的数据展示和编辑需求。

创建自定义网格控件的动机

在开发过程中,使用了标准的列表视图来展示数据,并使用单独的对话框进行编辑。虽然市面上有许多不同的网格控件,但它们并没有完全符合需求。与其修改现有的控件,不如从头开始创建一个更符合需求的网格控件。

演示环境要求

为了演示自定义网格控件的功能,创建了一个名为“Northwind”的示例。这个示例需要在本地计算机上安装SQL服务器,并安装“northwind”数据库。此外,还需要为“sa”账户设置无密码登录。可以通过修改MainFrm.h中的连接字符串并重新编译来适应不同的环境。这些解决方案是在Visual Studio.NET 2003中创建的,因此无法在旧版本中打开,但可以在新项目中添加文件来使用。

自定义网格控件的特点

自定义网格控件具有以下特点:

  • 行数仅受内存限制。在2 GHz的机器上,已经测试了100000行数据,没有遇到问题。
  • 单元格可以使用标准的编辑控件、组合框(下拉或下拉列表)或日期时间选择器进行编辑。
  • 为数据库用户设计,因此所有内容都使用_variant_t(对于字符串使用_bstr_t)。
  • 组合框使用查找值来设置列的值。ID字段放置在网格中,网格显示和编辑信息性文本字段。

未来可能会为这个网格控件添加更多功能,但目前这些功能已经足够满足需求。欢迎任何关于改进的评论和建议。

如何使用

下面将展示如何创建最基本的网格。这里从一个标准的WTLAppWizard应用程序开始,将主窗体视图替换为网格。创建网格后,设置了样式,以便在网格中点击右键时显示上下文菜单。

CGridCtrl m_view; LRESULT CMainFrame::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // ... m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE); m_view.SetExtendedGridStyle(GS_EX_CONTEXTMENU); // ... } m_view.AddColumn("Last Name", 140, CGridCtrl::EDIT_TEXT); m_view.AddColumn("First Name", 140, CGridCtrl::EDIT_TEXT);

现在将添加另一列,用于输入人的性别。这里将看到为AddColumn函数添加了更多的参数。第一个新参数是对齐方式,最后一个参数是列使用的数据类型。默认情况下使用了VT_BSTR,现在将性别信息存储为整数,所以使用VT_I4

m_view.AddColumn("Sex", 100, CGridCtrl::EDIT_DROPDOWNLIST, CGridCtrl::CENTER, VT_I4); m_view.AddColumnLookup("Sex", 1, "Male"); m_view.AddColumnLookup("Sex", 2, "Female");

通过调用AddRowSetItem来添加行。在这个例子中,在添加行之前和之后使用了SetRedraw。对于单行来说,这不是必需的,但对于多行来说是必须的。

m_view.SetRedraw(FALSE); long nItem = m_view.AddRow(); m_view.SetItem(nItem, "Last Name", "Henden"); m_view.SetItem(nItem, "First Name", "Bjørnar"); m_view.SetItem(nItem, "Sex", 1); m_view.SetRedraw(TRUE);

为了捕获事件,创建了一个名为CListener的类,可以从这个类继承。这个类不仅用于事件处理,还用于查询单元格背景色的信息。

class CListener { public: virtual bool OnRowChanging(UINT uID, long nRow); virtual void OnRowChanged(UINT uID, long nRow); virtual void OnEdit(UINT uID, long nRow); virtual bool OnDeleteRow(UINT uID, long nRow); virtual void OnNewRow(UINT uID, long nRow); virtual void OnModified(UINT uID, LPCTSTR pszColumn, _variant_t vtValue); virtual void OnRowActivate(UINT uID, long nRow); virtual COLORREF GetCellColor(UINT uID, long nRow, LPCTSTR pszColumn); virtual bool OnValidate(UINT uID); };

以下示例将展示如何在从网格中删除行时收到通知,并为已修改的行设置新的背景色。

class CMainFrame : public CFrameWindowImpl, ..., public CGridCtrl::CListener { LRESULT CMainFrame::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // ... m_view.SetListener(this); // ... } virtual bool OnDeleteRow(UINT uID, long nRow) { CString str; str.Format("Do you want to delete row %d?", nRow); // Returning false will abort the delete return IDYES == AtlMessageBox(m_hWnd, (LPCTSTR)str, IDR_MAINFRAME, MB_YESNO | MB_ICONQUESTION); } virtual COLORREF GetCellColor(UINT uID, long nRow, LPCTSTR pszColumn) { _variant_t vt = m_view.GetItem(nRow, "Sex"); if (!m_view.IsNull(vt)) { if ((long)vt == 1) // Blue-ish for males return RGB(192, 192, 255); else // And red-ish for females return RGB(255, 192, 192); } // Return (COLORREF)-1 to use default colors return (COLORREF)-1; } };
  • void AddColumn():向网格中添加一列。如果网格中有行,则无法添加列。此函数的最后一个参数是列的名称,可以作为其他函数的参数。如果省略此参数,则使用列标题作为名称。
  • void AddColumnLookup():向列添加查找值。这不一定是使用下拉列表的列,可以是任何列。
  • long AddRow():向网格中添加一行,并返回插入的行号。使用此返回值来设置此行的单个单元格值。
  • void ClearModified():当单元格被编辑时,它们将行状态设置为已修改。调用此函数可将指定行重置为未修改。指定-1表示所有行。
  • void DeleteAllColumns():删除所有列。
  • void DeleteAllItems():删除所有行。
  • void EnsureVisible():在网格的可见区域显示行。
  • long GetColumnCount():返回网格中的列数。
  • _variant_t GetEditItem():当处于编辑模式时,调用此函数以获取正在编辑的单元格的当前值。
  • _variant_t GetItem():返回行和单元格的值。
  • bool GetModified():如果行已修改,则返回true。对于所有行使用-1。
  • long GetRowCount():返回网格中的行数。
  • long GetSelectedRow():返回选中行的编号,如果没有行被选中,则返回-1。
  • bool IsNull():一个静态函数,用于检查_variant_t是否为null。对于VT_NULLVT_EMPTY返回true
  • BOOL PreTranslateMessage(MSG* pMsg):应从主窗体PreTranslateMessage函数调用。没有它,单元格之间的Tab键将无法工作。如果在模态对话框中使用此网格,Tab键也将无法工作,因为PreTranslateMessage无法被调用。改为打开模态对话框,并禁用父窗口。
  • void SetColumnFocus():将焦点设置到列。只有在编辑模式下才重要。如果字段验证失败,并且想要焦点缺失的值,这很有用。
  • void SetItem():设置单元格的值。
  • void SetListener():必须设置为从CGridCtrl::CListener继承的类。
  • void SetNull():一个静态函数,用于将_variant_t的值设置为null。
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485