2013-04-21 9 views
11

Neulich habe ich begonnen, mein neues Projekt zu entwickeln. Es sollte ein MDI-Formular mit einigen untergeordneten Formularen geben. Aber als ich anfing, mich zu entwickeln, stieß ich auf folgendes Problem: Wenn das Hauptformular zur MDI-Form wird, wird es mit einem schrecklichen Rand (Fase) gezeichnet. Und ich kann es nicht wegnehmen. Sie können diese Situation auf dem Screenshot sehen:Wie kann ich den versunkenen inneren Rand eines MDI-Client-Fensters entfernen?

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

Entgegengesetzt, eine MDI-Kind-Form ohne die gleiche Schräge zieht.

Das Projekt enthält zwei Formulare, Form1 und Form2. Form1 ist ein Haupt-MDI-Formular.

Form1 Quellcode:

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 Quellcode:

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 

Bitte, mir sagen, wie kann ich diese Fase nehmen aus der Hauptform entfernt.

Antwort

17

Der Rahmen wird gezeichnet, weil das MDI-Clientfenster den erweiterten Fensterstil WS_EX_CLIENTEDGE aufweist. Dieser Stil wird so beschrieben:

Das Fenster hat eine Grenze mit einer versunkenen Kante.

Allerdings scheiterten meine ersten einfachen Versuche, diesen Stil zu entfernen. Zum Beispiel können Sie diesen Code versuchen:

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; 

Dieser Code in der Tat WS_EX_CLIENTEDGE nicht entfernt. Aber Sie können keine visuelle Änderung sehen, und wenn Sie das Fenster mit einem Tool wie Spy ++ untersuchen, werden Sie sehen, dass das MDI-Client-Fenster WS_EX_CLIENTEDGE behält.

Also, was gibt? Es stellt sich heraus, dass die Fensterprozedur des MDI-Client-Fensters (implementiert in dem VCL-Code) die Client-Kante dazu zwingt, angezeigt zu werden. Dies überschreibt alle Versuche, den Stil zu entfernen.

Der betreffende Code sieht wie folgt aus:

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; 

Also, Sie einfach die Handhabung dieser $3F Nachricht außer Kraft setzen müssen.

Tun Sie das wie folgt aus:

enter image description here

Beachten Sie, dass der Code oben nicht rufen Sie die Standardfensterprozedur:

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; 

Das Endergebnis sieht wie folgt aus. Ich bin mir nicht sicher, ob das andere Probleme verursachen wird, aber es ist sehr plausibel, dass anderes MDI-Verhalten betroffen sein wird. Daher müssen Sie möglicherweise einen leistungsfähigeren Verhaltenspatch implementieren. Hoffentlich gibt Ihnen diese Antwort das Wissen, das Sie benötigen, damit sich Ihre Anwendung so verhält, wie Sie es wünschen.


Ich dachte, ein bisschen mehr darüber, wie eine umfassende Lösung zu implementieren, die die Standardfensterprozedur gewährleistet für die $3F Nachricht aufgerufen wurde, was auch immer diese Botschaft sein geschieht. Es ist nicht trivial zu erreichen, da die Standardfensterprozedur in einem privaten Feld FDefClientProc gespeichert ist. Das macht es ziemlich schwer zu erreichen.

Ich nehme an, Sie könnten einen Klassenhelfer verwenden, um die privaten Mitglieder zu knacken. Aber ich bevorzuge einen anderen Ansatz. Mein Ansatz wäre, die Fensterprozedur genau so zu belassen, wie sie ist, und die Aufrufe des VCL-Codes an SetWindowLong anzuhängen. Immer wenn die VCL versucht, die WS_EX_CLIENTEDGE für ein MDI-Client-Fenster hinzuzufügen, kann der Hook-Code diesen Stil blockieren.

Die Implementierung sieht wie folgt aus:

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); 

Oder wenn Sie bevorzugen die Version, die über ein eigenes Mitglied Klasse Helfer Riss verwendet, die wie folgt aussieht:

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; 

Schließlich I Danke für die sehr interessante Frage. Es hat sicherlich viel Spaß gemacht, dieses Problem zu erforschen!

+0

Scheint mir, Sie müssen die Standardfensterprozedur aufrufen. – kobik

+0

@kobik Ja, arbeite daran –

+1

@ kobik OK, ich habe es jetzt genagelt. –

2

Sie könnten meine Open-Source-Komponente verwenden NLDExtraMDIProps (herunterladbar von here), die eine ShowClientEdge Eigenschaft für genau das hat. (Der Code ist ähnlich dem von David's, obwohl ich Interception WM_NCCALCSIZE bin, anstatt $3F).

Zusätzlich zu, dass das Bauteil auch folgende praktische MDI Eigenschaften:

  • BackgroundPicture: ein Bild von der Festplatte, Ressourcen oder DFM in der Mitte des Client-Fensters gemalt werden.
  • CleverMaximizing: Umordnen mehrerer MDI-Clients durch Doppelklicken auf ihre Titelleisten und damit Maximieren es auf den größten freien Speicherplatz in der MDI-Form.
  • ShowScrollBars: Aktivieren oder deaktivieren Sie die Bildlaufleisten von MDI Form, wenn Sie einen Client über das MDI-Formular hinaus ziehen.