在开发数据库应用程序时,经常需要展示和编辑大量的数据。WTL(Windows Template Library)提供了一套轻量级的模板库,用于创建Windows应用程序。然而,WTL并没有提供足够灵活的网格控件来满足所有需求。因此,本文将介绍如何使用WTL创建一个自定义的网格控件,以满足特定的数据展示和编辑需求。
在开发过程中,使用了标准的列表视图来展示数据,并使用单独的对话框进行编辑。虽然市面上有许多不同的网格控件,但它们并没有完全符合需求。与其修改现有的控件,不如从头开始创建一个更符合需求的网格控件。
为了演示自定义网格控件的功能,创建了一个名为“Northwind”的示例。这个示例需要在本地计算机上安装SQL服务器,并安装“northwind”数据库。此外,还需要为“sa”账户设置无密码登录。可以通过修改MainFrm.h
中的连接字符串并重新编译来适应不同的环境。这些解决方案是在Visual Studio.NET 2003中创建的,因此无法在旧版本中打开,但可以在新项目中添加文件来使用。
自定义网格控件具有以下特点:
_variant_t
(对于字符串使用_bstr_t
)。未来可能会为这个网格控件添加更多功能,但目前这些功能已经足够满足需求。欢迎任何关于改进的评论和建议。
下面将展示如何创建最基本的网格。这里从一个标准的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");
通过调用AddRow
和SetItem
来添加行。在这个例子中,在添加行之前和之后使用了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_NULL
和VT_EMPTY
返回true
。BOOL PreTranslateMessage(MSG* pMsg)
:应从主窗体PreTranslateMessage
函数调用。没有它,单元格之间的Tab键将无法工作。如果在模态对话框中使用此网格,Tab键也将无法工作,因为PreTranslateMessage
无法被调用。改为打开模态对话框,并禁用父窗口。void SetColumnFocus()
:将焦点设置到列。只有在编辑模式下才重要。如果字段验证失败,并且想要焦点缺失的值,这很有用。void SetItem()
:设置单元格的值。void SetListener()
:必须设置为从CGridCtrl::CListener
继承的类。void SetNull()
:一个静态函数,用于将_variant_t
的值设置为null。