程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 深入VCL理解BCB的消息機制2

深入VCL理解BCB的消息機制2

編輯:關於C++

重載TControl的WndProc方法

還是先談談VCL的繼承策略。VCL中的繼承鏈的頂部是TObject基類。一切的VCL組件和對象都繼承自TObject。

打開BCB幫助查看TControl的繼承關系:

TObject->TPersistent->TComponent->TControl

呵呵,原來TControl是從TPersistent類的子類TComponent類繼承而來的。TPersistent抽象基類具有使用流stream來存取類的屬性的能力。

TComponent類則是所有VCL組件的父類。

這就是所有的VCL組件包括您的自定義組件可以使用dfm文件存取屬性的原因『當然要是TPersistent的子類,我想您很少需要直接從TObject類來派生您的自定義組件吧』。

TControl類的重要性並不亞於它的父類們。在BCB的繼承關系中,TControl類的是所有VCL可視化組件的父類。實際上就是控件的意思吧。所謂可視化是指您可以在運行期間看到和操縱的控件。這類控件所具有的一些基本屬性和方法都在TControl類中進行定義。

TControl的實現在\Borland\CBuilder5\Source\Vcl\control.pas中可以找到。『可能會有朋友問你怎麼知道在那裡?使用BCB提供的Search -> Find in files很容易找到。或者使用第三方插件的grep功能。』

好了,進入VCL的源碼吧。說到這裡免不了要抱怨一下Borland。哎,為什麼要用pascal實現這一切.....:-(

TControl繼承但並沒有重寫TObject的Dispatch()方法。反而提供了一個新的方法就是xycleo提到的WndProc()。一起來看看Borland的工程師們是怎麼寫的吧。

procedure TControl.WndProc(var Message: TMessage);
var
  Form: TCustomForm;
begin
//由擁有control的窗體來處理設計期間的消息
  if (csDesigning in ComponentState) then
  begin
   Form := GetParentForm(Self);
   if (Form <> nil) and (Form.Designer <> nil) and
    Form.Designer.IsDesignMsg(Self, Message) then Exit;
  end
//如果需要,鍵盤消息交由擁有control的窗體來處理
  else if (Message.Msg >= WM_KEYFIRST) and (Message.Msg <= WM_KEYLAST) then
  begin
   Form := GetParentForm(Self);
   if (Form <> nil) and Form.WantChildKey(Self, Message) then Exit;
  end
//處理鼠標消息
  else if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then
  begin
   if not (csDoubleClicks in ControlStyle) then
    case Message.Msg of
     WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK:
      Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
    end;
   case Message.Msg of
    WM_MOUSEMOVE: Application.HintMouseMessage(Self, Message);
    WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:
     begin
      if FDragMode = dmAutomatic then
      begin
       BeginAutoDrag;
       Exit;
      end;
      Include(FControlState, csLButtonDown);
     end;
    WM_LBUTTONUP:
     Exclude(FControlState, csLButtonDown);
   end;
  end
// 下面一行有點特別。如果您仔細的話會看到這個消息是CM_VISIBLECHANGED.
// 而不是我們熟悉的WM_開頭的標准Windows消息.
// 盡管Borland沒有在它的幫助中提到有這一類的CM消息存在。但很顯然這是BCB的
// 自定義消息。呵呵,如果您對此有興趣可以在VCL源碼中查找相關的內容。一定會有不小的收獲。
  else if Message.Msg = CM_VISIBLECHANGED then
   with Message do
    SendDockNotification(Msg, WParam, LParam);
// 最後調用dispatch方法。
  Dispatch(Message);
end;

看完這段代碼,你會發現TControl類實際上只處理了鼠標消息,沒有處理的消息最後都轉入Dispatch()來處理。

但這裡需要強調指出的是TControl自己並沒有獲得焦點Focus的能力。TControl的子類TWinControl才具有這樣的能力。我憑什麼這樣講?呵呵,還是打開BCB的幫助。很多朋友抱怨BCB的幫助實在不如VC的MSDN。毋庸諱言,的確差遠了。而且這個幫助還經常有問題。但有總比沒有好啊。

言歸正傳,在幫助的The TWinControl Branch 分支下,您可以看到關於TWinControl類的簡介。指出TWinControl類是所有窗體類控件的基類。所謂窗體類控件指的是這樣一類控件:

1. 可以在程序運行時取得焦點的控件。

2. 其他的控件可以顯示數據,但只有窗體類控件才能和用戶發生鍵盤交互。

3. 窗體類控件能夠包含其他控件(容器)。

4. 包含其他控件的控件又稱做父控件。只有窗體類控件才能夠作為其他控件的父控件。

5. 窗體類控件擁有句柄。

除了能夠接受焦點之外,TWinControl的一切都跟TControl沒什麼分別。這一點意味著TwinControl可以對許多的標准事件作出響應,Windows也必須為它分配一個句柄。並且與這個主題相關的最重要的是,這裡提到是由BCB負責來對控件進行重畫以及消息處理。這就是說,TwinControl封裝了這一切。

似乎扯的太遠了。但我要提出來的問題是TControl類的WndProc方法中處理了鼠標消息。但這個消息只有它的子類TwinControl才能夠得到啊!?

這怎麼可以呢... Borland是如何實現這一切的呢?這個問題實在很奧妙。為了看個究竟,再次深入VCL吧。

還是在control.pas中,TWinControl繼承了TControl的WndProc方法。源碼如下:

procedure TWinControl.WndProc(var Message: TMessage);
var
  Form: TCustomForm;
  KeyState: TKeyboardState;
  WheelMsg: TCMMouseWheel;
begin
  case Message.Msg of
   WM_SETFOCUS:
    begin
     Form := GetParentForm(Self);
     if (Form <> nil) and not Form.SetFocusedControl(Self) then Exit;
    end;
   WM_KILLFOCUS:
    if csFocusing in ControlState then Exit;
   WM_NCHITTEST:
    begin
     inherited WndProc(Message);
     if (Message.Result = HTTRANSPARENT) and (ControlAtPos(ScreenToClient(
      SmallPointToPoint(TWMNCHitTest(Message).Pos)), False) <> nil) then
      Message.Result := HTCLIENT;
     Exit;
    end;
   WM_MOUSEFIRST..WM_MOUSELAST:
    //下面這一句話指出,鼠標消息實際上轉入IsControlMouseMsg方法來處理了。
    if IsControlMouseMsg(TWMMouse(Message)) then
    begin
     if Message.Result = 0 then
      DefWindowProc(Handle, Message.Msg, Message.wParam, Message.lParam);
     Exit;
    end;
   WM_KEYFIRST..WM_KEYLAST:
    if Dragging then Exit;
   WM_CANCELMODE:
    if (GetCapture = Handle) and (CaptureControl <> nil) and
     (CaptureControl.Parent = Self) then
     CaptureControl.Perform(WM_CANCELMODE, 0, 0);
  else
   with Mouse do
    if WheelPresent and (RegWheelMessage <> 0) and
     (Message.Msg = RegWheelMessage) then
    begin
     GetKeyboardState(KeyState);
     with WheelMsg do
     begin
      Msg := Message.Msg;
      ShiftState := KeyboardStateToShiftState(KeyState);
      WheelDelta := Message.WParam;
      Pos := TSmallPoint(Message.LParam);
     end;
     MouseWheelHandler(TMessage(WheelMsg));
     Exit;
    end;
  end;
  inherited WndProc(Message);
end;

鼠標消息是由IsControlMouseMsg方法來處理的。只有再跟到IsControlMouseMsg去看看啦。源碼如下:

function TWinControl.IsControlMouseMsg(var Message: TWMMouse): Boolean;
var
  //TControl出現啦
  Control: TControl;
  P: TPoint;
begin
  if GetCapture = Handle then
  begin
   Control := nil;
   if (CaptureControl <> nil) and (CaptureControl.Parent = Self) then
    Control := CaptureControl;
  end else
   Control := ControlAtPos(SmallPointToPoint(Message.Pos), False);
  Result := False;
  if Control <> nil then
  begin
   P.X := Message.XPos - Control.Left;
   P.Y := Message.YPos - Control.Top;
   file://TControl的Perform方法將消息交由WndProc處理。
   Message.Result := Control.Perform(Message.Msg, Message.Keys, Longint(PointToSmallPoint(P)));
   Result := True;
  end;
end;

原來如此,TWinControl最後還是將鼠標消息交給TControl的WndProc來處理了。這裡出現的Perform方法在BCB的幫助裡可以查到是TControl類中開始出現的方法。它的作用就是將指定的消息傳遞給TControl的WndProc過程。

結論就是TControl類的WndProc方法的消息是由TwinControl類在其重載的WndProc方法中調用IsControlMouseMsg方法後使用Peform方法傳遞得到的。

由於這個原因,BCB和Delphi中的TControl類及其所有的派生類都有一個先天的而且是必須的限制。那就是所有的TControl類及其派生類的Owner必須是TwinControl類或者TWinControl的派生類。Owner屬性最早可以在TComponent中找到,一個組件或者控件是由它的Owner擁有並負責釋放其內存的。這就是說,當Owner從內存中釋放的時候,它所擁有的所有控件占用的內存也都被釋放了。Owner最好的例子就是Form。Owner同時也負責消息的分派,當Owner接收到消息的時候,它負責將應該傳遞給其所擁有的控件的消息傳遞給它們。這樣這些控件就能夠取得處理消息的能力。TImage就是個例子:你可以發現Borland並沒有讓TImage重載TControl的WndProc方法,所以TImage也只有處理鼠標消息的能力,而這種能力正是來自TControl的。

唧唧崴崴的說了一大堆。終於可以說處理消息的第二種方法就是重載TControl的WndProc方法了。例程如下:

void __fastcall TForm1::WndProc(TMessage &Message)
{
    switch (Message.Msg)
    {
       case WM_CLOSE:
          OnCLOSE(Message); // 處理WM_CLOSE消息的方法
       break;
    }
    TForm::WndProc(Message);
}

乍看起來,這和上次講的重載Dispatch方法好象差不多。但實際上還是有差別的。差別就在先後次序上,從前面TControl的WndProc可以看到,消息是先交給WndProc來處理,最後才調用Dispatch方法的啦。

這樣,重載WndProc方法可以比重載Dispatch方法更早一點點得到消息並處理消息。

好了,這次就說到這裡。在您的應用程序裡還有沒有比這更早得到消息的辦法呢?有,下次再說。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved