現在咱繼續在TEdit上做文章,同時來熟悉某些Windows的系統消息。現在我講的是如何實現一個類似QQ編輯框的樣式。
再講解之前,先回饋前幾篇文章有些人提出的問題。通過前幾篇文章,有很多人關注,同時也有人給了一些建議。所以我這裡說明一下,我這個組件編寫的教程指南過程可能不是和書上按部就班一樣的講解,我的著重點是如何編寫一個組件,這個編寫的著重點偏向於實現方式,也就是實現一個組件要涉及到的消息還有 Delphi VCL的一些虛擬方法重載等等,至於有人提到的包的概念,我這裡講解的很粗略,甚至說是沒有講解,其實這個我有小括弧注釋,給了一個cnpack的包的說明文檔,那個講的非常詳細的。至於這個系列的文章講解順序(有人說應該先建立一個包,然後在添加組件實現單元,然後注冊等),而我是先實現了一個組件,然後才建立的包,其實這個都不是問題的,編寫組件的重點是在於組件的實現方式上面,這個包的建立以及注冊到IDE上,只要稍微找找相關資料就能明白了,所以我也就沒詳細說明!另外有人說咋沒講組件編輯器,呵呵,那是因為還不到時候,看看我這個TEdit的擴充哪裡有我自定義的特殊屬性需要自己編寫屬性編輯器哈。所以還請暫時等待等待,總會講到的!
OK,那麼咱們開始正文吧!這次,我們將打造一個類似QQ的TEdit樣式的編輯框。先來看看QQ的編輯框的樣式,首先QQ的編輯框無論在任何系統下,他都是他自己的呈現方式,也就是是平面的,在無皮膚的系統和有皮膚的系統下都是他自己的方式;其次,再來看看,鼠標移動上去的時候,外部多了一層邊框,而且是高亮效果,似乎是顏色模糊化的。好,現在分析清楚了,那麼我們就看看這個的實現需要的一些消息或者處理過程。
第一步,平面效果。Windows系統有幾個消息專門用來處理Windows組件的邊框部位,那就是WM_NCCALCSIZE和WM_NCPAINT這兩個消息,從消息名字看來NC這個就代表著No ClIEnt也就是非客戶區域,NCCALCSIZE也就是說明了計算非客戶區和客戶區的消息,而WM_NCPAINT消息,也就是非客戶區域的繪制觸發消息,所以就要截獲這兩個消息來繪制自己的邊框取代Windows系統的繪制方式。在Delphi中攔截系統消息非常簡單,直接在消息的處理過程後面跟一個message關鍵字,然後加上消息常量就可以了!不像MFC要搞消息映射那麼麻煩,聲明代碼如下
procedure WMNcCalcSize(var msg: TWMNCCalcSize);message WM_NCCALCSIZE;
msg參數為TWMNCCalcSize結構,其實也就是TMessage結構,可以相互轉化的,這個結構為
TWMNCCalcSize = packed record
Msg: Cardinal;
CalcValidRects: BOOL;
CalcSize_Params: PNCCalcSizeParams;
Result: Longint;
end;
這個結構體中msg就是WM_NCCALCSize的消息
CalcValidRects表示是否計算客戶區的有效區域,如果為True,此時CalcSize_Params為一個rgrc: array[0..2] of TRect;的結構體,
rgrc[0]指向的是新的windows的RECT,rgrc[1]是之前的Windows的RECT,rgrc[2]傳入的是move/resize前的clIEnt rect。
如果本值為false的時候,指向的rect和TRUE時的rgrc[0]功能相同
CalcSize_Params是一結構體,這個結構體上面介紹CalcValidRects已經說明,Result指定消息返回值
關於本消息的詳細解釋清參考MSDN或者這個帖子,然而本篇,我們可以不用這個攔截這個消息,因為Delphi的Edit有一個BorderStyle屬性默認為bsSingle也就是是有邊框的,所以我們只用攔截WM_NCPaint這個消息,然後自己處理邊框的繪制就OK了。
第二步,平面效果繪制過程,也不多說了,直接給出代碼更加直觀,下面給出代碼實現過程
procedure TEdit1.WMNCPAINT(var msg: TWMNCPaint);
var
DC: HDC;
BorderBrush: HBRUSH;
R: TRect;
begin
DC := GetWindowDC(Handle);
try
SetRect(R,0,0,Width,Height);
if FMouseIn then
begin
BorderBrush := CreateSolidBrush(RGB(123,228,255));//創建畫刷
FrameRect(Dc, R, BorderBrush);//繪制外部的高亮邊框
DeleteObject(BorderBrush);
InflateRect(R,-1,-1);
end
else
begin
InflateRect(R,-1,-1);
BorderBrush := CreateSolidBrush(ColortoRGB(Color));
FrameRect(Dc, R, BorderBrush);//這個是因為如果鼠標不在上面,就要用本身的顏色填充內部線框
DeleteObject(BorderBrush);
InflateRect(R,1,1);
end;
BorderBrush := CreateSolidBrush(RGB(78,160,209));
FrameRect(Dc, R, BorderBrush);//繪制默認的邊線框
DeleteObject(BorderBrush);
finally
ReleaseDC(Handle,DC)
end;
end;
這個繪制過程就完成了,但是這個還不夠,運行就會發現,此時只有一個平面效果,鼠標移動上去和離開沒有任何效果的,所以此時還要加上鼠標的處理效果,有兩個消息CM_MOUSEENTER和CM_MOUSELEAVE表示鼠標進入控件和鼠標離開控件時候觸發!所以我們攔截這兩個消息,然後鼠標進入的時候改變一個狀態,然後發送一個重新繪制邊線框的消息讓它觸發WM_NCPaint消息,於是我實現一個過程發送邊框重繪消息
procedure TEdit1.InvalidateNC;
begin
if Parent = nil then Exit;
SendMessage(Handle, WM_NCPAINT, 0, 0);
end;
,然後就是兩個鼠標消息的處理了
procedure TEdit1.CMMouseEnter(var msg: TMessage);
begin
inherited;
FMouseIn := True;
InvalidateNC;
end;
procedure TEdit1.CMMouseLeave(var msg: TMessage);
begin
inherited;
FMouseIn := False;
InvalidateNC;
end;
實現過程都很簡單僅僅是發送一個WM_NCPAINT消息而已。