在Win32標准控件庫中(comctl32.dll)的控件絕大部分的控件都支持自畫(OwnerDraw)功能的設置,這裡以按鈕為例子說說此類控件在delphi中的編寫方式以及應該注意的問題!由於標准的按鈕都有一套規定的外觀標准(由Windows的comctl32.dll中的代碼繪制),為了給用戶一個自己繪制控件的機會,多數的控件都支持在創建的時候設置一個標志來告訴系統這個控件需要自己繪制,例如按鈕中有:BS_OWNERDRAW,菜單的有:MF_OWNERDRAW等等(還有諸如:ComboBox,ListBox都有這方面的特性),在delphi中設置這個標志不需要按Win32的方式那樣在窗口類中定義,只要在CreateParams方法中處理就可以了,雖然內部實現方式是一樣的,但畢竟我們寫起來方便許多,不是嗎?當你在窗口類中的Style中設置了這些旗標的話,系統會在控件需要繪制的時候先發送一個WM_MEASUREITEM消息給當此控件的父窗體(注意這裡要注意,這是由於標准Win32開發方式決定的,由於原來大多數的控件都是在接收到主窗體的WM_CREATE消息時候創建的,一個窗口過程是當時程序員可以編寫代碼唯一的機會,所有的消息都發送到主線程的消息循環中,所以控件的消息自然發到這裡來了!可沒有這麼頻繁使用子類化或者超類化的方式)來確定控件的繪制范圍,然後接著發送WM_DRAWITEM給此控件的父窗體,而我們要做一個獨立的組件,它怎麼知道什麼時候該繪制呢?代碼本來應該寫在窗體中才對啊!好在delphi在庫中意見考慮到這個需求,只要你的控件是在Delphi中使用的,那麼TForm窗體會將所有接收的消息發送給相應的窗口過程處理,TWinControl.WMDrawItem相關代碼如下(經過處理):
procedure TWinControl.WMDrawItem(var Message: TWMDrawItem);
begin if not DoControlMsg(Message.DrawItemStruct^.CtlID, Message) then iherited;
end;
而DoControlMsg的實現很簡單:
function DoControlMsg(ControlHandle: HWnd; var Message): Boolean;
var Control: TWinControl;
begin DoControlMsg := False;
Control := FindControl(ControlHandle);
if Control <> nil then with TMessage(Message) do begin Result := Control.Perform(Msg + CN_BASE, WParam, LParam);
DoControlMsg := True;
end;
end;
找到控件後將該消息的標識加上CN_BASE發送給相應窗口就是了(CN_DRAWITEM=CN_BASE+WM_DRAWITEM),所以這裡是第二個注意點:在組件中截獲WM_DRAWITEM消息是沒有效果的,事實上根本沒有這個消息傳送到組件的窗口過程,而應該截獲的是CN_DRAWITEM,WM_MEASUREITEM的消息處理過程是一樣的,組件中應該截取CN_MEASUREITEM消息!注意了這些我們就可以由TButton來派生一個組件了,代碼如下:
TSundyButton = class(TButton) private FCanvas: TCanvas;
//你可以在下面的方法中按你喜歡的方式畫出各種效果,這裡只是簡單的示例,沒有什麼 //炫目的效果^_^
IsFocused: Boolean;
IsDown: boolean;
protected procedure WMMOUSEDOWN(var message: TWMLButtonDown);
message WM_LButtonDown;
procedure WMMOUSEUP(var message: TWMLButtonUp);
message WM_LButtonUp;
procedure CNDRAWITEM(var message: TWMDRAWITEM);
message CN_DRAWITEM;
procedure CNMEASUREITEM(var message: TWMMEASUREITEM);
message CN_MEASUREITEM;
procedure CreateParams(var Params: TCreateParams);
override;
procedure CMEnabledChanged(var Message: TMessage);
message CM_ENABLEDCHANGED;
procedure CMFontChanged(var Message: TMessage);
message CM_FONTCHANGED;
public constructor Create(AOwner: TComponent);
override;
destructor Destroy;
override;
procedure SetButtonStyle(ADefault: Boolean);
override;
procedure DrawItem(const DrawItemStruct: TDrawItemStruct);
end;
implementation
procedure TSundyButton.CNDRAWITEM(var message: TWMDRAWITEM);
begin DrawItem(message.DrawItemStruct^);
end;
procedure TSundyButton.CNMEASUREITEM(var message: TWMMEASUREITEM);
begin message.MeasureItemStruct^.itemWidth := Width;
message.MeasureItemStruct^.itemHeight := Height;
end;
constructor TSundyButton.Create(AOwner: TComponent);
begin inherited Create(AOwner);
ControlStyle := ControlStyle + [csReflector];
FCanvas := TCanvas.Create;
Width := 85;
Height := 30;
end;
procedure TSundyButton.CreateParams(var Params: TCreateParams);
begin inherited CreateParams(Params);
with Params do Style := Style or BS_OWNERDRAW;
end;
destructor TSundyButton.Destroy;
begin FreeAndNil(FCanvas);
inherited Destroy;
end;
procedure TSundyButton.DrawItem(const DrawItemStruct: TDrawItemStruct);
var Rec: TRect;
begin FCanvas.Handle := DrawItemStruct.hDC;
Rec := ClIEntRect; with FCanvas do begin Pen.Style := psSolid; Brush := Parent.Brush;
RoundRect(Rec.Left, Rec.Top, Rec.Right, Rec.Bottom, 4, 4);
Brush.Color := clBtnFace;
Pen.Color := clBlack;
SetBkMode(FCanvas.Handle, Transparent);
if IsDown then begin Rec.Left := Rec.Left + 3;
Rec.Top := Rec.Top + 1; Font.Color := clBlack;
DrawText(Handle, PChar(Caption), Length(Caption), Rec, DT_CENTER or DT_VCENTER or DT_SINGLELINE);
end else if not IsDown then begin Font.Color := clRed;
DrawText(Handle, PChar(Caption), Length(Caption), Rec, DT_CENTER or DT_VCENTER or DT_SINGLELINE);
end;
if Enabled then begin if IsFocused then DrawFocusRect(Rec);
end;
end;
FCanvas.Handle := 0;
end;
計算機教程Delphi中編寫OwnerDraw方式按鈕的方法以及注意點來自www.itwen.comIT WEN計算機教程網procedure TSundyButton.CMEnabledChanged(var Message: TMessage);
//這個方法要改寫,且不能調用inherited去調用上層方法 procedure TSundyButton.SetButtonStyle(ADefault: Boolean); begin if (ADefault <> IsFocused) then begin IsFocused := ADefault; Invalidate;
begin inherited;
Invalidate;
end;
procedure TSundyButton.CMFontChanged(var Message: TMessage);
begin inherited;
Invalidate;
end;
end;
end;
procedure TSundyButton.WMMOUSEDOWN(var message: TWMLBUTTONDOWN); begin inherited; IsDown := True; Invalidate; end;
procedure WMMOUSEUP(var message: TWMLButtonUp);begin inherited; IsDown := False; Invalidate; end;
這裡還有一個小的問題不知道大家做按鈕組件的時候發現沒有,我開始做的按鈕完全注意到上述的兩點,但效果出來後一點擊按鈕,按鈕又恢復了原來的樣子,為了找到這個原因,我試著跟蹤它,發現只是在開始的時候CN_DRAWITEM會發送到我的組件窗口,當按鈕或者焦點後就再也沒有這個消息發送過來了,經過很久的尋找終於在TButton的CM_FOCUSCHANGED消息中發現了原因:
procedure TButton.CMFocusChanged(var Message: TCMFocusChanged);
begin with Message do if Sender is TButton then FActive := Sender = Self else FActive := FDefault; SetButtonStyle(FActive);
inherited;
end;
而SetButtonStyle(FActive)在或者焦點的時候會執行
SetButtonStyle(True);
procedure TButton.SetButtonStyle(ADefault: Boolean);
const BS_MASK = $000F;
var Style: Word;
begin if HandleAllocated then begin if ADefault then Style := BS_DEFPUSHBUTTON else Style := BS_PUSHBUTTON; if GetWindowLong(Handle, GWL_STYLE) and BS_MASK <> Style then SendMessage(Handle, BM_SETSTYLE, Style, 1);
end;
end;
當我的ADefault為True或者False的時候,Style就變成標准的按鈕樣式了,所以這個方法我們一定要改寫並且不能在組件中調用上層的方法!這裡簡單總結一下此類組件開發的3個注意點: 1.自畫消息是系統發送到父窗口的,所以不能在組件的窗口過程中去截獲這些消息,如果是使用delphi開發的話,你可以在組件中截獲相應的CN_BASE+WM_XXXXX消息,因為父窗口對此類消息一般不處理,只是改變一下然後交給組件本身來處理! 2.你應該在CreateParams中設置好相應的屬性,如:BS_OWNERDRAW,SS_OWNERDRAW等等(設置方式可以看源代碼相應部分)! 3.應該注意Delphi中的一些默認的處理是否會改變組件的行為,如果會的話,你應該改寫它或者通過其他方式避免它,如此例中按鈕的SetButtonStyle方法無論在何時都會將我們設置好的屬性改變為默認屬性,所以要改寫它(幸好它是個虛方法,給了我們一次機會^_^)!