荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: jek (好好学习天天向上), 信区: Program
标  题: 创世纪的 C++builder(5)
发信站: BBS 荔园晨风站 (Sun Mar 12 02:41:08 2000), 转信

以C++ Builder处理Windows 讯息(Message)



前言



虽然C++Builder为一RAD式的程式发展工具,程式设计师在大多数情

况下不需理会Windows讯息的细节,只要将心思放在软体元件的事件

处理函式即可。然而由於Windows作业系统终究是一个以讯息驱动的

系统,因此架构其上的的应用程式自然无法自外於系统之外,在遭遇

到C++Builder没有定义的事件时,Windows讯息处理能力仍然是

C++Builder程式人不可或缺的能力。





不可否认地,C++Builder所提供的事件处理能力已具备了某一程度的

完备性,然而我们也必须承认,在C++Buider建构的VCL美丽新世界

中,仍然不免有漏网之鱼。例如使用者自定讯息的处理,Winsock讯息

的处理及一些Windows讯息如WM_NC****

系列的讯息都是C++Builder的物件模型所未包含的。



在本文中我将告诉你如何以C++Builder来处理Windows讯息,并透过

此一能力,来达成在一般VCL元件所无法做到的功能。



何谓Window讯息(Message)



大家都知道 Windows是一套以讯息驱动(Message

Driven)的作业系统。然而对於讯息本身却讳莫如深,只知其然而不

知其所以然,虽然C++Builder将某些Windows讯息封装於事件(Event)

系统中,但身为一个Windows程式设计师,实有必要了解Windows的

讯息系统。





所谓讯息是由Windows作业系统送往程式的事件。它是系统中各个物

件沟通的方式,举例来说,当移动滑鼠、按下滑鼠键、改变视窗大小

时,Windows都会送出讯息以通知程式。当然,为了要辨别事件的内容,

Windows系统中定义了许多的讯息,如WM_PAINT,WM_CHAR等等。





当事件发生时,Windows会判断该事件必须由那个程式接收,然後将事

件以讯息的方式送往程式的视窗中。虽然在Windows系统中包含了数

以百计的事件,但是作业系统并没有为各个事件设计不同的讯息结构,

而是以一个一般性的结构来描述讯息,这个结构在C++Builder就称是

TMessage。





messages.hpp中,你可以决定要自行解释TMessage叁数

或是直接将其转换成专属的结构。很抽象吗?我举个例子吧,以

WM_NCHITTEST讯息来说,C++Builder为它定义了TWMNCHitTest的专

属结构,所以你可以直接经由它来得到XPos、YPos等值。或者你也可

以直接由TMessage的LParam取得其值,端看你使用的方便。仔细观

察TMessage及TWMNCHitTest两个结构,你会发现它们是等价的,也

就是说它们的大小是一致的,因此你可以直接用强制转型互相转换(这

有点类似union的方法)。





struct TMessage

{

Cardinal Msg;

union

{

struct 断ⅲ羰遣淮恚蚩芍苯咏?

给Windows的内定处理程序来处理之,若是程式需要传回值,也可以

在此时传回,Windows会将该值传回给呼叫方。如此就完成了讯息传递

的程序。





复杂吗?一点也不!了解Windows讯息系统的运作後,我们来看看可

以利用它来做些什麽有趣的事吧!



讯息使用范例一 使用者自定标题棒的实作



一般Windows程式的标题棒位於视窗的上方,我们可以利用该标题棒

来移动视窗。以下我将为你示范如何利用C++Builder实作出置於视窗

左方的标题棒。如图一:





图一 标题棒在左方的视窗。



如上图,你可以很清楚地看到,这个视窗和其他的视窗有很大的不同;

它的标题棒位於左方,而且其颜色为绿色,同时其文字的走向为由下

而上的90度字形,而其功能则和一般的标题棒相同,你可以将滑鼠移

至该处,然後移动该视窗。到底这是如何达成的呢?





WM_NCHITTEST讯息的奥秘



WM_NCHITTEST讯息是一个很特殊的讯息。它是用来决定目前滑鼠所在

位置属性的讯息,因此我们可以利用此特性,当滑鼠移至指定的位置

时,传回

HTCAPTION,使得系统以为滑鼠目前位於标题棒,如此你就可以移动视

窗了。如何?是不是很神奇呢?



由上可知,只要我们适时地拦截WM_NCHITTEST讯息,然後传回

HTCAPTION,就可以顺利地欺骗系统,达成在任何位置模拟出标题棒的

效果。



C++ Builder的处理讯息的巨集



在C++Builder为了处理讯息的方便,因此定义了三个处理讯息的巨集

(Macro)。



BEGIN_MESSAGE_HANDLER;它共需要

三个叁数,第一个叁数代表讯息的ID,第二个代表叁数型态,最後一

个则是讯息事件处理函数。





乍看之下,这个巨集似乎和MFC及OWL所使用的巨集有几分神似,没

错,不过其机制却更为简单及简洁,我们可以看看C++Builder对於这

三个巨集的原始定义:



#define BEGIN_MESSAGE_MAP virtual void __fastcall Dispatch(void

*Message) \

{ \

switch (((PMessage)Message)->Msg) \

{

#define MESSAGE_HANDLER(msg,type,meth) \

case msg: \

meth(*((type *)Message)); \

break;

#define END_MESSAGE_MAP(base) default: \

base::Dispatch(Message); \

break; \

} \

}



相较於MFC或 OWL的可怕巨集,它实在是简单多了,这是因为

C++Builder已替你完成了大部份的工作。其实若我们把以上的巨集展

开後,可以得到以下的结果:



virtual void __fastcall Dispatch(void *Message)

{

switch (((PMessage)Message)->Msg)

{

case WM_NCHITTEST:

OnNcHitTest(*((TMessage *)Message));

break;

default:

TForm::Dispatch(Message);

break;

}

}



怎麽样?展开之後是不是有恍然大悟的感觉,要弄清楚这个巨集在卖

啥膏药是很容易的,如果你玩过MFC的讯息处理机制,再看到以上的

巨集,相较之下,实在是小儿科,不过也就因其简单,所以C++Builder

的优势益加彰显。





我简单地说明以上的程式:在每个TForm中都定义一个名为Dispatch

的虚拟函式,它就是用来处理Windows的讯息的,在大部份情况下,

讯息都是呼叫C++Builder所提供的处理函式,因此你不需要修改它。





换句话说,我们只要改写Dispatch函式,就可以藉以处理指定的讯息

了。前面提到的三个巨集只是将这个程序简化而已,没什麽大不了。



自定标题的绘制



由於我们要使用自定的标题,所以你必须将程式所使用的 TForm的

BorderStyle性质设为 bsNone,如此你的TForm就不会有标题棒了。



再来你就必须自行绘制标题棒,我们希望绘制一个位於左於的标题,

因此我们必须处理TForm的OnPaint事件,然後在此事件中绘制标题

棒。以下即为其事件处理函式:



void __fastcall TForm1::FormPaint(TObject *Sender)

{

RECT rc;

::SetRect(&rc,0,0,ClientWidth,ClientHeight);

DrawButtonFace(Canvas,rc,1);

Canvas->Pen->Color=clGreen;

Canvas->Brush->Color=clGreen;

Canvas->Rectangle(0,0,20,ClientHeight);

:// 以下略去

:

}



你可以看到,我们画出一个宽为20,颜色为绿色的标题棒。因此我们

处理WM_NCHITTEST讯息的处理函式也必须做相对应的修改:



void __fastcall TForm1::OnNcHitTest(TMessage& Msg)

{

TPoint pt;

pt.x=LOWORD(Msg.LParam);

pt.y=HIWORD(Msg.LParam);

pt =ScreenToClient(pt);

RECT rc;

::SetRect(&rc,0,0,20,ClientHeight);

if (PtInRect(&rc,pt))

Msg.Result = HTCAPTION;

else

DefaultHandler(&菀耘卸?

是否落於我们所定义的标题棒范围内,若是则传回HTCAPTION值,否

则就交由内定的处理函式DefaultHandler来处理。如此就完成了一个

位於左方的标题棒了。





旋转文字的输出



仔细观察图一,你会发现它所使用的标题字元的方向,已经因应标题

棒的转向而成为90旋转的文字,这是如何达成的呢?



其实说穿了没什麽,只是利用传统SDK的绘图方法来画出来的。因为

在C++Builder的TFont物件并没有定义文字旋转的属性,所以我们只

好透过传统的GDI绘图方法来达成这个目标。





char* msg=Caption.c_str();

LOGFONT fontRec;

memset(&fontRec,0,sizeof(LOGFONT));

fontRec.lfHeight = -13;

fontRec.lfWeight = FW_NORMAL;

fontRec.lfEscapement = 900; // 旋转文字的关键

lstrcpy(fontRec.lfFaceName,"细明体");

HFONT hFont=CreateFontIndirect(&fontRec);

HFONT hOld=::SelectObject(Canvas->Handle,hFont);

::SetRect(&rc,0,0,20,ClientHeight);

::SetTextColor(Canvas->Handle,RGB(255,255,255));

::TextOut(Canvas->Handle,3,ClientHeight-3,msg,lstrlen(msg));

::SelectObject(Canvas->Handle,hOld);

::DeleteObject(hFont);



以上的程式我不打算详加说明,简单地说,它就是建立一个旋转90度

的字形,然後将字串以此字形画於萤幕上,此段程式码的关键在於你

必须知道Canvas->Handle即是代表GDI绘图的HDC。其馀的函式说明

你都可以在一般讲解传统Windows

SDK绘图的书籍中找到。



由此我们也可以得到一个经验:虽然C++Builder的快速程式发展环境

已经取代了传统SDK式的程式设计中大部份的工作,然而通晓一些必

要的SDK程式技巧却可以使你上一层楼。所以我建议你在『行有馀力』

时,不妨可以看看SDK相关书籍,充实基础知识。或许我们可以名之

为『立足

BCB,放眼 SDK』的学习态度吧!



其他说明



在本程式中因为TForm的BorderStyle性质为bsNone。因此并没有外

框,为了美化视窗,所以我写了几个辅助函式来绘出立体框。若你在

其他程式中有类似的需求,也可以使用之。





void DoRect(TCanvas* Canvas,RECT& rect,COLORREF

cTopColor,COLORREF cBottomColor)



{

POINT p[3];

p[0].x = rect.right;

p[0].y = rect.top;

p[1].x = rect.left;

p[1].y = rect.top;

p[2].x = rect.left;

p[2].y = rect.bottom;

Canvas->Pen->Color=TColor(cTopColor);

Canvas->Polyline(p,3);

p[1].x = rect.right;

p[1].y = rect.bottom;

p[2].x--;

Canvas->Pen->Color=TColor(cBottomColor);

Canvas->Polyline(p,3);

}



void Frame3D(TCanvas* Canvas,RECT& rect,COLORREF

cTopColor,COLORREF

cBottomColor,int iColWidth)

{

rect.bottom--; rect.right--;

while (iColWidth > 0)

{

iColWidth--;

DoRect(Canvas,rect,cTopColor,cBottomColor);



{

Canvas->Brush->Color=clBtnFace;

Canvas->FillRect(TRect(rect));

Frame3D(Canvas,rect,::GetSysColor(COLOR_BTNSHADOW),::GetSysCo

lor(COLOR_WINDOWFRAME),nBevelWidth);



Frame3D(Canvas,rect,::GetSysColor(COLOR_BTNHIGHLIGHT),::GetSy

sColor(COLOR_BTNSHADOW),nBevelWidth);



}



这三个函式中最重要的就是

DrawButtonFace,它是用来在一个矩形范围中画出一个类似Button的

立体方框,在本程式中我用它来画出TForm的边框。你可以由图一看

出它的视觉效果。



程式的改进



前面我们提到改进bsNone视窗视觉效果的方式是利用自行撰写的

DrawButtonFace函式来达成,它虽不失为一个解决问题的方法,但是

却也因此增加了程式的复杂度,再来我为你示范一种利用改写

CreateParams函式的技巧来达成类似功能的方法。





CreateParams是一个虚拟函式,你可以经由它来修改windows的

style,因为原先在C++Builder中所定义的Form是一个Dialog(交谈

窗),而交谈窗的外形内定是有标题棒的,然而如果我们如前面的方法

将外框设为bsNone

的话,那就必须自行画出假的视窗外框,否则看起来不好看。



但是在Windows系统中除了前面的Dialog式的视窗之外,还提供了另

一种POPUP式的视窗,只不过在C++Builder并未提供该选项罢了。因

此我们其实可以透过改写CreateParams的方式来产生WS_POPUP形式

的视窗,如此一来我们就不必煞费周章地撰写画外框的函式了。它的

程式其实很简单,只是将Params.Style的WS_DLGFRAME

(代表使用Dialog外框),改成另一种WS_POPUP (弹出式视窗)。要

做到以上效果,只要利用and及or运算就可以达到了。以下即为其程

式码:



void __fastcall TForm1::CreateParams(TCreateParams& Params)

{

TForm::CreateParams(Params);

Params.Style |= WS_POPUP;

Params.Style ^= WS_DLGFRAME;

}



图二为改写後的程式执行结果,不仅程式简洁了许多,而且外观也较

好看,那是因为我们在画标题棒时,不会像前面一样将外框盖住的缘

故。





图二 利用CreateParams技巧的新程式。



讯息使用范例二 在程式中使用材质背景



许多人在使用网际网路浏览器如Internet

Explorer、Netscape上网站时,会发现许多网页上普遍使用了材质图

案做为背景,大大加强了它的视觉效果,也使用网页看起来更为美仑

美奂,这时也许你会想:这个材质背景是如何做出来的呢?





在以下的文章中,我会示范如何利用C++Builder做出上述的材质背景

效果,让你的程式也可以做出如Browser般的效果。此程式的执行效

果如图三





图三 具有材质片背景的Form



WM_ERASEBKGND讯息说明



WM_ERASEBGGN是在Windows背景将要被清除时,所触发的讯息。在此

讯息发生时,会传入要清除的Windows的HDC (

还记这个SDK中用来绘图的重要角色吧?)。因此我们可以取得此HDC,

然後将Canvas的Handle值设为该值,如此便可以在Canvas上作画了。

宣告使用WM_ERASEGKGN



class TForm1 : public TForm

{

__published: // IDE-managed Components

TPanel *Panel1;

private: // User declarations

public: // User declarations

__fastcall TForm1(TComponent* Owner);

void virtual __fastcall OnWMEraseBkgnd(TWMEraseBkgnd& Msg);

BEGIN_MESSAGE_MAP

MESSAGE_HANDLER(WM_ERASEBKGND,TWMEraseBkgnd,OnWMEraseBkgnd)

END_MESSAGE_MAP(TForm)

};



为了要拦截WM_ERASEBMGN讯息,因此我们必须利用前面谈过的巨集来

宣告之,在此我们采用C++Builder为WM_ERASEBKGND定义的

TWMEraseBkgnd讯息结构做为叁数,同时定义了一个讯息处理函数

OnWMEraseGkgnd。当然,它所传入的讯息叁数是前述的

TWMEraseBkgnd&。



以材质图案填满画面



在完成了讯息处理函数的定义之後,再来我们就必须撰写实际的程式

码。为了在所传入的HDC中做画,我们必须new一个

Canvas,然後自TWMEraseBkgnd中取得HDC的值。



接着为了要将材质背景载入,我们必须new一个Graphics::TBitmap (在

这里加上Graphics:: 是因为尚有另一种Tbitmap

,是位於Windows的namespace中的,因此必须以Graphics::

来区别它的名称空间)。然後,我们就可以利用LoadFromFile将材质

背景图案载入。



在完成了以上两个必要的准备动作之後,我们就可以正式将材质背景

画在Canvas上面了,首先当然要计算所画的次数,然後利用圈以

Canvas的Draw指令,将它填满整个萤幕。最後不要忘了将new产生的

物件,以delete删除之。以下为其程式列表:





void __fastcall TForm1::OnWMEraseBkgnd(TWMEraseBkgnd& Msg)

{

TCanvas* canvas = new TCanvas;

Graphics::TBitmap* bitmap = new Graphics::TBitmap;

bitmap->LoadFromFile("back.bmp");

canvas->Handle = Msg.DC;

int cx = ClientWidth/bitmap->Width + 1;

int cy = ClientHeight/bitmap->Height + 1;

for (int i=0; i<cy; i++)

for (int j=0; j<cx; j++)

canvas->Draw(j*bitmap->Height,i*bitmap->Width,bitmap);

Msg.Result = true;

dele到别的标准元件,否则你就必须具备讯息处理的能力。所以

说,了解Windows讯息是你不可或缺的技巧,唯有如此,你才能『百

尺竿头,更进一步』,不会被RAD给局限住。





--
※ 来源:·BBS 荔园晨风站 bbs.szu.edu.cn·[FROM: 192.168.1.118]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店