ATL 网格控件






4.67/5 (15投票s)
2000年2月7日

409059

54615
一个用于显示表格数据的网格控件,基于 Chris Maunder 的网格控件
本文基于 Chris Maunder(版权所有 © 1998-1999)的先前作品,版权所有 © 1999-2000 Mario Zucca(Mario Zucca)。
Chris Maunder 的前言
“我早在 98 年 2 月就发布了我的网格控件。当时我需要一个网格用于我正在进行的一个项目,但买不起当时市面上的商业版本。所以,我以 Joe Willcoxson 的网格为基础,继续编写我自己的,两年后我还在写!我收到最多的请求之一就是将该类封装在 COM 包装器中,或提供一个 ATL 版本。我从未有时间或精力去完成这样的事业,所以当 Mario 发送给我他的第一个原型时,我惊叹于有人花了我无法付出的时间和精力去将网格推广给一个我之前无法触及的受众。在过去的几个月里,我看到 Mario 克服了许多障碍,我认为他的第一个发布版本是值得称赞的努力。
谢谢 Mario!”
引言 |
概述 |
移植 |
数据类型 |
改进 |
技术细节 |
结论 |
引言
在花了许多天研究 COM 架构和 Activex 控件后,我开始使用 Visual Basic 5 和 ATL 版本 2 开发了几个组件。
在进行了一年的 COM 工作后,我认为是时候接受一个有趣的挑战了:编写一个 Activex 控件,其复杂程度足以迫使我学习 COM 和 Windows 最难的细节。在这种想法下,我发现了Chris 的网格,并开始着手工作。
我读他的代码如此之多,以至于我已烂熟于心:画笔、铅笔、字体几天来一直是我挥之不去的噩梦。我一直只为了学习或开玩笑而使用 GDI 对象,直到那天,但我从未深入了解 Windows 图形世界的全部细节。
在咖啡、MSDN@、MFC 和许多尝试的过程中度过我的夜晚,我获得了一些关于设备上下文和其他有用小知识的了解,因此我可以开始将 Chris 的网格移植到一个 Activex 控件。
最终目标是开发一个简单、快速、可靠、并且基本上轻量级的网格。市面上有许多专业的网格控件,它们提供了丰富的功能,我想实现一个,也许不是那么专业,但易于使用且轻量级。这就是我选择放弃 MFC 而选择 ATL 的原因。
结果是一次非常棒的体验,让我学习了 Activex 控件、Windows 图形世界和 ATL 类的内部工作细节。此外,我现在拥有一个 150 KB 的组件,几乎包含了 Chris 网格的所有功能。
在本文中,我将描述我在移植网格过程中遵循的策略,我遇到的主要问题以及我为之设计的解决方案。我将提供一本在我学习过程中帮助过我的书籍列表。
通过这次经历,我写了很多关于 COM、ATL 和 Visual Basic 的入门级文章。
最后,非常感谢 Chris Maunder,他是一位非常轻巧、简单且可靠的网格的作者。
谢谢你,Chris。
概述
激动人心的时刻终于到了:它正在运行。经过许多天的工作,现在终于有了一个功能性的网格版本。
这并不意味着工作完成了!我认为代码中还有很多恼人的 bug 存在(即使它已经由 Numega BoundsChecker ® 检查过)。
我认为这段代码可以作为改进实现的良好起点。
它还没有进行优化:任何好的性能分析器的帮助都将不胜感激。
现在是好消息
- 网格正在运行,并且具备主要功能。
- 我深入研究了 Windows 的底层:画笔、画刷、句柄、窗口和消息;我在一个复杂且真实的 COM 项目中获得了经验。
移植。
第一项工作是为移植选择正确的策略
事实上,Chris 的网格是一个非常复杂的对象(约 5000 行代码),我认为很难一次性将所有代码转换为 ATL,即使它不依赖于 MFC。
我创建了一个从 ATL 开始的 ActiveX 控件,并启用了 MFC 支持,这样它就与现有项目兼容,并且我可以毫不费力地编译代码。第一个令人惊叹的结果是在 Visual Basic 窗体中运行 Chris 的网格。
另一个重要的选择是网格应该支持的容器类型:我喜欢开发一个 OC96 控件;然后使其成为一个无窗口控件,具有快速激活和绘图优化。这就是我使用向导创建 ATL 控件的主要原因,在此版本中仅支持 Windows。
当我看到网格在 VB 窗体中运行时,我非常满意。但我知道目标还很遥远。
我创建了控件,将 Chris 的代码放入我的项目中,创建了另一个类似于原始项目中测试用的网格,然后删除了所有 MFC 调用。
我选择直接调用 Windows API 有两个原因
教学目的:我喜欢深入研究 Windows API 和图形细节的功能(gdi32.dll)。
性能:我的最终目标是创建一个开销尽可能小的组件,因此我需要理解每一行代码。
数据类型
代码中使用的数据类型及其在控件中的使用非常重要:事实上,Windows 使用句柄来表示窗口、画笔、画刷、字体、颜色……,程序员必须使用这些对象并调用 API。COM 客户端和脚本语言更喜欢使用 COM 接口和 dispinterfaces:IFontDisp、IPictureDisp 等。例如,在 Windows API 中使用的 HFONT(字体对象句柄)被映射(作为 OLE)为指向 IFontDisp 接口的指针,它是 IFont 接口的 IDispatch 版本。
在网格中,我还必须为颜色、字符串、字体和位图调用 Windows 图形 API,因此我更喜欢使用标准的 Windows 数据类型,并将数据类型转换留给自动化接口。
字符串在这个项目中也很重要:OLE 世界更喜欢 BSTR,UNICODE 字符串(你可以在技术文档中找到所有细节)。
网络上有很多类允许使用 BSTR 并隐藏细节(通常很无聊):有些软件实现了接近 MFC CString 类的接口,使得移植变得容易。
C++ 标准库也可以通过 `
因此,最佳选择是使用 VC++ 编译器类之一:_bstr_t。这个类是免费的、受支持且易于使用的。这就是我在我的简单开发中需要的。
现在我必须将 MFC 从项目中移除:我移除了 CString、CPen、CRect、CMap,尤其是 CWinApp 和 MFC*.dll。
从 1 MB 到几 KB。
从项目中移除 MFC 后,我必须使网格成为一个 COM 对象,因此我必须定义接口,入口接口和出口接口(带有事件)。
我同意所有 COM 书籍的作者的观点,即接口的定义是任何活动的起点。在这种情况下,Chris 已经定义了接口,我的工作是使其兼容自动化,所以我实现了一个双接口 IGrid,然后我定义了方法和属性而不是函数。
改进
结果是一个 ActiveX 控件(140 KB),它实现了 Chris 网格的许多功能(功能),除了打印、打印预览、与剪贴板的集成以及拖放功能,但现在我可以通过一个简单的网格来执行我需要的所有操作。
其他需要改进的功能
图形方面
- 单元格合并
- 移动单元格和列
- 设置不同类型的单元格
最终用户方面
- OLE 集成(剪贴板、拖放)
- 打印和打印预览
- 使用绑定模式
- 将网格保存和加载为 XML 格式
设计方面
-
使用设计模式使网格通用化
技术细节
CProxy_IGridEvents 是代理类,CGrid 在 IGridEvents 接口中触发方法。向导生成了代理,但遇到了一个问题。在多次崩溃后,我查看了向导生成的 VB 代码:具体来说,在接口的 BeforeEdit 和 ValidateEdit 方法中,有一个 VARIANT_BOOL 参数 (Cancel),被定义为指针:VB 崩溃是因为该参数是通过值传递的。
所以我像这样更改了向导生成的代码
- pvars[0].vt = VT_BOOL | VT_BYREF;
- pvars[0].pboolVal = Cancel;
然后它就能运行了。
网格的接口定义
全局 typedef 定义
typedef enum { GVL_NONE = 0, GVL_HORZ = 1, GVL_VERT = 2, GVL_BOTH = 3 } grGridLines; typedef enum { grPictOrientationLeft = 0, grPictOrientationCenter = 1, grPictOrientationRight = 2 } grPictOrientation; // Horizontal Text Alignment typedef enum { grOrizAlignLeft = 0, //DT_LEFT grOrizAlignRight = 1, //DT_RIGHT grOrizAlignCenter = 2 //DT_CENTER } grOrizontalAlignment; // Vertical Text Alignment typedef enum { grVertAlignBottom = 0, //DT_BOTTOM grVertAlignTop = 1, //DT_TOP grVertAlignCenter = 2 //DT_VCENTER } grVerticalAlignment; // Breaking Text words typedef enum { grBreakingTWNormal = 0, //nor DT_END_ELLIPSIS nor DT_WORDBREAK grBreakingTWWordBreak = 1, //DT_WORDBREAK grBreakingTWEndEllipsis = 2 //DT_END_ELLIPSIS } grBreakingTextWords; // Breaking Text line typedef enum { grTextSingleLine = 0, //DT_SINGLELINE grTextNoLimit = 1 // Toggle DT_SINGLELINE } grTextLine;
IGridCell 定义了单元格的行为
interface IGridCell : IUnknown { [propget, helpstring("property PictureOrientation")]
HRESULT PictureOrientation([out, retval] grPictOrientation *pVal); [propput, helpstring("property PictureOrientation")]
HRESULT PictureOrientation([in] grPictOrientation newVal); [propget, helpstring("property Text")]
HRESULT Text([out, retval] BSTR *pVal); [propput, helpstring("property Text")]
HRESULT Text([in] BSTR newVal); [propget, helpstring("property Font")]
HRESULT Font([out, retval] IFontDisp* *pVal); [propput, helpstring("property Font")]
HRESULT Font([in] IFontDisp* newVal); [propget, helpstring("Horizontal Alignment")]
HRESULT HorizontalAlignment([out, retval] grOrizontalAlignment *pVal); [propput, helpstring("Horizontal Alignment")]
HRESULT HorizontalAlignment([in] grOrizontalAlignment newVal); [propget, helpstring("Vertical Alignment")]
HRESULT VerticalAlignment([out, retval] grVerticalAlignment *pVal); [propput, helpstring("Vertical Alignment")]
HRESULT VerticalAlignment([in] grVerticalAlignment newVal); [propget, helpstring("Breaking Text Words")]
HRESULT BreakingTextWords([out, retval] grBreakingTextWords *pVal); [propput, helpstring("Breaking Text Words")]
HRESULT BreakingTextWords([in] grBreakingTextWords newVal); [propget, helpstring("property Picture")]
HRESULT Picture([out, retval] IPictureDisp* *pVal); [propput, helpstring("property Picture")]
HRESULT Picture([in] IPictureDisp* newVal); };
IGrid 接口
interface IGrid : IDispatch { // Stock properties [propputref, bindable,requestedit, id(DISPID_FONT)]
HRESULT Font([in]IFontDisp* pFont); [propput, bindable,requestedit, id(DISPID_FONT)]
HRESULT Font([in]IFontDisp* pFont); [propget, bindable,requestedit, id(DISPID_FONT)]
HRESULT Font([out, retval]IFontDisp** ppFont); [propget, id(29), helpstring("property BackColor")]
HRESULT BackColor([out, retval] OLE_COLOR *pVal); [propput, id(29), helpstring("property BackColor")]
HRESULT BackColor([in] OLE_COLOR newVal); [propget, id(1), helpstring("Imposta/ legge l'image per l'item specificato")]
HRESULT Image([in] int Row,[in] int Col, [out, retval] short *pVal); [propput, id(1), helpstring("Imposta/ legge l'image per l'item specificato")]
HRESULT Image([in] int Row,[in] int Col, [in] short newVal); [propget, id(2), helpstring("Imposta/ legge il testo per l'item specificato")]
HRESULT Text([in] int Row,[in] int Col, [out, retval] BSTR *pVal); [propput, id(2), helpstring("Imposta/ legge il testo per l'item specificato")]
HRESULT Text([in] int Row,[in] int Col, [in] BSTR newVal); [id(3), helpstring("method InsertRow")]
HRESULT InsertRow([in] int Row,[in] BSTR caption); [propget, id(4), helpstring("Imposta/ritorna il numero di righe nella griglia")]
HRESULT RowCount([out, retval] int *pVal); [propput, id(4), helpstring("Imposta/ritorna il numero di righe nella griglia")]
HRESULT RowCount([in] int newVal); [propget, id(5), helpstring("Imposta/Ritorna il numero di colonne nella grid")]
HRESULT ColumnCount([out, retval] int *pVal); [propput, id(5), helpstring("Imposta/Ritorna il numero di colonne nella grid")]
HRESULT ColumnCount([in] int newVal); [propget, id(6), helpstring("Imposta/ritorna l'altezza della riga specificata")]
HRESULT RowHeight([in] int nRow, [out, retval] int *pVal); [propput, id(6), helpstring("Imposta/ritorna l'altezza della riga specificata")]
HRESULT RowHeight([in] int nRow, [in] int newVal); [propget, id(7), helpstring("Imposta/ritorna la larghezza della colonna")]
HRESULT ColumnWidth([in] int Col, [out, retval] int *pVal); [propput, id(7), helpstring("Imposta/ritorna la larghezza della colonna")]
HRESULT ColumnWidth([in] int Col, [in] int newVal); [propget, id(9), helpstring("property CellFont")]
HRESULT CellFont([in] int Row,[in] int Col, [out, retval] IFontDisp* *pVal); [propput, id(9), helpstring("property CellFont")]
HRESULT CellFont([in] int Row,[in] int Col, [in] IFontDisp* newVal); [id(11), helpstring("method AutoSize")] HRESULT AutoSize(); [propget, id(12), helpstring("Allow Column resizing")]
HRESULT ColumnResizing([out, retval] VARIANT_BOOL *pVal); [propput, id(12), helpstring("Allow Column resizing")]
HRESULT ColumnResizing([in] VARIANT_BOOL newVal); [propget, id(13), helpstring("property RowResizing")]
HRESULT RowResizing([out, retval] VARIANT_BOOL *pVal); [propput, id(13), helpstring("property RowResizing")]
HRESULT RowResizing([in] VARIANT_BOOL newVal); [propget, id(14), helpstring("property GridLines")]
HRESULT GridLines([out, retval] grGridLines *pVal); [propput, id(14), helpstring("property GridLines")]
HRESULT GridLines([in] grGridLines newVal); [propget, id(15), helpstring("property Editable")]
HRESULT Editable([out, retval] VARIANT_BOOL *pVal); [propput, id(15), helpstring("property Editable")]
HRESULT Editable([in] VARIANT_BOOL newVal); [propget, id(16), helpstring("property FixedRows")]
HRESULT FixedRows([out, retval] int *pVal); [propput, id(16), helpstring("property FixedRows")]
HRESULT FixedRows([in] int newVal); [propget, id(17), helpstring("property FixedCols")]
HRESULT FixedCols([out, retval] int *pVal); [propput, id(17), helpstring("property FixedCols")]
HRESULT FixedCols([in] int newVal); [id(18), helpstring("method AutosizeColumn")]
HRESULT AutosizeColumn([in] int col); [id(19), helpstring("method AutosizeRow")]
HRESULT AutosizeRow([in] int row); [propget, id(20), helpstring("property CellEnabled")]
HRESULT CellEnabled([in] int row,[in] int col, [out, retval]
VARIANT_BOOL *pVal); [propput, id(20), helpstring("property CellEnabled")]
HRESULT CellEnabled([in] int row,[in] int col, [in] VARIANT_BOOL newVal); [propget, id(21), helpstring("property AllowSelection")]
HRESULT AllowSelection([out, retval] VARIANT_BOOL *pVal); [propput, id(21), helpstring("property AllowSelection")]
HRESULT AllowSelection([in] VARIANT_BOOL newVal); [propget, id(22), helpstring("property ListMode")]
HRESULT ListMode([out, retval] VARIANT_BOOL *pVal); [propput, id(22), helpstring("property ListMode")]
HRESULT ListMode([in] VARIANT_BOOL newVal); [propget, id(23), helpstring("property CurrentRow")]
HRESULT CurrentRow([out, retval] int *pVal); [propput, id(23), helpstring("property CurrentRow")]
HRESULT CurrentRow([in] int newVal); [propget, id(24), helpstring("property CurrentCol")]
HRESULT CurrentCol([out, retval] int *pVal); [propput, id(24), helpstring("property CurrentCol")]
HRESULT CurrentCol([in] int newVal); [id(25), helpstring("method DeleteRow")] HRESULT DeleteRow([in] int row); [propget, id(26), helpstring("property HeaderSort")]
HRESULT HeaderSort([out, retval] VARIANT_BOOL *pVal); [propput, id(26), helpstring("property HeaderSort")]
HRESULT HeaderSort([in] VARIANT_BOOL newVal); [propget, id(27), helpstring("property SingleRowSelection")]
HRESULT SingleRowSelection([out, retval] VARIANT_BOOL *pVal); [propput, id(27), helpstring("property SingleRowSelection")]
HRESULT SingleRowSelection([in] VARIANT_BOOL newVal); [id(28), helpstring("method DeleteColumn")] HRESULT DeleteColumn([in] int col); [propget, id(30), helpstring("property FixedBackColor")]
HRESULT FixedBackColor([out, retval] OLE_COLOR *pVal); [propput, id(30), helpstring("property FixedBackColor")]
HRESULT FixedBackColor([in] OLE_COLOR newVal); [propget, id(31), helpstring("property TextColor")]
HRESULT TextColor([out, retval] OLE_COLOR *pVal); [propput, id(31), helpstring("property TextColor")]
HRESULT TextColor([in] OLE_COLOR newVal); [propget, id(32), helpstring("property TextBackColor")]
HRESULT TextBackColor([out, retval] OLE_COLOR *pVal); [propput, id(32), helpstring("property TextBackColor")]
HRESULT TextBackColor([in] OLE_COLOR newVal); [propget, id(33), helpstring("property Color")]
HRESULT Color([out, retval] OLE_COLOR *pVal); [propput, id(33), helpstring("property Color")]
HRESULT Color([in] OLE_COLOR newVal); [propget, id(34), helpstring("property FixedTextColor")]
HRESULT FixedTextColor([out, retval] OLE_COLOR *pVal); [propput, id(34), helpstring("property FixedTextColor")]
HRESULT FixedTextColor([in] OLE_COLOR newVal); [propget, id(35), helpstring("property CellFgColor")]
HRESULT CellFgColor([in] int Row, [in] int Col, [out, retval] OLE_COLOR *pVal); [propput, id(35), helpstring("property CellFgColor")]
HRESULT CellFgColor([in] int Row, [in] int Col, [in] OLE_COLOR newVal); [propget, id(36), helpstring("property CellBgColor")]
HRESULT CellBgColor([in] int Row, [in] int Col, [out, retval] OLE_COLOR *pVal); [propput, id(36), helpstring("property CellBgColor")]
HRESULT CellBgColor([in] int Row, [in] int Col, [in] OLE_COLOR newVal); [id(37), helpstring("method SimpleConf")] HRESULT SimpleConf(); [propget, id(38), helpstring("property Cell")]
HRESULT Cell([in] int Row,[in] int Col, [out, retval] IGridCell* *pVal); [propput, id(38), helpstring("property Cell")]
HRESULT Cell([in] int Row,[in] int Col, [in] IGridCell* newVal); [propget, id(39), helpstring("property ToolTip")]
HRESULT ToolTip([out, retval] VARIANT_BOOL *pVal); [propput, id(39), helpstring("property ToolTip")]
HRESULT ToolTip([in] VARIANT_BOOL newVal); [id(40), helpstring("method Refresh")] HRESULT Refresh(); [id(41), helpstring("method SelectAllCells")] HRESULT SelectAllCells(); [propget, id(42),
helpstring("KeepTab specifies if TAB send or not the Focus to the next control")]
HRESULT KeepTab([out, retval] VARIANT_BOOL *pVal); [propput, id(42),
helpstring("KeepTab specifies if TAB send or not the Focus to the next control")]
HRESULT KeepTab([in] VARIANT_BOOL newVal); };
以及网格的事件(出口接口)
dispinterface _IGridEvents { properties: methods: [id(1), helpstring("Fired before start Cell edit")]
HRESULT BeforeEdit(int Row,int Col,VARIANT_BOOL* Cancel); [id(2), helpstring("Fired before exit Cell edit")]
HRESULT ValidateEdit(int Row,int Col,VARIANT_BOOL* Cancel); [id(3), helpstring("Fired after Cell edit end")]
HRESULT AfterEdit(int Row,int Col); [id(4), helpstring("Fired before enter in a Cell")]
HRESULT EnterCell(int Row,int Col); [id(5), helpstring("Fired after exit from a Cell")]
HRESULT LeaveCell(int Row,int Col); [id(6), helpstring("Fired after click on a fixed column")]
HRESULT ColumnClick(int Col); [id(7), helpstring("Fired after click on a fixed row")]
HRESULT RowClick(int Row); [id(8), helpstring("Fired before a selection changed")]
HRESULT SelChanging(); [id(9), helpstring("Fired after a selection changed")]
HRESULT SelChanged(); };
结论
我在软件开发领域工作了十年,其中六年是在 Windows 世界,三年是在 COM 和 C++ 领域,今天我可以肯定地说,这次经历非常棒,尽管很辛苦!
在开发网格的过程中,我研究了一些工作机会中很少有机会深入研究的论题,并且我很高兴能通过电子邮件与 Chris 交流,他非常耐心地测试 beta 版本,指出 bug 并鼓励我完成这项工作。
如果有人对此感兴趣,或者想帮助我开发功能(功能)或有任何建议,请给我发电子邮件:我的地址是 Mario@genoavalley.org