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

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

Android开发进阶之NIO非阻塞包(八) Android开发进阶之NIO非阻塞包(七) Android开发进阶之NIO非阻塞包(六) Android开发进阶之NIO非阻塞包(五) Android开发进阶之NIO非阻塞包(四) Android开发进阶之NIO非阻塞包(三) Android开发进阶之NIO非阻塞包(二) Android开发进阶之NIO非阻塞包(一) Android开发进阶教程系列(一)目录篇 Android开发调试工具TraceView多图演示 Android JNI实例代码(二) Android JNI实例代码(一) Android JNI开发高级篇 Android NDK开发技巧二 Android JNI开发进阶篇 Android JNI开发提高篇 Android JNI开发入门篇 Android NDK开发技巧一 Android数据库ContentProvider封装原理 Android Permission列表,ADT 0.9.9 bug Android Theme和Styles内部定义解析 Android平台下图表绘制相关方法 Activity类的runOnUiThread方法你用过吗 Android上鲜为人知的UI控件介绍和使用 获取Android手机上的图片和视频缩略图 Android JSON解析示例代码 Android ANR介绍与避免 Windows Phone 7 SDK完善度不如Android m3 Android中使用定时器TimerTask类介绍 控制Android LED灯颜色代码 Android应用Icon大小在不同分辨率下定义 这几句TSQL有错?帮忙看看 请教关于网卡与声卡冲突的处理方法 谁有installshield 6.30的license注册文件? 请问如何在页面操作中保存dataset 怎样选定ListCtrl中的一行(包括subItem区域)? 类对象的实例和类的实例有什么区别?? 请问各位大虾,公文管理系统怎么做? 如何将TIF或TIFF文件转换成网页可以显示的JPG,或是GIF文件(最好给出JAR和例子) 这段程序应该怎么编? 各位老大,近来看看吧,一下午了也没有人来回答,苦命呀!加分...狂加....跳楼... TO net_lover(孟子E章) : 在datagrid中如何在获得当前字段内的内容? 高手都过来,这里有好几个问题!!! 安全队列问题?up有分! 如何在XML文件中獲得某個節點下重復的節點的個數﹐用IXMLNodeCollectionMA嗎? 谁解决了我的问题,我再送他100分一共200分!够吧! 滚屏公告栏滚动速度太快,如何调慢?帮帮忙 一个简单的问题? 需要紧急帮助!!!!!! 一个进制转换的问题? 刚开始看java,第一个hello,java程序 HELP ME!JAVA & XML的问题。。。 用过F1做报表的请进 在java里怎样创建自己指定目录,如不是文件? 关于C51串口通讯问题? Realplay的问题 RDS无法启动业务对象上的方法? 钱能的《钱能C++程序设计实验指导》从哪下载? 哪儿可以下载photoshop7.0?? 高分请教! 手机或Palm,大侠指教 初学者提问,数据库问题 怎样能从dll中通过消息返回主程序一个字符串?? 编写进程管理器 10个小球中取出3个球,求出所有可能的情况,能提供一个通用的算法吗? dbf数据库访问的中文问题 同样的ASP代码在2000能通过,但在XP下却经常报错!!!???怎么更正?高手请指点。 win2000server 下安装Oracle8.0 的问题? BCB怎样写COM+?哪位高手指点一下?给个简单的例子最好 对面的高手看过来——如何把查询出来的记录转化成excell文件!!!!(100高分求解) 请教指点:有关ASP和IIS 用什么API函数可以读取INI文件中的设置项(不是WIN.INI,是自定义的INI文件) 如何对用sql搜寻出来的记录进行计算?请各位帮忙?谢过!!! 高手请进!!!! 请那里有图标,图片下载,可以把界面搞得漂亮一点。 directdraw中如何设置屏幕为主表面? 问题 谁能给我解释这个问题(关于响应消息),60分! 一个关于调用servlet问题!~!~! 如何用Delphi实现线性优化? 还是关于 SQL 语句 MFC的消息处理函数(例如:OnCreate)和消息过程函数(例如:WindowProc)的区别?并不是说OnCreate和WindowProc的区别,只是分别举个消息映射函数和窗口过程函数的例子.MFC的消息处理函数和消息过 某晶体和溶液的颜色区别,就比如Cu2+显蓝色,那么CuCl2固体是什么颜色? 蚂蚱的生活特点111111111111111111111111111111111111111111 甲存款的3/4等于乙存款的2/3,甲乙两人共存款3400元,甲、乙两人各存了多少元?记得列式计算 某晶体和溶液的颜色区别,就比如Cu2+显蓝色,那么CuCl2固体呢? mathematica作图,t1 = Plot3D[Abs[x - 1]×Sin(2×x×t×Pi),{x,0,1},{t,0,1}]ViewPoint -> {1.632,-3.688,2.059} mathematica画图问题如何用mathematica画圆锥和圆柱?举个例子,比如圆锥z=x^2+y^2,圆柱x^2+y^2=1 有甲,乙两个杯子,甲盛水,乙盛纯酒精.有甲、乙两个杯子,甲盛水,乙盛纯酒精.先将甲杯中的水倒进乙杯,使乙杯中液体增加一倍,调匀;再将乙杯中酒精溶液倒进甲杯,使甲杯中液体增加一倍,调 mathematica 我需要在二维坐标系中描出100来个数据点,并最佳拟合.同时对一部分线性拟合.我在mathematica中输入之后,总是报错,二维图都画不出来,求大神指导下,怎么写编码,错在哪了? 关于Mathematica绘图的问题.用Mathematica的Show[]函数,应该可以把定义域不相同的两个函数画到同一个坐标系中,可是我一直没能成功,只能画出一半,我的Mathematica版本是7.01的,谁知道原因哪?或者能 求直线l:2x-y-2=0,被圆C:(x-3)^2 y^2=9所截得的弦长 关于mathematica作图Show[Graphics[Circle[{3,7},2],Axes -> True]]做出的圆居然与坐标轴相切! 如果按红橙黄绿青蓝紫的顺序,将19921992······1992(1991个1992)只彩灯依次反复排列,那么什么颜色的彩灯要比其他颜色的彩灯少一只? 若直线l:y=k(x-2)-1被圆C:x^2+y^2-2x-24=0截得的最短弦长的方程..请写出具体步骤~谢谢拉~ 怎样抓蟑螂 有一组彩灯,是用两红一蓝三黄两绿两紫五种颜色依次装配的.一共要装150个灯泡,各种颜色的灯需要多少个? 如果直线2X-Y+1=0截圆X^2+Y^2=R^2的弦长等于5,求圆的半径R. 怎样捉蟑螂 元旦挂彩灯六种颜色灯泡红黄蓝绿白紫的次序装配一共装了80个,每种灯泡各需多少个 甲,乙两个杯子盛了同样多的水.甲杯里的水占杯子的三分之一,乙杯里的水占杯子的三分之二.哪个杯子大些? 蟑螂为什么难捉? 按红橙黄绿青蓝紫的顺序,将19921992.只彩灯依次排列,哪种颜色肯定少1只 甲乙两个杯子内盛水一样多,甲杯倒入乙杯40克后,甲杯水相当于乙杯水的4/5,原每杯水有多少克 人体辐射的红外线波长为()频率约为() 有一组彩灯,用两红,一蓝,三黄,两绿,两紫五种颜色依次装配.一共要装150个灯泡,各种颜色需多少个 求直线l:2x-y-2=0被圆C:(x-3)²+y²=9所截得的弦长? 人体辐射出的红外线波长为10微米,频率约为多少?为什么检测人体辐射的红外线就能确定认的体温?针对这一问题提一个合理的猜想! 望远镜原理由于______变大,我们感觉像变大了. mathematica的画图问题mathematica8里输好后执行没有显示,为什么?a = {Sin[1], 2 Sin[1/2], 3 Sin[1/3]};Do[a = Append[a, i Sin[1/i]]; ListPlot[a, PlotRange -> {0, 2}, PlotStyle -> PointSize[0.018]], {i, 4, 20}] 人体红外线频率(Hz)和波长(M)是多少? 在线等待人体红外线频率(Hz)和波长(M)是多少? 小明在超市帮妈妈买回一袋纸杯,他把纸杯整齐地叠放在一起,如图请你根据图中的信息若小明把100个纸杯整齐叠放在一起时,它的高度约是 cm.为什么 尽量不要二元一次方程 求直线L:2x-y-2=0 被圆C:(x-3)²+y²=9所截得的弦长 蝗虫 蜥蜴 蛙 蝙蝠哪个属于无脊椎动物? 下列动物中,属于陆生脊椎动物的是 ( ) A、蝗虫 B、蜈蚣 C、青蛙 D、蛇 蜥蜴的特点快,急.详细点.明天我就关了.!最好是以作文的形式。从特点,习性,概括 蜥蜴 鱼 家燕 青蛙 蝗虫 河蚌⑶上述动物中最高等的一种是_________,最低等的一种是________.(填字母) ⑷上述动物中成体用肺呼吸的有_______________,成体用鳃呼吸的有_________________.(填字母) 已知圆c点过(1,0),且圆心在x轴的正半轴上,直线y=x-1被该圆所截的弦长为2根2,则圆c的标准方程? 蜥蜴的生殖特点是? 求教蚱蜢和蝗虫是什么关系 已知圆C和y轴相切,圆心在直线x-3y=0上,且被直线y=x截得的弦长为2倍的根下7,求圆c的方程. 问下这种蜥蜴的名称我们本地称这种为“油龙” 不知道真实的名字是什么呢? 我的妈妈100字作文 已知圆c过点(1,0),且圆心在x的正半轴上,直线l:被该圆所截得的弦长为2倍根2,则圆c的标准方程为直线l:y=x-1 用Mathematica作图,求代码. 直线X-Y-2=0被圆(X-2)的平方+(y-2)的平方=4截得的弦长为? mathematica如何画图如题,刚用这款软件,不知道如何输出图像,是像office系列一样需要插入图像呢,还是输完plot函数之后自动生成的?我按照百度知道的一些答案的示例函数,输进去之后就是没图像. Mathematica 作图程序 留的数学期末试验.不过我们根本没学过 Mathematica,作图 y=sinX y=cosX极限 lim1/X(x趋近于无穷大) limsinX(x趋近于0)微分 对y=sinX 和 y=1/X求微分积分 求y=cosX的原函数 不定积分3x 妈妈作文100字 mathematica怎样画图?我想画3D的图形,比如想X^2+Y^2+Z^2=1,应该怎么输入?还有就是怎样把两个函数图形画到同一幅图里? 高中数学极坐标和参数方程!坐等 一直线的斜率为-2,且被圆x的平方+y的平方=4所截得得弦长为2,求此直线的方程 望远镜的工作原理最好能给出光路图~ 蚂蚱的品种有哪些(要详细哦~!)生活周期是多少以及习性如题悬赏15分哦!还有真诚的祝福 直线y=2被圆(X-2)平方+(y-2)平方=4截得的弦长 反射望远镜原理我买了一个EQUATORIAL REFLECTOR TELESCOPE 望远镜说明书是全英文,求中文说明书. 求化学中常见的金属化合物颜色,如Cu2+是蓝色,Fe3+淡黄等Mg Al Ag Na K Na Ca Zn Sn Pb NH4 等 蝗虫的生活习性(简短) 折反射望远镜原理自制一台折反射难吗 教我写《一个蓝颜色的故事》的开头写故事的, 请问俊男美女们哪些动物捕捉蟑螂,并且他们怎么捕捉蟑螂.
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘