簡體   English   中英

如何刪除 MDI 客戶端窗口的凹陷內邊緣?

[英]How can I remove the sunken inner edge of an MDI client window?

前幾天,我開始開發我的新項目。 應該有一個帶有一些子表單的 MDI 表單。 但是當我開始開發時,我遇到了以下問題:當主窗體變成 MDI 窗體時,它在內部繪制了一個可怕的邊框(斜角)。 我不能把它拿走。 您可以在屏幕截圖中看到這種情況:

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

相反,MDI-Child 窗體繪制時沒有相同的斜角。

該項目包含兩個窗體,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

請告訴我如何將這個斜角從主窗體上移開。

繪制邊框是因為 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;

最終結果如下所示:

在此處輸入圖片說明

請注意,上面的代碼不會調用默認窗口過程。 我不確定這是否會導致其他問題,但其他 MDI 行為很可能會受到影響。 因此,您可能需要實現功能更強大的行為補丁。 希望這個答案為您提供了使您的應用程序按照您希望的方式運行所需的知識。


我更多地考慮如何實現一個全面的解決方案,以確保為$3F消息調用默認窗口過程,無論該消息是什么。 由於默認窗口過程存儲在私有字段FDefClientProc ,因此實現起來並不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;

最后,感謝您提出非常有趣的問題。 探索這個問題當然很有趣!

您可以使用我的開源組件NLDExtraMDIProps (可從此處下載),它有一個ShowClientEdge屬性。 (代碼類似於David 的代碼,雖然我截取的是WM_NCCALCSIZE ,而不是$3F )。

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

  • BackgroundPicture :來自磁盤、資源或 DFM 的圖像,要繪制在客戶端窗口的中心。
  • CleverMaximizing :通過雙擊標題欄重新排列多個 MDI 客戶端,從而將其最大化到 MDI 表單中的最大可用空間。
  • ShowScrollBars :將客戶端拖到 MDI 窗體擴展之外時,打開或關閉 MDI 窗體的滾動條。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM