Board logo

Subject: 组件前缀专家改进手记 [Print This Page]

Author: zjy    Time: 2006-9-8 18:07     Subject: 组件前缀专家改进手记

这两天为组件前缀专家增加根据 Action 名称和 DataField 字段名来命名新组件的功能,使用了N种方案后总算大功告成,于此记载下来以作纪念。

需求来源:
1、以往做界面时经常是先用 ActionList 定义一堆 Action,再关联到 MenuItem 和 ToolButton 上。这些 MenuItem 和 Button 如果不取个名字总感觉看起来不舒服,如果一个一个去改名又太痛苦。
2、前段时间有个用户建议我们使用字段名来命名数据感知控件。当时简单考虑了一下,觉得有点复杂就搁下了,这次顺便一起搞定。

分析问题:
首先来解决 Action 的需求。初步的设计是在修改控件关联的 Action 时,如果该控件未命名(即前缀不正确或前缀加数字这种形式),则自动将用控件前缀加 Action 去前缀名称作为控件名。

第一个问题是怎样控件的 Action 变更时得到通知。OTA 是没有现成的接口的,使用属性编辑器来处理则可能会遇到跟第三方工具冲突的问题,如果要用定时查询的办法就太笨了,看来这次又要动用俺的终极武器“方法挂接”。

支持 Action 的组件有好几类,有从 TControl 派生出来的控件,还有从 TComponent 派生出的 TMenuItem 等,另外还可能有第三方控件自己声明的 Action 对象。通过分析 VCL 源码,我把目光投向了 TBasicActionLink。这个类有个 protected 的方法 SetAction,通常当控件的 Action 变更时最终会调用到这个方法,于是我拿出 TCnMethodHook 把这个方法给 Hook 掉,以获得 Action 变更通知。

接下来发现原来的代码中,对需要更名的组件处理太复杂了,顺手优化了一下,改成新的流程:

获得更名通知=>保存要更名的组件到列表=>OnIdle时检查更名列表=>执行更名操作。

其中“获得更新通知”的方式有两种,一种是 OTA 提供的组件更名通知事件,一种是前面用 Hook 得到的 Action 变更通知。在通知过程中,将要改名的组件加到列表中即可。

之所以使用 OnIdle 来批量处理,一是因为在 OnIdle 中处理不会因为未知的原因打断 IDE 当前执行的操作产生异常,二是可以解决用户一次性往窗体上粘贴一堆控件时一个一个文件提示太麻烦的问题。这个 OnIdle 的处理是以前就采用的,这次简单优化了一下。

最后是根据命名策略对列表中的组件进行改名。针对 Action 的情况,增加了一些处理,总算把这个需求给解决了。

完成 Action 的需求,接下来要实现的功能是根据 DataField 的内容自动命名数据感知控件。

有了前面的基础,很自然地就想到了挂接 DataField 变更相关的方法。查看 VCL 源码发现,数据感知控件派生自不同的基类,内部通过 TFieldDataLink 来关联字段。郁闷的是 TFieldDataLink.SetFieldName 是一个 private 方法,没办法直接获得地址。

访问私有方法的方法倒是有几种,但都有些复杂。突然想到 SetFieldName 方法正好是 FieldName 这个 public 属性的写方法,如果我从 TFieldDataLink 派生出一个子类,再把 FieldName 重声明为 published,不就可以用 RTTI 来获得其写方法,也就是 SetFieldName 的地址进行挂接吗?

兴高采烈地按照这个方法写了段代码,结果却发现 SetFieldName 并没有成功地挂接。仔细分析才发现,原来当初 CnWizards 为了支持在没有安装数据库包的个人版 Delphi 中运行,没有带 vcldbXX.bpl 编译。我通过派生子类挂接到的方法并不是 IDE 中 vcldbXX.bpl 中的方法,而是编译在专家 DLL 中的代码。

难道为了这个功能,真的要牺牲对个人版的支持吗?不行,即便是针对不同 IDE 出不同版本的方法,成本也太高了。尽管通过增加数据库包编译,已经成功地实现了需求功能,我仍然不得不放弃前面的努力。

接下来,我考虑了一个通过 bpl 中导出函数名来取得地址的方法,这个方法在代码助手等专家中用到。分析发现在 vcldbXX.bpl 中确实有 SetFieldName 这个导出函数(实际上是一长串字符的函数名)。但是用这个方法代价也是很高的,必须为每个编译器版本定义 bpl 的名字和函数的名字,维护起来很麻烦。而且,如果不 uses DBCtrls 单元,要从 FieldDataLink 对象中取得数据组件也很麻烦。

漫长的思考,我又想到一个方法:挂接属性编辑器。查看代码发现 DataField 的属性编辑器 TDataFieldProperty 最终调用了 TStringProperty.SetValue 来为 DataField 属性设置值。剩下的工作就比较简单了,Hook 掉 TStringProperty.SetValue,取得正在修改的组件对象,象 Action 那样把组件扔到列表,再在改名时处理一下,一切OK!
Author: helpme5    Time: 2006-9-8 19:40

学习一下。
Author: wenfei    Time: 2006-9-9 19:48

强,可以说是费尽心思才得出这样完美的解决方法。




Welcome to CnPack Forum (http://bbs.cnpack.org/) Powered by Discuz! 5.0.0