说三道四技术文摘-感悟人生的经典句子
说三道四 > 文档快照

改变VCL的行为--一个使用可视化元件的实例-Delphi资料

HTML文档下载 WORD文档下载 PDF文档下载
改变VCL的行为--一个使用可视化元件的实例-Delphi资料

要使一个可视化控件的行为与其默认行为不同,我们通常要从这个原始类继承,创建一个新的控件。本文将介绍如何在不创建新类的情况下动态改变原生Delphi可视化控件的行为。

这可能实现吗?秘密在于在控件之前抢先截获Windows消息。这可以通过使用一个叫做WindowProc的TControl属性来实现,这个属性实质上指向控件的Windows消息事件处理器(event handler)。

为了展示这一技术,我们将创建一个LinkedLabel控件,可以将它连接到任何TControl控件并且动态改变它的行为。TLinkedLabel由TLabel继承而来,附加4个公开的属性:

? Associate —— 将被改变行为的相连控件

? CapsLock —— 当这个Boolean属性被设置为True时,特定类型的控件将把小写键盘输入作为大写来处理。这个属性并不对所有控件有效,因为并不是所有的控件都以相同的方式相应WM_CHAR消息。经测试Edit,MaskEdit,Memo,和RichEdit控件都对CapsLock属性有响应,但是ComboBox则不响应。很明显,CapsLock属性对于很多其他控件(如Button、CheckBox等)只有很小的影响,或者没有影响。

? Gap —— LinkedLabel与相连控件的距离

? OnTop —— 这个Boolean属性决定LinkedLabel出现在相连控件的左侧还是顶端。

另外,TlinkedLabel将保持自身和相连控件的Enabled和Visible属性相一致。它也会保持自身和相连控件的距离和角度,也就是说,当你移动LinkedLabel时,其关联也会随之移动,反之亦然。

我们来看一下TLinkedLabel类的声明,如图1所示。

unit LinkedLabel;

interface

uses

Messages

Classes

Controls

StdCtrls;

type

TLinkedLabel = class(TLabel)

private

// 相连控件.

FAssociate: TControl;

// 将 FAssociate 置为全大写模式

FCapsLock: Boolean;

// 标签与关联控件之间的距离

FGap: Integer;

// 标签在关联控件顶端时为true

FOnTop: Boolean;

// 保存 FAssociate.WindowProc的原始值

FOldWinProc: TWndMethod;

// 用于防止无限更新循环

FUpdating: Boolean;

protected

procedure Adjust(MoveLabel: Boolean);

procedure SetGap(Value: Integer);

procedure SetOnTop(Value: Boolean);

procedure SetAssociate(Value: TControl);

procedure NewWinProc(var Message: TMessage);

procedure Notification(AComponent: TComponent;

Operation: TOperation); override;

procedure WndProc(var Message: TMessage); override;

public

constructor Create(AOwner :TComponent); override;

destructor Destroy; override;

published

property Associate: TControl

read FAssociate write SetAssociate;

property CapsLock: Boolean

read FCapsLock write FCapsLock;

property Gap: Integer read FGap write SetGap default 8;

property OnTop: Boolean read FOnTop write SetOnTop;

end;

现在让我们来仔细看看这个控件中的不同方法,先由构造器(constructor)开始。首先说明一下,当创建一个新对象时,与它相关联的所有内存都被清空。这个动作将会自动把Fassociate和FoldWinProc设置为nil,将FcapsLock、FonTop、Fupdating设置为False。所有这些都不需要在构造器中明确的初始化它们。因此,唯一需要我们在构造器中设置的就是Gap的默认值。

implementation

constructor TLinkedLabel.Create(AOwner: TComponent);

begin

inherited;

FGap := 8;

end;

现在我们来看一下Adjust方法,它负责安排LinkedLabel或者关联控件的放置(取决于MoveLabel参数的取值)。正如你将在代码中看到的,LinkedLabel与相关控件的实际位置取决于Gap和OnTop属性(见图2)。虽然我们在OnTop中只提供了两种可能的选择,不过可以很容易的对其编程以提供更多的可能性。不过,把TlinkedLabel武装到牙齿(原文是“add a lot of "bells and whistles"”,译者注)并不是本文的重点,这项任务就委托给读者们来完成吧。

procedure TLinkedLabel.Adjust(MoveLabel: Boolean);

var

dx

dy: Integer;

begin

if (Assigned(FAssociate)) then begin

if (FOnTop) then

begin

dx := 0;

dy := Height + FGap;

end

else

begin

dx := Width + FGap;

dy := (Height - FAssociate.Height) div 2;

end;

if (MoveLabel) then

begin

Left := FAssociate.Left - dx;

Top := FAssociate.Top - dy;

end

else

begin

FAssociate.Left := Left + dx;

FAssociate.Top := Top + dy;

end;

end;

end;

现在,我们来完成Gap和OnTop属性的set方法(见图3),以便当Gap或者Onop属性被修改时我们可以改变LinkedLabel的位置。

procedure TLinkedLabel.SetGap(Value: Integer);

begin

if (FGap <> Value) then

begin

FGap := Value;

Adjust(True);

end;

end;

procedure TLinkedLabel.SetOnTop(Value: Boolean);

begin

if (FOnTop <> Value) then

begin

FOnTop := Value;

Adjust(True);

end;

end;

现在是SetAssociate方法

procedure TLinkedLabel.SetAssociate(Value: TControl);

begin

if (Value <> FAssociate) then begin

if (Assigned(FAssociate)) then

FAssociate.WindowProc := FOldWinProc;

FAssociate := Value;

if (Assigned(Value)) then

begin

Adjust(True);

Enabled := FAssociate.Enabled;

Visible := FAssociate.Visible;

FOldWinProc := FAssociate.WindowProc;

FAssociate.WindowProc := NewWinProc;

end;

end;

end;

为了便于理解,我们需要详细的讨论一下WindowProc属性。WindowProc被定义为TwndMethod类型。TwndMethod可以在Controls单元中找到,定义如下:

TWndMethod = procedure(var Message: TMessage) of object;

注意,FoldWinProc同样被定义为TwndMethod,并且NewWinProc方法拥有与TwndMethod相同的参数结构。这就允许我们将FoldWinProc指向WindowProc的当前值,并把WindowProc重定向到NewWinProc方法。如果WindowProc只是另一个事件属性的话,我们为什么需要使用FoldWinProc呢?因为WindowProc与其它事件属性的不同之处在于WindowProc指向一个已经存在的事件处理器。如果我们只是简单的将WindowProc指向我们的方法,这个控件将不能再对任何Windows消息产生响应。为了解决这个问题,我们在把WindowProc指向NewWinProc之前把FoldWinProc设置为WindowProc的当前值。

在NewWinProc中,我们通过FoldWinProc调用原先的消息处理器(message handler),并且处理特定的Windows消息。因为我们修改了关联控件的WindowProc值,因此要在把关联改变到一个新的控件之前恢复它从前的取值。

避免把关联控件的WindowProc属性指向一个不再存在的例程也同样重要。如同我们所见的,在析构器中调用SetAssociate(nil)将会把WindowProc恢复为初始值。

destructor TLinkedLabel.Destroy;

begin

SetAssociate(nil);

inherited;

end

另外,我们也不希望关联到一个不再存在控件。通过覆盖Notification方法,我们可以知道关联组件何时被销毁,从而重置关联的指针:

procedure TLinkedLabel.Notification(AComponent: TComponent;

Operation: TOperation);

begin

if ((Operation = opRemove) and

(AComponent = FAssociate)) then SetAssociate(nil);

end;

现在我们来看NewProc方法。这里,我们只是寻找发送给关联控件的特定Windows消息。认识到这一点是很重要的:虽然方法通过关联控件调用,但它实际上是LinkedLabel的一部分,例如,Self=LinkedLabel,而不是关联控件。这对为一个按钮创建onclick事件处理器来说也是一样的,onclick事件处理器是作为按钮父窗体的一部分,而不是扩充Tbutton类的新方法。

procedure TLinkedLabel.NewWinProc(var Message: TMessage);

var

Ch: Char;

begin

if (Assigned(FAssociate) and (not FUpdating)) then begin

FUpdating := True;

try

case(Message.Msg) of

WM_CHAR:

if (FCapsLock) then begin

Ch := Char(TWMKey(Message).CharCode);

if (Ch >= ’a’) and (Ch <= ’z’) then

TWMKey(Message).CharCode := ord(UpCase(Ch));

end;

CM_ENABLEDCHANGED:

Enabled := FAssociate.Enabled;

CM_VISIBLECHANGED:

Visible := FAssociate.Visible;

WM_SIZE

WM_MOVE

WM_WINDOWPOSCHANGED:

Adjust(True);

end;

finally

FUpdating := False;

end;

end;

FOldWinProc(Message);

end;

如果你检查一下这个例程,就会发现我们并没有花多少力气去处理Windows消息。我们只注意几个特定的消息,然后就让关联通过调用FOldWinProc正常的处理它们。在处理WM_CHAR消息的时候,我们对消息的一部分做了改变,让控件认为我们按下的是大写字母键。

最后,我们关心一下两个不同的消息,以确定关联控件是否被移动了。这样做的原因在于从TwinControl继承的控件会在它们被移动时接到WM_MOVE消息,而此时其它的可视控件(如一个标签)则会收到WM_WINDOWPOSCHANGED消息。程序也检查了WM_SIZE消息,原因是如果OnTop属性为False,则LinkedLabel的位置会随控件的高度而变化。

我们这个控件的最后一个方法是:当LinkedLabel被改变时,要在关联的什么地方作修改?当然我们不使用覆盖Tlabel的现存方法来实现它,而是要用修改关联行为的相同技术来做。注意我们不是重新定向WindowsProc属性,而是覆盖了WndProc方法。为什么把它们叫做相同的技术呢?如果你看一下TControl的构造器,你可以发现WindowProc会被初始化以指向WndProc方法。所以从本质上讲,我们覆盖的是同一种方法,不过做得更“干净”,也不用去保存WindowProc的初始值。

procedure TLinkedLabel.WndProc(var Message: TMessage);

begin

if (Assigned(FAssociate) and (not FUpdating)) then begin

FUpdating := True;

try

case(Message.Msg) of

CM_ENABLEDCHANGED: FAssociate.Enabled := Enabled;

CM_VISIBLECHANGED: FAssociate.Visible := Visible;

WM_WINDOWPOSCHANGED: Adjust(False);

end;

finally

FUpdating := False;

end;

end;

inherited;

end;

对于刚刚完成的控件还有最后一点需要注意。你也许发现NewWinProc和WndProc中都使用了Fupdating。这个变量被用来通知LinkedLabel和它的关联控件其它控件正在发生改变。如果你忽略了这一步,很容易造成一个无限的更新循环,或者其它无法预料的结果。下面是一个事件流程,显示为什么需要Fupdating变量。

? 用户把 LinkedLabel 拖动到一个新位置。

? WndProc 接收到一个 WM_WINDOWPOSCHANGED 消息,并且触发 Adjust(False) 来移动关联控件。

? 作为对关联控件调整的一部分,Adjust 把FAssociate.Left设置为新值。

? FAssociate 触发 WM_MOVE 消息,指出它已经改变了位置。

? NewWinProc 监测到 WM_MOVE 消息并调用 Adjust(True) 以修改 LinkedLabel 的位置配合关联控件的移动。

如你所见,在关联控件试图移动LinkedLabel之前我们没有什么机会改变关联控件的Top属性来配合LinkedLabel的新位置。通过使用Fupdating变量,关联控件不会注意到WM_MOVE消息,也不会试图调用Adjust来重新布置LinkedLabel。

一对问题

在这篇文章中我没有提及TlinkedLabel的一对问题。下面是对它们的大致说明:

? 如果你把两个或者两个以上LinkedLabel关联到同一个控件然后释放它们之中的一个或者几个,就可能导致各种各样的问题。你可能会打断到其它LinkedLabel的关联,甚至可能导致被关联控件的WindowProc指向一个并不存在的历程。

? 如果你把 LinkedLabel 关联到另一个窗体上的控件,那么Notification 方法在那个控件被销毁时不会被调用。当控件被关联时调用 FreeNotification 可以解决这个问题,但这并没有真正指出问题所在。真正的问题在于我们允许它被关联在其它窗体的控件上。其实我们真正想实现的是把LinkedLabel与拥有相同Parent的控件相关联。虽然这么做并不难,不过要只在对象查看器的Associate属性下拉列表中显示符合条件的控件也需要一些小技巧。

结论

其实结论也没多少东西。替换现存控件的WindowProc确实有它的局限性,不过这毕竟是一种非常有用的技术。我想不出什么其它合适的方法来创建一个像TlinkedLabel这样的控件,让关联控件在被移动时也一并移动LinkedLabel。我可不想去尝试并且列出这种技术其它可能的用法,因为这种可能性是无限的,它只会被一个程序员的灵活性所局限。

高薪技术排行:大数据居首,苹果相关次之,Java、C等相去甚远 谷歌开发网络跟踪新技术:AdID将取代第三方Cookie 穿在脚上的苹果?苹果聘用Nike设计主管Ben Shaffer 七款Debug工具推荐:iOS开发必备的调试利器 六年亲历,见证中国大数据技术与应用时代的到来 Mailbox:日支撑过亿信息数据库的性能调优及集群迁移 PayPal前CTO 在美国改变世界的乌克兰犹太人 放弃Bootstrap&amp;amp;Foundation,迎接Semantic UI? 一键分享:iOS版Google+发布SDK InMobi杨娟:中国移动游戏如何挑战全球? Adobe再遭APT攻击:数十G源代码及290万用户信息失窃 程序员,如何在工作中崭露头角? 国内外三个不同领域巨头分享的Redis实战经验及使用场景 这是一场潜在的战争:谷歌与Facebook、苹果在搜索上的博弈 设计新闻类网站需要注意的关键点:移动友好、宽带…… 直接开始用:5个免费的在线思维组织工具 招聘开发者常见的九大误区 开源影响销售,维护赞助商? 盘点黑客攻击途径:最常用的7个策略及简单的防护方法 专访AngularJS框架创始人Misko Hevery:让Web开发更便捷 果粉的又一个节日:新iPad或于10月22日发布 新车间李大维:到集装箱里来看创客嘉年华 亚马逊推“登录与支付”功能 直接冲击PayPal Google编程之夏新里程碑:5000万行源代码 8500名学生开发者 非常实用的15款开源PHP类库 Runnable:一站式代码片段服务平台,打造编程界的YouTube 谷歌向所有开发者开放Google App Engine PHP Runtime “沟通”电子表格和矢量图形的“桥梁”:在线可视化工具Raw Facebook北极圈数据中心,Open Compute Project的力作! 最后三天!4折抢购2013移动开发者大会门票 电子书追踪统计,App Annie for eBooks上线 vftable是什么东西? 难道bcb的程序员都要当和尚吗 现在有很多注册表清理工具,请问注册表清理的原理是什么? An Urgent Problem about Word 2000----Is that an mission imposibile ?(s---o---s:high score is warranted) 请看看这个函数... 如何确定线程运行消耗的资源量? 文件系统的树形列表怎么做啊,不要ocx的 为何我使用javascript中location'****.jsp'跳到某一页面,jsp页面不重新编译执行 VC6.0没有声音怎么办 循环20000000次所需要时间(急) 如何使按钮象工具栏上的按钮一样当鼠标移到按钮上时出现文字说明? 关于使用GDI+转换文件格式的问题. 各位能不能介绍点比较经典的算法的书籍,大家分享一下 如何恢复或新建临时表空间????在线等候!!!马上给分! Gis 请教一个数据库的问题,欢迎发言提出解决方案 ??怎样用asp程序把一段html代码转换成纯文本格式? 下拉数据窗口不能下拉是什么原因啊? 有关用VB做数据库的简单问题、ComboBox控件问题! 大家帮我看看这段程序为什么值为0 oracle8.1.7的数据库备份文件怎样恢复到oracle8i上 ? 怎样屏蔽flash的右键菜单,弹出自己的菜单? 我想问一下有没有不写入数据库用ASP代码可以查看当天的浏览访问量 灌水区的水越来越没质量了,你觉得呢? 按钮消息响应的问题,详细请看进去再看 异常详细信息: System.Data.OleDb.OleDbException: 无法启动应用程序。工作组信息文件丢失,或是已被其它用户以独占方式打开。 求助,网页制作0=={=========>(在线等,送分) 谁能告诉我《C++Builder 5编程实例与技巧》这本书在哪儿可以下载到完整的电子版!高分感谢! 请教如何将一张图片旋转? 关于流媒体和vc++的结合,高人请进!! 关于DataOutputStream和DataInputStream求教 会DirectX的一起来讨论! 4444 菜鸟求助~~~ help me 刚才发现www2.google.com www3.google.com也不能访问了,见鬼:( 我想停止window2000[域控制器]的389端口,但我不知服务是不是LDAP,因为我想占用端口389 各位!帮个忙! 帮忙啊!因为很着急,只有在技术论坛发了,大家原谅 电子地图 上海的程序员都在哪里"混"? 如何最小化所有窗口? 你们有谁读大学一学年要交近1000元书籍费? 帮忙啊!因为很着急,只有在技术论坛发了,大家原谅 asp中如何使用ADO实现对sql数据库操作 求助,网页制作0=={=========>(在线等,送分) 我在用mcisendcommond()函数时,总提示设备类型出错,是什摸原因? 紧急求教高分相送100分。 怎么判断女孩喜欢你? CSDN的首页能登录吗? C 与 C++ 到底有什么不同?? 蘑菇罐头做出来发酸,怎么去除那种酸味?买来的蘑菇罐头都含有柠檬酸,已经在水里浸泡过了,可是做出来菜来还是发酸,不怎么好吃,请问怎么去除这种酸味?谢谢! 大和油墨的镜面银怎么样? 地理小知识:台湾省的素有美称及其含义想到一条算一条,积少成多嘛! 咸肉有点发酸了,怎么样才能去掉酸味呢? 如图,三角形ABC中,角ABC=30°,以BC,AC为边作等边△BCD和等边△ACE,联结BE.求证;AB平方+BC平方=BE平方 台湾岛素有”森林宝库”,”东方甜岛”等美称,含义是什么呢?他们的含义是什么呢?有什么特殊意义?台湾岛还有什么美称呢?(地理用滴:P) 紧急!谁知道各农业地域主导区位因素?注意!是主导区位因素!要求:各种典型的农业地域类型,例如“季风水田农业”“商品谷物农业”“大牧场放牧业”“混合农业”“种植园农业”“水稻 以△ABC的AB,AC为边向三角形外作等边△ABD,△ACE,连结CD,BE相交于点O.①求证:OA平分∠DOE.②点F在DO上,若DF=BO,连AF,求证:△AOF是等边三角形.③求证:OA+OC=OE (1)分析长江中下游地区发展农业的有利条件和不利条件(2)分析新疆发展农业的有利条件及限制性因素 红茶发酵温度怎么控制主要是茶叶加工过程中,发酵室温度湿度的控制范围问题,不是十分的清楚.想请教各专家.什么样的茶叶需要什么样的范围等. 请写出描写四季景物的诗句各一句就是春夏秋冬de诗都一句. 已知函数f(x)+sin(2x+∏/3)求函数最小生周期T,以及用5点法作图 证明欧姆表的中值电阻只决定于E和满偏电流 多电源电路个部分电流如何计算其中 xmm3 中的电流时如何计算得来的 地震来了要怎么办 台湾的主要城市是哪里?台湾最著名的树种是什么? 电路中有两个电源串联,那这个电压应该怎么算呢可以移动位置吗 为什么会地震 免疫系统的基本功能是什么 急需描写四季景物的古诗{和}诗歌,要完整的,不是一句两句.要多 白酒有酸味是怎么回事?我自己在家里酿制白酒,可是这几次的都带有一点酸味.这是什么原因产生的?每次都采用相同的方法,可是只有这一次的酸, 什么是免疫系统?作用是什么? 青岛市的平均降雨强度是多少? 如图,在等边三角形ABC中,点D是AB边上的一点,将三角形BCD绕着点C顺时针旋转后与三角形ACE重合,说明AE∥BC 免疫系统的功能主要有? 高一数学空间几何体一题、在线等!已知正六棱锥的底面边长为3cm,斜高为5cm,求它的体积.(V椎体=1/3S底*h)主要是六棱锥的底面积不会求,请写过程和答案,谢谢帮忙! 为什么打来白酒或醋,可以闻到酒香或酸味? 免疫系统r的主要作用是什么,免疫器官有哪些? 高一数学空间几何体结构题 例4 淋巴因子与神经递质是否处于内环境 设计实验:证明变形虫对外界刺激有反应 如果函数f(x)的定义域为{x|x>0},且f(x)为增函数,f(x•y)=f(x)+f(y)(1)证明:f(x)=f(x)-f(y)(2)已知f(3)=1,且f(a)>f(a-1)+2,求a的值范围 淋巴因子是内环境成分吗 这个有关变形虫细胞核的实验说明了什么用一根玻璃针将一个变形虫切成两半,有核的一半能继续生活,无核的一半死亡.如果将一个变形虫的核取出,无核的部分能短期生存,但不能繁殖后代,单 描写四季景物的诗句 T细胞可以产生淋巴因子么 效应T细胞呢?抗体由哪个细胞产生 吞噬细胞呢 总是分不清 T细胞可以产生淋巴因子么 效应T细胞呢?抗体由哪个细胞产生 吞噬细胞呢 总是分不清 希望详细回答 探索变形虫对刺激的反应(一定要做实验.取三块载玻片,分别在其两端各滴一滴含有变形虫培养液,然后把这两滴培养液联通起来.在载玻片相连一侧(统一在左边)的培养液边缘分别放上食盐 100℃ 98.5%硫酸 用什么材质的管道合适? 淋巴因子作用于抗原发生于内环境这句话是错的, 证明细胞是有机的整体,提供材料:正常生活的变形虫数只,培养皿,培养液.设计实验证明.步骤结果结论 输送酸性介质用什么泵和管道比较好?介质是硫酸溶液.用什么样的泵好一些? 用一根84厘米的木做一个正方体框架,每根短木条多少厘米 台湾哪个城市最大? 什么是镜面银油墨 请大家帮帮忙,解释一下蚂蚁为什么怕酸味?谢谢了,很急很急! 埃及的农业特色是以什么农业为主的 浓度为30克/升 含量在3%左右的硫酸可以用什么材质的管道输送 如图,以三角形ABC的边BC,AC为一边作等边三角形BCD和等边三角形ACE,连接DE.试猜想DE和AB的数量关系并证明. 变形虫和草履虫不具备生命活动的哪个层次 用到镜面银油墨的有哪些产品 如图,三角形abc为等腰三角形,ac=bc,三角形bcd和三角形ace分别为等边三角形,求证g是ab中点.求两种方法 在水溶液中 钠离子 氯离子 硝酸根可以共存吗还有在水溶液中 氢氧根 氢离子 硫酸根能共存吗 发烟硫酸用什么材质的泵和管道输送呢?说明选择的原因 免疫系统的功能是什么? 汤放久了发酸要怎么去除酸味 使用镜面银油墨的注意事项? 变形虫摄取食物的时候,它的细胞发生什么变化~ 面筋发酸如何去除酸味 镜面银油墨哪家好? 太阳能电池单体和太阳能电池片一样吗
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘