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

改变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。我可不想去尝试并且列出这种技术其它可能的用法,因为这种可能性是无限的,它只会被一个程序员的灵活性所局限。

大势所趋,IBM Acme Air至少使用Netflix的5个开源工具 【观察】跨越“朦胧期”的云计算:产业、核心技术、生态圈以及突破点 Rovio Account:平台化之路修成正果 甲骨文总裁马克•赫德采访实录:用软硬件集成一体机打垮对手 如何一步一步打造高可扩展性的应用程序? 用Java编程,请保持简洁! 从安全隐患带来的商机来看,Hadoop同样很美 三年增四倍:如今谷歌服务占北美互联网流量四分之一 决战低功耗?Intel明年推低功耗Xeon处理器 在软件架构上增加新功能的注意事项 90后的代码界“女神”李雪:在编程中找到自己的“灵魂” 大数据之路不乏荆棘,然则其中的机遇却高于一切 iPhone销量令人意外:苹果Q3财季净利69亿美元 同比下滑 励志:12位早起的IT大佬们让小伙伴们都惊呆了 独家:苹果5周年限免神作,BADLAND开发者访谈 10亿次订阅!苹果Podcast的里程碑 免费利器Unity 4.2正式发布 支持WP8、Win8和BB10 SDCC 2013:Pinterest首位中国籍工程师两场演讲议题确定 甲骨文公布中国首届“Duke选择奖”名单 Moco、X幻想、开源中国获奖 直接拿来用,10个PHP代码片段 编程的未来 数据将成为主角 OpenCL 2.0发布,带来更强悍的异构计算能力 代码审查方式大调查 辅助工具居首(信息图) 一网打尽当下NoSQL类型、适用场景及使用公司 初创公司Treasure Data:每天处理7000亿行数据的20万次查询 豌豆荚王俊煜:将推支付SDK 与开发者3:7分成 英特尔-百度移动应用测试中心(MTC)正式启动 Intel将在2014年为数据中心用户定制低功耗SoC芯片 情绪低落?没事,让计算机来帮你! 水果生意难做 前新浪架构师徐佳转行试水挖煤 思考软件开发中的快与慢 在DELPHI中如何控制DOS命令语句的运行顺序? 请问:网站发手机短信息的技术解决方案,请详细说明一下! 为什么JBuilder5中没有Component Palette ,即能够产生各种控件的页.象jb3那样.有swing,swingcontainers,dataexpress..... 为什么我不能给分? 如何实现网络定时传输文件??? 完了,完了,我彻底完蛋了! 我从网上下载的*.bin文件的电影怎么播放? 考水平考试的朋友请过来看。 刚收到111222的信~~~难过~~~我会想念他的~~~你们是不是也会~~~ 我的硬盘为何这样!help!! 考水平考试的朋友过来看看 我要毕业啊 ===============想知道111222去向的朋友,看这(独家披漏)===================== 以下SQL语言如何写? 111222走了~~送分~~~心里不好受~~~ 111222真的走了~~~~~~~~~~~ JBuilder中用MYSQL数据库,用中文进行条件查询时,查不出任和数据? ASM 菜鸟问题,请大家帮忙!谢谢 解释一下好了吗? ============答对我的问题者,将可获得111222的签名照片!!!!!!!!=========== 关于图片的上传 ======== 绝对好消息,activereport2.0已经release 了,cracker们出动啊~~~~~加油 ============= 关于什么才是真正的系统分析和系统分析员的讨论!——欢迎大家参加,进行善意地讨论!^&^ 今晚的球赛什么时候开始啊? 哪里有关于水平考试(高程)的模拟题的啊??? 为什麽还是不能显示中文呢? 怎样获取WIN2000的密码 在父窗体怎样画jpg格式的背景图? 如何注册MSComm控件????????? 如何解决这个问题啊?重赏啊!!!! 公告: 从现在开始封shines 111222 :)流芳斑主,结贴的工作要开始了,会很累啊!我代表大家先谢谢你了! 怎样改变MSHflexgrid中每条记录的背景颜色。急急急急急急急 各位,能不能给我讲一下“句柄”是怎么一回事? 唉,心情不爽 关于C++语法的问题? 灌水-----大学时的恶作剧(转载) "setdibs()"是什么来着,请指教,谢谢! 写注释不宜过头 这段代码错在哪里? 请大家预测一下比分(参与者都有分)开赛后就不算了,还有半个小时,赶紧着 写网络电话程序的讨论 最后一帖,贴完睡觉----如何在电梯里捣乱 怎样定制一个在程序中重用的组件?只能通过包方式吗? 寻寻觅觅 汉英计算机科技文章 互译!!有劳各位朋友费心帮忙找找 我靠!真TMD不爽,这个时候准老婆的老妈进了医院! 怎样调用*.db格式文件 谁知道哪儿有英汉互译,急急急急!!!! ISO 9001认证 VS CMM2 谁知道哪儿有《神秘的人月》下载? 为111222求情 45%x=25%(x+16) 这个方程怎么解, 求方程如何解:x/14%+(5-x)/12%=40 求方程|x|+2|y|=0的解 45-x=x*6这道方程怎么解 20-x×12%=5这题解方程怎么做?请告诉方法还有过程 方程(x-1)2-(x+1)(x-1)+3(x+1)=0的解是 (36+x)/(6+x)=4方程式怎样解? 解释一下线性回归方程 顺便求一下y关于x的线性回归方程 要过程x: 3 4 5 6y: 2.5 3 4 4.5 2个x的方程怎解解答 36+X分之36X=24方程怎么解 (16+x)/(36+x)=9/19 (x+1)^2+x-2=1这道方程怎么解啊!其实原题目是:x+1 1----- + ----- =1x-2 x+1 谁知 x/36+x/24=16 这个方程如何解, 16+x=9/19(x+36) X*X=108+3*X怎么解呀X*X=108+3*X, (8-X)^2-X^2=36这个方程能解吗?怎么拆括号?得什么? 16+x=5/9*(36+x)怎么解 X+10+X+8=108怎么解 34+x-8=36-x方程怎么解 x-0.36=16解方程一分钟 (108-x)/x=12.5% 怎么解 这个方程怎么解:36/X=48/(X+8)? X-0.36=16,用解方程做错了错了,是X-0.36X=16 9(X-3.8)=108怎么解? 方程2x+4=36的解与y=+x-6的解相同,y应该是多少呢?要全面的,明天就交了, 100×(1-x)的2次方=81 求x的解 7.6x-3.2=4x-1.76 方程解 方程x/2+m/3=x-4与方程1/2(x-16)=-6的解相同,则m的值是 方程x÷4-x÷5=81怎么解 10(29/60-x)=18(25/60-x)这个方程怎么解? 方程2/x+3/m=x—4与方程2/1(x—16)=-6的解相同,求m的值 (2-3/4)x =4/5 (X-15)*2/3-(X-15)/3=10 怎么解这个方程? 若方程2分之x+3分之m=x-4与方程2分之一(x-16)=-6的解相同,求m的值 用方程解答4(x+1)=3(x+2) 60%x+14=18 这个方程怎样解? 若方程x/2+m/3=x-4与方程1/2(x-16)=-6的解相同,求m的值. (x+4)÷3=2.5 60-x+18=53的方程怎么解 方程2分之x+3分之m=x-4与方程2分之1(x-16)=-6的解相同求m的值本人不太容易懂 4×(10X+7)=700000+X 这个方程怎么解啊?一步也别省略! 18与X的15倍的和是63.用方程解. X-0.8X+6=16这道方程怎样解 4.8x-x=0.76方程式怎么解? 已卖出10盆花,没盆15元,又卖出x盆同样的花,共收入270元(列方程并求出方程的解) 方程:x+0.8x=2.16怎么解 8x-108=28 解方程式 (100%-40%)X + 90 = 15% X 这个方程怎么解 x/3.5+(x-35)/3.5=x/2 这个方程怎么解 方程8x+16=16的解是x=0 8x-108=208 解方程式 解方程式X-2=X÷3+12 x平方-8x=16 这个方程怎么解? 方程x(x-2)+x-2=0的解是? 2.8+x=10.35 解这个方程 (16+X)/(36+X)=6/11 求解方程的过程跪求解方程的过程 方程x^2-|x|-1=0的解是? χ²+12χ+36=0请问怎么解这个方程.非常谢谢.在线等谢谢各位了!能详细点写出步骤吗,谢谢! 求一道方程的解,(200+x)×36%=40+x 求方程x^2-2^x=0的解?谁会?
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn