65.9K
CodeProject 正在变化。 阅读更多。
Home

另一个WTL网格

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (36投票s)

2003年6月2日

5分钟阅读

viewsIcon

255932

downloadIcon

8402

一个 WTL 网格,主要用于与数据库交互。

Sample Image - WTLGrid2.png

目录

引言

我正在使用 WTL 编写一个数据库应用程序,到目前为止,我一直使用标准的列表视图来查看数据,并使用单独的对话框进行编辑。市面上有很多不同的网格,但我不太喜欢 WTL 提供的那些。并不是说它们不好,只是它们不符合我的需求。与其修补和修复任何现有的网格以满足我的需求,不如自己做一个可能更快。

关于演示的几点说明。Northwind 演示需要一个位于 localhost 上的 SQL 服务器,并且安装了 northwind 数据库,sa 帐户没有密码。您可以修改 MainFrm.h 中的连接字符串并重新编译。这些解决方案是用 Visual Studio.NET 2003 创建的,因此它们无法在旧版本中打开,但创建一个新的空项目并添加文件应该可以正常工作。

特点

  • 行数仅受内存限制。
  • 我在我的 2 GHz 机器上测试了 100000 行,没有出现问题。这也将取决于列的数量以及您交互的代码。不过,请记住在使用 SetRedraw 添加许多行时。
  • 单元格可以使用标准的编辑控件、组合框(下拉式或下拉列表式)或 DateTimePicker 控件进行编辑。
  • 专为数据库用户而设计,因此所有内容都使用 _variant_t(字符串使用 _bstr_t)。
  • 组合框使用查找值来设置列的值。ID 字段放在网格中,网格显示并编辑信息性文本字段。

还有很多其他功能我可能还会添加到这个网格中,但目前这已经足够满足我的需求了。我欢迎任何关于改进的意见和建议。

如何使用

简单用法

我将向您展示如何创建最基本的网格。这里我从一个标准的 WTL AppWizard 应用程序开始,然后用一个网格替换主窗体视图。创建网格后,我设置样式以允许在网格中右键单击时显示上下文菜单。

创建网格

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 函数使用了更多的参数。第一个新参数是 `alignment`,最后一个参数是该列使用的数据类型。默认值是 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);
};

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

首先,使 CMainFrame 继承自 CGridCtrl::Clistener,然后重写您想要的函数。

class CMainFrame : public CFrameWindowImpl<CMainFrame>, ..., 
                                    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。

致谢

  • 使用了 Bjarke Viksoe 的 atlgdix.h (CMemDC)。
  • 灵感来源于 Noel Frankinet 及其 WTL Grid,并使用了他的一些代码用于绘图和列拖动。
© . All rights reserved.