2014-07-02 166 views
4

也許一個愚蠢的問題,但...如何從WndProc中獲取窗口句柄?

我正在寫一個class應採取保持一個窗口的護理(FGuestHWnd,從現在起)視覺錨定到「主機窗口」(FHostHWnd)。

  • FGuestHWndHostHWnd沒有父母/所有者/子女關係。
  • FGuestHWnd屬於另一個過程 - 不關心。
  • FHostHWnd是VCL TWinControl的窗口句柄,所以這是我的過程中的一個子窗口。它可以坐在父/子樹內的任何級別。例如,假設這是一個TPanel

現在我必須「鉤」FHostHWnd的移動/調整大小,並在我的自定義計算後調用SetWindowPos(FGuestHWnd...。調整大小非常簡單:我可以使用SetWindowLong(FHostHWnd, GWL_WNDPROC, ...)FHostHWnd的WndProc「重定向到」我的自定義WindowPorcedure和陷阱WM_WINDOWPOSCHANGING。由於FHostHWnd是客戶端對齊的,因此此消息會自動發送到FHostHWnd,因爲其中一個祖先的大小已調整。

感動,如果我不缺少的東西,是有點麻煩,因爲如果我移動的主要形式FHostHWnd是不是真的很感動。它保持與其父母相同的位置。所以它不會以任何方式通知祖先的移動。

我的解決辦法是有父的WndProc「重定向」到自定義窗口過程和陷阱WM_WINDOWPOSCHANGING爲「移動」唯一消息。 在這種情況下,我可以用自定義消息通知FHostHWnd。 我班內的一些字段將跟蹤Win Handles,原WndProc addesses和新WndProc地址鏈。

下面是一些代碼來解釋我的結構:

TMyWindowHandler = class(TObject) 
private 
    FHostAncestorHWndList: TList; 
    FHostHWnd: HWND; 
    FGuestHWnd: HWND; 
    FOldHostAncestorWndProcList: TList; 
    FNewHostAncestorWndProcList: TList; 
    //... 
    procedure HookHostAncestorWindows; 
    procedure UnhookHostAncestorWindows; 
    procedure HostAncestorWndProc(var Msg: TMessage); 
end; 

procedure TMyWindowHandler.HookHostAncestorWindows; 
var 
    ParentHWnd: HWND; 
begin 
    ParentHWnd := GetParent(FHostHWnd); 
    while (ParentHWnd > 0) do 
    begin 
    FHostAncestorHWndList.Insert(0, Pointer(ParentHWnd)); 
    FOldHostAncestorWndProcList.Insert(0, TFarProc(GetWindowLong(ParentHWnd,  GWL_WNDPROC))); 
    FNewHostAncestorWndProcList.Insert(0, MakeObjectInstance(HostAncestorWndProc)); 
    Assert(FOldHostAncestorWndProcList.Count = FHostAncestorHWndList.Count); 
    Assert(FNewHostAncestorWndProcList.Count = FHostAncestorHWndList.Count); 
    if (SetWindowLong(ParentHWnd, GWL_WNDPROC, LongInt(FNewHostAncestorWndProcList[0])) = 0) then 
     RaiseLastOSError; 
    ParentHWnd := GetParent(FHostHWnd); 
    end; 
end; 

這裏是處理程序:

procedure TMyWindowHandler.HostAncestorWndProc(var Msg: TMessage); 
var 
    pNew: PWindowPos; 
begin 
    case Msg.Msg of 
    WM_DESTROY: begin 
     UnHookHostAncestorWindows; 
    end; 
    WM_WINDOWPOSCHANGING: begin 
     pNew := PWindowPos(Msg.LParam); 
     // Only if the window moved! 
     if ((pNew.flags and SWP_NOMOVE) = 0) then 
     begin 
     // 
     // Do whatever 
     // 
     end; 
    end; 
    end; 
    Msg.Result := CallWindowProc(???, ???, Msg.Msg, Msg.WParam, Msg.LParam); 
end; 

我的問題是

我怎樣才能得到的窗口句柄從我的WindowProcedure裏面,當我最終調用CallWindowProc? (如果我有窗口句柄,我也可以在FOldHostAncestorWndProcList中找到它,然後在FHostAncestorHWndList中查找右邊的Old-WndProc-pointer) 或者,作爲替代,如何獲取CURRENT方法指針,以便我可以在其中找到它FNewHostAncestorWndProcList並在FHostAncestorHWndList中查找HWND。

或者你提出其他解決辦法?

請注意,我想保留一切HWND爲導向,而不是VCL/TWinControl感知。
換句話說,我的應用程序應該只實例TMyWindowHandler傳遞給它兩個HWND S(主機和客戶)。

回答

5

我個人不會在這裏使用MakeObjectInstanceMakeObjectInstance如果您希望將實例綁定到單個窗口句柄,它非常有用。 MakeObjectInstance的神奇之處在於生成了一個將窗口過程調用轉發給實例方法的thunk。這樣做,窗口句柄不會傳遞給實例方法,因爲假設實例已經知道它的關聯窗口句柄。 TWinControl的情況當然是MakeObjectInstance的主要用例。

現在,您將它綁定到多個窗口句柄。當實例方法執行時,您無法知道許多窗口句柄中的哪一個與此方法執行相關聯。這是你問題的關鍵所在。

我的建議是放棄MakeObjectInstance,因爲它不符合您的需求。取而代之的是,這種形式的平面窗口過程:當你實現這樣的窗口過程

function WindowProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; 
    lParam: LPARAM): LRESULT; stdcall; 

,您就可以獲得一個窗口句柄,你的願望。

您可能需要保留TMyWindowHandler實例的全局列表,以便您可以查找與傳遞給窗口過程的窗口關聯的TMyWindowHandler實例。或者,您可以使用SetProp將某些數據與窗口關聯。

請注意,您對子窗口進行子分類的方式存在各種問題。提供SetWindowSubclass函數是爲了避免這些問題。更多細節在這裏:Subclassing Controls

+0

謝謝大衛。但是'MakeObjectInstance'不是專門爲解決這個問題而存在的嗎?看起來VCL本身綁定了一個'TWinControl'的'WndProc'。 – yankee

+0

我已經擴大了。 'MakeObjectInstance'是你的問題。 'AllocateHwnd'和'TWinControl.Create'使用它來形成窗口和實例之間的一對一關係。您的問題具有多對一的實例關係。所以'MakeObjectInstance'根本不好。 –

+0

好點大衛。謝謝。 – yankee

6

可以將用戶定義的數據傳遞給MakeObjectInstance()。它需要一個閉包作爲輸入,並且閉包可以使用TMethod記錄進行操作,因此您可以將它的Data字段設置爲指向任何您想要的,並且可以通過方法體內的Self指針訪問它。例如:

type 
    PMyWindowHook = ^TMyWindowHook; 
    TMyWindowHook = record 
    Wnd: HWND; 
    OldWndProc: TFarProc; 
    NewWndProc: Pointer; 
    Handler: TMyWindowHandler; 
    end; 

    TMyWindowHandler = class 
    private 
    FHostAncestorHWndList: TList; 
    FHostAncestorWndProcList: TList; 
    FHostHWnd: HWND; 
    FGuestHWnd: HWND; 
    //... 
    procedure HookHostAncestorWindows; 
    procedure UnhookHostAncestorWindows; 
    procedure HostAncestorWndProc(var Msg: TMessage); 
    end; 

procedure TMyWindowHandler.HookHostAncestorWindows; 
var 
    ParentHWnd: HWND; 
    Hook: PMyWindowHook; 
    NewWndProc: Pointer; 
    M: TWndMethod; 
begin 
    ParentHWnd := GetParent(FHostHWnd); 
    while ParentHWnd <> 0 do 
    begin 
    M := HostAncestorWndProc; 
    New(Hook); 
    try 
     TMethod(M).Data := Hook; 
     Hook.Hwnd := ParentHWnd; 
     Hook.OldWndProc := TFarProc(GetWindowLong(ParentHWnd, GWL_WNDPROC)); 
     Hook.NewWndProc := MakeObjectInstance(M); 
     Hook.Handler := Self; 
     FHostAncestorWndProcList.Insert(0, Hook); 
     try 
     SetLastError(0); 
     if SetWindowLongPtr(ParentHWnd, GWL_WNDPROC, LONG_PTR(Hook.NewWndProc)) = 0 then 
     begin 
      if GetLastError() <> 0 then 
      begin 
      FreeObjectInstance(Hook.NewWndProc); 
      RaiseLastOSError; 
      end; 
     end; 
     except 
     FHostAncestorWndProcList.Delete(0); 
     raise; 
     end; 
    except 
     Dispose(Hook); 
     raise; 
    end; 
    ParentHWnd := GetParent(ParentHWnd); 
    end; 
end; 

procedure TMyWindowHandler.UnhookHostAncestorWindows; 
var 
    Hook: PMyWindowHook; 
begin 
    while FHostAncestorWndProcList.Count > 0 
    begin 
    Hook := PMyWindowHook(FHostAncestorWndProcList.Items[0]); 
    FHostAncestorWndProcList.Delete(0); 
    SetWindowLongPtr(Hook.Hwnd, GWL_WNDPROC, LONG_PTR(Hook.OldWndProc)); 
    FreeObjectInstance(Hook.NewWndProc); 
    Dispose(Hook); 
    end; 
end; 

procedure TMyWindowHandler.HostAncestorWndProc(var Msg: TMessage); 
var 
    Hook: PMyWindowHook; 
    pNew: PWindowPos; 
begin 
    Hook := PMyWindowHook(Self); 
    case Msg.Msg of 
    WM_DESTROY: begin 
     Msg.Result := CallWindowProc(Hook.Wnd, Hook.OldWndProc, Msg.Msg, Msg.WParam, Msg.LParam); 
     Hook.Handler.FHostAncestorWndProcList.Remove(Hook); 
     SetWindowLongPtr(Hook.Hwnd, GWL_WNDPROC, LONG_PTR(Hook.OldWndProc)); 
     FreeObjectInstance(Hook.NewWndProc); 
     Dispose(Hook); 
     Exit; 
    end; 
    WM_WINDOWPOSCHANGING: begin 
     pNew := PWindowPos(Msg.LParam); 
     // Only if the window moved! 
     if (pNew.flags and SWP_NOMOVE) = 0 then 
     begin 
     // 
     // Do whatever 
     // 
     end; 
    end; 
    end; 
    Msg.Result := CallWindowProc(Hook.Wnd, Hook.OldWndProc, Msg.Msg, Msg.WParam, Msg.LParam); 
end; 

當然,這不是一個理想的設置。 SetWindowSubClass()將是一個比SetWindowLong(GWL_WNDPROC)更好的選擇。鉤子程序爲您提供了HWND,您可以指定用戶定義的數據。不需要黑客。例如:

type 
    TMyWindowHandler = class 
    private 
    FHostAncestorHWndList: TList; 
    FHostAncestorWndProcList: TList; 
    FHostHWnd: HWND; 
    FGuestHWnd: HWND; 
    //... 
    procedure HookHostAncestorWindows; 
    procedure UnhookHostAncestorWindows; 
    class function HostAncestorWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData): LRESULT; stdcall; static; 
    end; 

procedure TMyWindowHandler.HookHostAncestorWindows; 
var 
    ParentHWnd: HWND; 
begin 
    ParentHWnd := GetParent(FHostHWnd); 
    while ParentHWnd <> 0 do 
    begin 
    FHostAncestorWndProcList.Insert(0, Pointer(ParentWnd)); 
    try 
     if not SetWindowSubclass(ParentWnd, @HostAncestorWndProc, 1, DWORD_PTR(Self)) then 
     RaiseLastOSError; 
    except 
     FHostAncestorWndProcList.Delete(0); 
     raise; 
    end; 
    ParentHWnd := GetParent(ParentHWnd); 
    end; 
end; 

procedure TMyWindowHandler.UnhookHostAncestorWindows; 
begin 
    while FHostAncestorWndProcList.Count > 0 do 
    begin 
    RemoveWindowSubclass(HWND(FHostAncestorWndProcList.Items[0]), @HostAncestorWndProc, 1); 
    FHostAncestorWndProcList.Delete(0); 
    end; 
end; 

class function TMyWindowHandler.HostAncestorWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData): LRESULT; stdcall; 
var 
    pNew: PWindowPos; 
begin 
    case uMsg of 
    WM_NCDESTROY: begin 
     RemoveWindowSubclass(hWnd, @HostAncestorWndProc, 1); 
     TMyWindowHandler(dwRefData).FHostAncestorWndProcList.Remove(Pointer(hWnd)); 
    end; 
    WM_WINDOWPOSCHANGING: begin 
     pNew := PWindowPos(Msg.LParam); 
     // Only if the window moved! 
     if (pNew.flags and SWP_NOMOVE) = 0 then 
     begin 
     // 
     // Do whatever 
     // 
     end; 
    end; 
    end; 
    Result := DefSubclassProc(hWnd, uMsg, wParam, lParam); 
end; 
+1

+1關於'MakeObjectInstance'的有趣信息。 我結束了使用'SetWindowSubclass'。更乾淨。 – yankee

+0

儘管如此,這是一個寶貴的例子(在Delphi中)如何使用'SetWindowSubclass'。 –