分享
 
 
 

深入C++ Builder之编写自己的元件-深入分析VCL继承、消息机制(3)

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

这篇文章提及内容可能大家已经在很多地方看到过了,作者也是如此,只不过还看了很多VCL源代码,加上自己实际编写元件的经验,拼凑了这么一篇文章。所以所有言论都是个人观点、经验的描述,仅供参考。

你可转载,拷贝,但必须加入作者署名Aweay,如果用于商业目的,必须经过作者同意。

题外话

很多朋友看了我的前两篇文章后,纷纷来信说能不能介绍一些元件入门的基础知识,因为他们根本找不到相关资料,并询问我是如何知道这些知识的。诚然,网上确实没有这方面的介绍资料,更何况大家是学BCB的,对于Delphi的源代码学习起来更是困难,对于作者来说也不比大家知道多少,我认为最好的方式就是看VCL源代码和去Borland的新闻组提问,至少我是这样解决问题的,希望你也可以。

这里是Borland新闻组地址,如果你英文够好,他们基本是有问必答的:

forums.borland.com

对于那些想学习基础元件知识的朋友,我会在这系列文章的最后部分专门安排2篇文章作为礼物送给你们,一篇是我会实际分析一个专业级元件,来个源代码解剖,把所有细节展示给大家,第二篇是我会实际编写一个简单使用的组件,并介绍全过程,希望大家喜欢。

更多消息处理

已经写了2篇文章了,怎么还是消息处理?是的,编写元件就是处理消息和表露事件,对于一般的消息处理,前面2篇文章介绍的内容已经足够用了,但是很多时候这还是不够的,比如如果在设计时期你更改了元件的Font属性,而你又想根据字体重新绘制。很明显传统的Windows消息处理其不到丝毫作用,这样的消息通常是WM_XXXX的形式。如果你研究过VCL源代码,你会发现很多CN_XXXX和CM_XXXX这样的消息,如果你要完成我上面提到的消息处理,这些消息可以帮助完成任务。

其实,VCL存在一些非API消息以供其内部使用,为什么要这样做呢?这要从WM_COMMAND & WM_NOTIFY消息说起,我们说WM_COMMAND消息并不是直接发给实际产生消息的窗体,而是发送到它的父窗体。但是父窗体几乎不可能用通常方法处理这些根本不知道如何处理的消息,于是父窗体把这个消息加上CN_BASE在分发到实际的子窗体中,然后由实际的子窗体处理。

比如TBitBtn元件为了在按钮表面绘制图象,处理了CN_DRAWITEM消息,这个消息处理函数是这样写的:

FCanvas.Handle := DrawItemStruct.hDC;

R := ClientRect;

… //省略一部分

if IsDown then

OffsetRect(R, 1, 1);

TButtonGlyph(FGlyph).Draw(FCanvas, R, Point(0,0), Caption, FLayout, FMargin,

FSpacing, State, False, DrawTextBiDiModeFlags(0));

if IsFocused and IsDefault then

begin

R := ClientRect;

InflateRect(R, -4, -4);

FCanvas.Pen.Color := clWindowFrame;

FCanvas.Brush.Color := clBtnFace;

DrawFocusRect(FCanvas.Handle, R);

end;

FCanvas.Handle := 0;

可以看出这和通常处理Paint的方法差不多,其实都是在HDC上作图。如果你学习过SDK的话,其实我们可以自己处理WM_NOTIFY消息来处理那些由控件产生的消息,只不过VCL替我们封装了一下而已。

还有一些消息是VCL内部控件而产生的,这类消息通常是CM_XXXX的格式,比如CM_FONTCHANGED这个消息就是当字体改变的时候触发,详细的定义你可以在Controls.pas文件中找到,这里就不再详细介绍了

对于上面的CM_FONTCHANGED消息,通常是这样处理的:

procedure TBitBtn.CMFontChanged(var Message: TMessage);

begin

inherited;

Invalidate;

end;

通过上面的讨论,得出一个结论,所有CN/CM消息都可以自己处理,但是他们没有对应的虚函数,所以我们只好用老方法,所以消息映射宏在这里是最好得解决方案,比如像这样:

BEGIN_MESSAGE_MAP

VCL_MESSAGE_HANDLER(CN_DRAWITEM, TWMDrawItem, CNDrawItem)

END_MESSAGE_MAP(TCustomControl)

Void __fastcall CNDrawItem(TWMDrawItem Msg);

定义自己的消息

在上篇文章的结束,我示范了一段元件代码,如果你还记忆犹新的话:

typedef void __fastcall (__closure *THoverShapeEvent)(TObject* Sender,int Index);

typedef void __fastcall (__closure *TShapeSelectedEvent)(TObject* Sender,int Index);

是否还记得上面的代码?

大概来说那是函数指针的申明,对于初学者来说,上面的申明真的很晦涩,我来解释一下:THoverShapeEvent是一个函数指针,该函数的返回值是void , 调用类型是__fastcall,有2个行参,分别是TObject*和int,关键在于红色的__closure关键字,什么意思?

在BCB的帮助我我找到了如下说明:

The keyword __closure was added to support the VCL and is used when declaring event handler functions.

就是如此简单,几乎没有提供任何信息,只知道__closure提供对事件处理函数的支持,下面我来详细介绍一下:

不知道你有没有写过这样的代码:

我们设计了一个类,比如遍历磁盘,有一个数据成员是回调函数指针,当我们遍历磁盘的的函数找到了一个文件时调用这个回调函数,通常情况下,我们这个回调函数需要申明在类的外面,那么还是指针需要这样申明:

typedef void __fastcall (*BDCallBack)(String path,int type);

但是这显然不符合OO设计原则,如果你想把一个类的成员函数指定为这个成员函数,那么你将需要这样申明:

typedef void __fastcall (base::* BDCallBack)(String path,int type);

同时你需要这样赋值:

BDCallBack m=&bass::func;

语法越来越晦涩了,这还不是最重要的,如果有很多类的成员函数都需要指定为回调函数呢?你需要为每一个类申明一个类似的函数指针,我想你已经崩溃了。

__closure这个时候就有用武之地了,如果你这样申明:

typedef void __fastcall (__closure *BDCallBack)(String path,int type);

那么所有问题都解决了,它可以方便的透过对象直接访问成员函数,在所有的类中你都可以这样做:

class A

{

BDCallBack func;

Void DoSometing()

{

func(“Find it”,0);

}

};

class B

{

Funcb()

{

A a;

a.func=this.callback;

}

void __fastcall callback(String path,int type)

{

}

}

上面的代码是不是很简捷,但这跟编写元件有什么关系呢?

我们还是以上篇文章的例子为例:

if(!Down)

{

int sh=ShapeAtPos(X,Y);

if(sh!=-1)

DoHoverShape(sh);

return;

}

这段代码很好的说明了问题,可以看出我们需要表露的事件同样是一个回调函数,我是这样调用的:

void __fastcall TVecCanvas::DoHoverShape(int index)

{

//TODO: Add your source code here

if(FOnHoverShape) //如果属性被赋值,及有相应处理函数

{

FOnHoverShape(this,index);

}

}

我是这样赋值的:

__property THoverShapeEvent OnHoverShape = { read=FOnHoverShape, write=FOnHoverShape };

可见事件处理器其实是一个属性,BCB会自动把On开头属性当作事件对待,所以这个属性就出现在Event列表里了。

我们来总结一下:我们自定义的事件实际上就是回调函数,在相应需要触发的地方调用由元件用户指定了的回调函数,一句话道破了自定义事件的真谛,但是却花了一大篇文章来解释它的原理,即使如此,我仍然相信由很多朋友没有真正了解这其中的奥秘,如果是这样,你需要看看什么是CallBack函数,属性如何定义等等这样的文章。

最后,这这篇文章的结尾,我留下自己的Email:

sineysan@163.com

如果大家有什么问题可以来信与我讨论:

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有