1 前言
作為和delphi類似的rad(rapid application development)工具,c++ builder的強大功能不僅體現在數據庫開發方面,也凸現於應用程序開發上(令人稱絕的是這兩方面結合得非常好)。僅就應用程序而言,要真正體現c++ builder的優勢,開發出高質量的軟件,則在拖拉拽放之外,尚需用到一些進階技術。如消息處理、dll、ole、線程、sdk編程。c++ builder在這些方面都或多或少有獨到的優勢。此外,可以方便地制作自定義控件,也是c++ builder的一大特色和高級功能。本文將通過制作一個標題棒在窗口左邊的對話框控件,來示范一些c++ builder中關於控件制作和消息處理的概念,同時涉及到一點sdk編程。我們將要制作的是一個對話框,就如同opendialog等一樣,一調用其execute()方法,就彈出一個如圖一所示的窗口。這個窗口的標題棒位於左方,綠色,文字走向為由下而上的90度字形,其功能和一般的標題棒相同,可以將鼠標移至該處來移動該窗口。
首先來完成這個窗口,然後用它來制作對話框控件。
2 利用wm_nchittest消息制作豎直標題的窗口
.wm_nchittest消息
c++builder將某些windows消息封裝於事件(event)中,但無法囊括所有消息,如wm_nc**** 系列消息。wm_nchittest消息發生於游標(cursor)移動或鼠標按下、釋放時,返回值指示目前游標所在位置,如返回hthscroll表示處於水平滾動條內,返回htcaption表示處於標題棒內(參見win32 sdk help)。其參數xpos、ypos分別表示游標的x、y坐標(相對於屏幕左上角),分別對應於lparam的低字和高字。如果攔截wm_nchittest消息,使得當鼠標在窗口左邊按下時,人為地將返回值設為htcaption,則系統將以為是在標題棒內,於是將可以移動窗口,完成了標題棒的功能,至於顏色和文字,則與消息無關,將在下面敘述其原理。
.windows消息
消息就是windows操作系統送往程序的事件。但事件數以百計,操作系統並沒有為各個事件設計不同的消息結構,而是以一個一般性的結構來來描述消息,這個結構在c++ builder中定義為tmessage。另外c++ builder對常見消息定義了專用結構,二者對等。可以直接將消息轉換為專用結構,也可以自行解釋tmessage參數。以wm_nchittest消息為例,它的定義如下:
struct twmnchittest
{
cardinal msg;
long unused;
union
{
struct
{
windows::tsmallpoint pos;
long result;
};
struct
{
short xpos;
short ypos;
};
};
};
對照tmessage定義:
struct tmessage
{
cardinal msg;
union
{
struct
{
word wparamlo;
word wparamhi;
word lparamlo;
word lparamhi;
word resultlo;
word resulthi;
};
struct
{
long wparam;
long lparam;
long result;
};
};
};
可以發現,tmessage的lparam成員對應twmnchittest的pos成員,就是說以下兩行語句
等價:
tpoint pt=tpoint(msg.lparam); //此時msg類型為tmessage
tpoint pt=tpoint(msg.pos); //此時msg類型為twmnchittest
.c++ builder處理消息的宏
在c++ builder中自定義消息處理是較為方便的,結合wm_nchittest舉例如下:
在窗口類的protected部分加入如下宏定義:
begin_message_map
message_handler(wm_nchittest,tmessage,onnchittest)
end_message_map(tform)
message_handler包含3個參數:wm_nchittest,消息標識,也可以為自定義消息如wm_mymessage,這時只需加一個宏如#define wm_mymessage wm_app+1等;第二個參數tmessage代表消息類型,也可以為符合要求的自定義消息結構類型如tmymsg等,onnchittest為消息處理函數。這樣,一旦有wm_nchittest消息傳給tform,對該消息的響應就完全交由onnchittest函數處理。onnchittest函數只有一個參數,類型為message_handler中第2個參數的引用,即tmessage &或tmymsg &。
.完成圖一的窗口。
開始一個新應用程序(new application),將form1命名為vcform,對應單元文件為vcap.cpp,頭文件為vcap.h。vcform的boarderstyle設置為bsnone,其上放置一個位圖按鈕bitbtn1,caption為&ok,kind為bkok,onclick事件處理函數中加入一句close()。然後在vcap.h的protected部分加入如前所述消息處理宏和函數onnchittest的聲明,以處理標題條的拖動功能。為完成標題的著色和文字輸出,雙擊vcform的onpaint事件以定制formpaint函數,具體代碼見下面源碼。此外為使窗口有立體感,重載虛函數createparams,以修改窗口的風格。完整的vcap.h和vcap.cpp如下:
//vcap.h
#ifndef vcaph
#define vcaph
#include
#include
#include
#include
#include
class tvcform : public tform
{
__published: // ide-managed components
tbitbtn *bitbtn1;
void __fastcall formpaint(tobject *sender);
void __fastcall bitbtn1click(tobject *sender);
private: // user declarations
protected:
void __fastcall onnchittest(tmessage & msg);
void __fastcall createparams(tcreateparams& params);
begin_message_map
message_handler(wm_nchittest,tmessage,onnchittest)
end_message_map(tform)
public: // user declarations
__fastcall tvcform(tcomponent* owner);
};
extern package tvcform *vcform;
#endif
//vcap.cpp
#include
#pragma hdrstop
#include "vcap.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
tvcform *vcform;
__fastcall tvcform::tvcform(tcomponent* owner)
: tform(owner)
{
}
void __fastcall tvcform::formpaint(tobject *sender)
{
//繪制寬20的綠色標題條
rect rc;
setrect(&rc,0,0,clientwidth,clientheight);
canvas->pen->color=clgreen;
canvas->brush->color=clgreen;
canvas->rectangle(0,0,20,clientheight);
//輸出旋轉文字
char* msg=caption.c_str();
logfont fontrec;
memset(&fontrec,0,sizeof(logfont));
fontrec.lfheight = -13;
fontrec.lfweight = fw_normal;
fontrec.lfescapement = 900; //旋轉角度900x0.1度=90度
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);
}
void __fastcall tvcform::bitbtn1click(tobject *sender)
{
close();
}
void __fastcall tvcform::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(&msg);
}
void __fastcall tvcform::createparams(controls::tcreateparams& params)
{
tform::createparams(params);
params.style |= ws_popup;
params.style ^= ws_dlgframe;
}
vcform的消息處理已經介紹過,這裡再對標題條的繪制作簡要說明。由於c++builder的tfont沒有定義文字旋轉旋轉的屬性,因此用傳統的sdk繪圖方法。canvas->handle即是代表gdi繪圖的hdc。
3 制作對話框控件在開始制作控件之前,先將vcap.cpp中的#pragma package(smart_init)行注釋掉。創建控件的步驟是:創建一個單元文件,在其中完成控件的類定義和注冊,然後就可以安裝了。控件類一般從某個現有類繼承導出。制作控件與一般類定義的主要區別在於屬性(property)和事件(event),事件也是屬性。由屬性就帶來了屬性的存取方法、缺省值、屬性編輯器等問題。為簡單起見,本控件只涉及到上述一部分概念,但能涵蓋控件制作的一般過程。
.開始一個空控件
由於要制作的對話框控件的最小必要功能是一個execute()方法,因此可以從tcomponent類繼承。命名控件名為tvcaptiondlg,定義控件的單元文件命名為vcapdlg.cpp,其頭文件為vcapdlg.h。用component wizard或手工方法完成如下文件:
//vcapdlg.h
#ifndef vcapdlgh
#define vcapdlgh
#include
#include
#include
#include
class package tvcaptiondlg: public tcomponent
{
private:
protected:
public:
virtual __fastcall tvcaptiondlg(tcomponent *owner);
__published:
};
#endif
//vcapdlg.cpp
#include
#pragma hdrstop
#include "vcapdlg.h"
#pragma package(smart_init)
static inline tvcaptiondlg * validctrcheck()
{
return new tvcaptiondlg(null);
}
namespace vcapdlg //同控件定義單元文件名,首字母大寫,其余小寫
{
void __fastcall package register()
{
tcomponentclass classes[1]={__classid(tvcaptiondlg)};
registercomponents("mailuo",classes,0);
}
}
__fastcall tvcaptiondlg::tvcaptiondlg(tcomponent * owner)
:tcomponent(owner)
{
}
registercomponents("mailuo",classes,0)指示在控件面板上的mailuo頁(沒有則創建該頁)上生成classes數組包含的所有控件,這裡是一個tvcaptiondlg控件。當然此時的tvcaptiondlg控件不具備tcomponent類之外的任何能力。
.將要用到的form文件包含進來
這只需在vcapdlg.cpp的#include "vcapdlg.h"後加入一行#include "vcap.cpp"(vcapdlg.*與vcap.*在同一目錄)即可,重申一句:vcap.cpp中的#pragma package(smart_init)行要去掉。將整個vcap.cpp和vcap.h的內容包括在vcapdlg.cpp中也是可以的,這樣就用不著vcap.*文件了.即將類vcform的定義與vcapdlg放在一個文件裡,反正vcform只不過是vcapdlg要用到的一個類定義罷了。不過這樣一來,在生成vcform的實例對象時,上面所說bitbtn1的caption、kind等與缺省值不等的屬性都需要運行時設置,因為非缺省屬性是保存在.dfm文件裡的。這也是使用了form的類常用單獨的單元文件保存的原因。
.添加接口屬性
這裡只提供一個caption屬性供控件使用者用於讀取或設置對話框的標題。為此只需在類tvcaptiondlg的聲明體的private區加入一個ansistring fcaption變量作內部存儲用,並在__published
區加入一行:
__property ansistring caption={read=fcaption, write=fcaption};
因為屬性類型是ansistring,所以不需專門的屬性編輯器處理設計時屬性的編輯。另外在設計時該屬性值的改變不需引起什麼立即的處理和過程,因此存取方法采用最簡單的立即存取(read=fcaption,
write=fcaption)。
.添加執行方法
vcaptiondlg的execute()方法的功能是創建一個類vcform的實例對象並模式顯示之。這只需如下代碼:
void __fastcall tvcaptiondlg::execute()
{
vcform=new tvcform(application);
vcform->caption=caption;
vcform->showmodal();
delete vcform;
}
其中vcform為vcap.cpp中已聲明的tvcform類類型的一個實例變量。相應地在vcapdlg.h裡需加入一個execute方法的聲明。
另外可以加入一些無關緊要的代碼,如tvcaptiondlg的構造函數中加入成員變量的初始化語句等。至此整個控件的制作完成。完整的控件代碼如下:
//vcapdlg.h
#ifndef vcapdlgh
#define vcapdlgh
#include
#include
#include
#include
class package tvcaptiondlg: public tcomponent
{
private:
ansistring fcaption;
protected:
public:
virtual __fastcall tvcaptiondlg(tcomponent *owner);
virtual void __fastcall execute();
__published:
__property ansistring caption={read=fcaption, write=fcaption};
};
#endif
//vcapdlg.cpp
#include
#pragma hdrstop
#include "vcapdlg.h"
#include "vcap.cpp"
#pragma package(smart_init)
static inline tvcaptiondlg * validctrcheck()
{
return new tvcaptiondlg(null);
}
namespace vcapdlg
{
void __fastcall package register()
{
tcomponentclass classes[1]={__classid(tvcaptiondlg)};
registercomponents("mailuo",classes,0);
}
}
__fastcall tvcaptiondlg::tvcaptiondlg(tcomponent * owner)
:tcomponent(owner)
{
fcaption="mailuo's sample";
}
void __fastcall tvcaptiondlg::execute()
{
vcform=new tvcform(application);
vcform->caption=caption;
vcform->showmodal();
delete vcform;
}
控件的安裝不再贅述。
4 結語
本文旨在演示c++ builder的控件制作和消息處理、sdk等高級編程技術。以上代碼全部在pwin98/c++ builder 3.0上通過調試。順便指出,c++ builder的幫助文檔中的creating custom components講控件制作講得非常好,是學習編寫控件的不可多得的好教程。但其中making a dialogbox a component一篇中有兩處小小瑕疵:一是including the form unit中所講用pragma link vcap.obj的方法是一個相對麻煩的方法,因為需要先將vcap.cpp放入一個無關項目中編譯以生成obj文件(或是用命令行編譯但要指定參數),不如本文一條#include"vcap.cpp"簡單。二是該文檔中沒有指出對這種自己生成的form,有一個限制就是一定要注釋掉#pragma package(smart_init)行,否則安裝時雖可生成包文件但控件不能被裝上。它舉的about對話框的例子中恰好沒有這一句。而用ide產生的form一般都是這一句的。