Delphi中編寫OwnerDraw方式按鈕的方法以及注意點

來源:互聯網
上載者:User

標籤:des   style   color   io   使用   ar   for   sp   問題   

{在Win32標準控制項陳列庫中(comctl32.dll)的控制項絕大部分的控制項都支援自畫(OwnerDraw)功能的
設定,這裡以按鈕為例子說說此類控制項在delphi中的編寫方式以及應該注意的問題!由於標
準的按鈕都有一套規定的外觀標準(由windows的comctl32.dll中的代碼繪製),為了給使用者
一個自己繪製控制項的機會,多數的控制項都支援在建立的時候設定一個標誌來告訴系統這個控
件需要自己繪製,例如按鈕中有:BS_OWNERDRAW,菜單的有:MF_OWNERDRAW等等(還有諸如:
ComboBox,ListBox都有這方面的特性),在delphi中設定這個標誌不需要按Win32的方式那樣
在視窗類別中定義,只要在CreateParams方法中處理就可以了,雖然內部實現方式是一樣的,
但畢竟我們寫起來方便許多,不是嗎?當你在視窗類別中的Style中設定了這些旗標的話,系統
會在控制項需要繪製的時候先發送一個WM_MEASUREITEM訊息給當此控制項的父表單(注意這裡要
注意,這是由於標準Win32開發方式決定的,由於原來大多數的控制項都是在接收到主表單的W
M_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_MEASUREITE
M的訊息處理過程是一樣的,組件中應該截取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^.itemHeigh
t := 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_VCENT
ER 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;

procedure TSundyButton.CMEnabledChanged(var Message: TMessage);
begin
inherited;
Invalidate;
end;

procedure TSundyButton.CMFontChanged(var Message: TMessage);
begin
inherited;
Invalidate;
end;

//這個方法要改寫,且不能調用inherited去調用上層方法
procedure TSundyButton.SetButtonStyle(ADefault: Boolean);
begin
if (ADefault <> IsFocused) then
begin
IsFocused := ADefault;
Invalidate;
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.你應該在CreateParam
s中設定好相應的屬性,如:BS_OWNERDRAW,SS_OWNERDRAW等等(設定方式可以看原始碼相應
部分)! 3.應該注意delphi中的一些預設的處理是否會改變組件的行為,如果會的話,你應
該改寫它或者通過其他方式避免它,如此例中按鈕的SetButtonStyle方法無論在何時都會將
我們設定好的屬性改變為預設屬性,所以要改寫它(幸好它是個虛方法,給了我們一次機會
^_^)}!

Delphi中編寫OwnerDraw方式按鈕的方法以及注意點

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.