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:
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!
Scheint mir, Sie müssen die Standardfensterprozedur aufrufen. – kobik
@kobik Ja, arbeite daran –
@ kobik OK, ich habe es jetzt genagelt. –