2013-04-21 45 views
11

有一天,我開始開發我的新項目。應該有一個帶有一些兒童形式的MDI表格。但是當我開始發展的時候,我碰到了一個下面的問題:當主表單變成MDI形式時,它會在內部繪製一個可怕的邊界(斜角)。我不能把它拿走。您可以在截圖中看到這樣的情況:如何去除MDI客戶端窗口的凹陷內邊緣?

http://s18.postimg.org/k3hqpdocp/mdi_problem.png

相反地,一個MDI-子窗體繪製不相同的斜面。

該項目包含兩種形式,Form1和Form2。 Form1是主要的MDI表單。

Form1的源代碼:

object Form1: TForm1 
    Left = 0 
    Top = 0 
    Caption = 'Form1' 
    ClientHeight = 346 
    ClientWidth = 439 
    Color = clBtnFace 
    Font.Charset = DEFAULT_CHARSET 
    Font.Color = clWindowText 
    Font.Height = -11 
    Font.Name = 'Tahoma' 
    Font.Style = [] 
    FormStyle = fsMDIForm 
    OldCreateOrder = False 
    PixelsPerInch = 96 
    TextHeight = 13 
end 

Form2的源代碼:

object Form2: TForm2 
    Left = 0 
    Top = 0 
    Caption = 'Form2' 
    ClientHeight = 202 
    ClientWidth = 331 
    Color = clBtnFace 
    Font.Charset = DEFAULT_CHARSET 
    Font.Color = clWindowText 
    Font.Height = -11 
    Font.Name = 'Tahoma' 
    Font.Style = [] 
    FormStyle = fsMDIChild 
    OldCreateOrder = False 
    Visible = True 
    PixelsPerInch = 96 
    TextHeight = 13 
end 

請告訴我,我怎麼能採取這種傘從主客場形式。

回答

17

繪製邊框是因爲MDI客戶端窗口具有擴展窗口樣式WS_EX_CLIENTEDGE。這種風格描述如下:

窗口有一個邊緣與下沉的邊緣。

但是,我第一次簡單的嘗試刪除該樣式失敗。例如,你可以試試這個代碼:

procedure TMyMDIForm.CreateWnd; 
var 
    ExStyle: DWORD; 
begin 
    inherited; 
    ExStyle := GetWindowLongPtr(ClientHandle, GWL_EXSTYLE); 
    SetWindowLongPtr(ClientHandle, GWL_EXSTYLE, 
    ExStyle and not WS_EX_CLIENTEDGE); 
    SetWindowPos(ClientHandle, 0, 0,0,0,0, SWP_FRAMECHANGED or 
    SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER); 
end; 

此代碼確實移除WS_EX_CLIENTEDGE。但是你看不到任何視覺變化,如果你使用Spy ++這樣的工具檢查窗口,那麼你會看到MDI客戶窗口保留WS_EX_CLIENTEDGE

那麼,什麼給了?事實證明,MDI客戶端窗口的窗口過程(在VCL代碼中實現)強制顯示客戶端邊緣。這會覆蓋您爲刪除樣式所做的任何嘗試。

有問題的代碼如下所示:

procedure ShowMDIClientEdge(ClientHandle: THandle; ShowEdge: Boolean); 
var 
    Style: Longint; 
begin 
    if ClientHandle <> 0 then 
    begin 
    Style := GetWindowLong(ClientHandle, GWL_EXSTYLE); 
    if ShowEdge then 
     if Style and WS_EX_CLIENTEDGE = 0 then 
     Style := Style or WS_EX_CLIENTEDGE 
     else 
     Exit 
    else if Style and WS_EX_CLIENTEDGE <> 0 then 
     Style := Style and not WS_EX_CLIENTEDGE 
    else 
     Exit; 
    SetWindowLong(ClientHandle, GWL_EXSTYLE, Style); 
    SetWindowPos(ClientHandle, 0, 0,0,0,0, SWP_FRAMECHANGED or 
     SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER); 
    end; 
end; 
.... 
procedure TCustomForm.ClientWndProc(var Message: TMessage); 
.... 
begin 
    with Message do 
    case Msg of 
     .... 
     $3F://! 
     begin 
      Default; 
      if FFormStyle = fsMDIForm then 
      ShowMDIClientEdge(ClientHandle, (MDIChildCount = 0) or 
       not MaximizedChildren); 
     end; 

所以,你只需要覆蓋此$3F消息的處理。

是這樣做的:

type 
    TMyMDIForm = class(TForm) 
    protected 
    procedure ClientWndProc(var Message: TMessage); override; 
    end; 

procedure TMyMDIForm.ClientWndProc(var Message: TMessage); 
var 
    ExStyle: DWORD; 
begin 
    case Message.Msg of 
    $3F: 
    begin 
     ExStyle := GetWindowLongPtr(ClientHandle, GWL_EXSTYLE); 
     ExStyle := ExStyle and not WS_EX_CLIENTEDGE; 
     SetWindowLongPtr(ClientHandle, GWL_EXSTYLE, ExStyle); 
     SetWindowPos(ClientHandle, 0, 0,0,0,0, SWP_FRAMECHANGED or 
     SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER); 
    end; 
    else 
    inherited; 
    end; 
end; 

最終的結果看起來是這樣的:

enter image description here

注意,上面的代碼不會調用默認的窗口過程。我不確定這是否會導致其他問題,但其他MDI行爲會受到影響是非常合理的。所以,你可能需要實現一個更強大的行爲補丁。希望這個答案爲您提供所需的知識,使您的應用程序按照您的期望行事。


我想詳細瞭解一下如何實現確保默認的窗口過程被稱爲爲$3F消息,無論該消息恰好是一個全面的解決方案。由於默認窗口過程存儲在專用字段FDefClientProc中,因此實現並不簡單。這使得很難觸及。

我想你可以使用類助手來破解私有成員。但我更喜歡不同的方法。我的做法是按照原樣離開窗口過程,並將VCL代碼所做的調用掛接到SetWindowLong。每當VCL嘗試爲MDI客戶端窗口添加WS_EX_CLIENTEDGE時,掛鉤的代碼都可以阻止該樣式。

實施看起來是這樣的:

type 
    TMyMDIForm = class(TForm) 
    protected 
    procedure CreateWnd; override; 
    end; 

procedure PatchCode(Address: Pointer; const NewCode; Size: Integer); 
var 
    OldProtect: DWORD; 
begin 
    if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then 
    begin 
    Move(NewCode, Address^, Size); 
    FlushInstructionCache(GetCurrentProcess, Address, Size); 
    VirtualProtect(Address, Size, OldProtect, @OldProtect); 
    end; 
end; 

type 
    PInstruction = ^TInstruction; 
    TInstruction = packed record 
    Opcode: Byte; 
    Offset: Integer; 
    end; 

procedure RedirectProcedure(OldAddress, NewAddress: Pointer); 
var 
    NewCode: TInstruction; 
begin 
    NewCode.Opcode := $E9;//jump relative 
    NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode); 
    PatchCode(OldAddress, NewCode, SizeOf(NewCode)); 
end; 

function SetWindowLongPtr(hWnd: HWND; nIndex: Integer; dwNewLong: LONG_PTR): LONG_PTR; stdcall; external user32 name 'SetWindowLongW'; 

function MySetWindowLongPtr(hWnd: HWND; nIndex: Integer; dwNewLong: LONG_PTR): LONG_PTR; stdcall; 
var 
    ClassName: array [0..63] of Char; 
begin 
    if GetClassName(hWnd, ClassName, Length(ClassName))>0 then 
    if (ClassName='MDIClient') and (nIndex=GWL_EXSTYLE) then 
     dwNewLong := dwNewLong and not WS_EX_CLIENTEDGE; 
    Result := SetWindowLongPtr(hWnd, nIndex, dwNewLong); 
end; 

procedure TMyMDIForm.CreateWnd; 
var 
    ExStyle: DWORD; 
begin 
    inherited; 
    // unless we remove WS_EX_CLIENTEDGE here, ShowMDIClientEdge never calls SetWindowLong 
    ExStyle := GetWindowLongPtr(ClientHandle, GWL_EXSTYLE); 
    SetWindowLongPtr(ClientHandle, GWL_EXSTYLE, ExStyle and not WS_EX_CLIENTEDGE); 
end; 

initialization 
    RedirectProcedure(@Winapi.Windows.SetWindowLongPtr, @MySetWindowLongPtr); 

或者如果你喜歡使用的私有成員類助手破解版,看起來像這樣:

type 
    TFormHelper = class helper for TCustomForm 
    function DefClientProc: TFarProc; 
    end; 

function TFormHelper.DefClientProc: TFarProc; 
begin 
    Result := Self.FDefClientProc; 
end; 

type 
    TMyMDIForm = class(TForm) 
    protected 
    procedure ClientWndProc(var Message: TMessage); override; 
    end; 

procedure TMyMDIForm.ClientWndProc(var Message: TMessage); 
var 
    ExStyle: DWORD; 
begin 
    case Message.Msg of 
    $3F: 
    begin 
     Message.Result := CallWindowProc(DefClientProc, ClientHandle, Message.Msg, Message.wParam, Message.lParam); 
     ExStyle := GetWindowLongPtr(ClientHandle, GWL_EXSTYLE); 
     ExStyle := ExStyle and not WS_EX_CLIENTEDGE; 
     SetWindowLongPtr(ClientHandle, GWL_EXSTYLE, ExStyle); 
     SetWindowPos(ClientHandle, 0, 0,0,0,0, SWP_FRAMECHANGED or 
     SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER); 
    end; 
    else 
    inherited; 
    end; 
end; 

最後,我感謝你提出了非常有趣的問題。探索這個問題當然很有趣!

+0

對我來說你確實需要調用默認的窗口過程。 – kobik 2013-04-21 09:37:52

+0

@kobik Ya,正在工作 – 2013-04-21 09:39:32

+1

@kobik好的,我已經釘了它。 – 2013-04-21 09:55:44

2

您可以使用我的開源組件NLDExtraMDIProps(可從here下載),它具有ShowClientEdge屬性。 (代碼類似於David's,儘管我攔截了WM_NCCALCSIZE,而不是$3F)。

除此之外,該組件還具有以下方便MDI屬性:

  • BackgroundPicture:從磁盤,資源,或DFM的圖像在客戶端窗口的中心被塗。
  • CleverMaximizing:通過雙擊它們的標題欄重新排列多個MDI客戶端,因此最大化它到MDI窗體中最大的可用空間。
  • ShowScrollBars:將MDI窗體的滾動條打開或關閉時,拖動客戶端超出MDI窗體擴展。