![](/img/trans.png)
[英]How can I display a window from another process in an MDI window in my application
[英]How can I remove the sunken inner edge of an MDI client window?
前幾天,我開始開發我的新項目。 應該有一個帶有一些子表單的 MDI 表單。 但是當我開始開發時,我遇到了以下問題:當主窗體變成 MDI 窗體時,它在內部繪制了一個可怕的邊框(斜角)。 我不能把它拿走。 您可以在屏幕截圖中看到這種情況:
相反,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.