众所周知,Delphi 的 MessageBox 消息框是封装的 Win32 函数。其函数原型为:
MessageBox(
HWND
hWnd,
LPCTSTR
lpText,
LPCTSTR
lpCaption,
UINT
uType);
其中各参数想必大家都很熟悉,在此不再赘述。主要谈谈 HWND
。按官方描述:此参数代表消息框拥有的窗口。如果为NULL,则消息框没有拥有窗口。按理说消息框就应该显示在 HWND 窗口中,但直至 Windows 11,无论 HWND 如何,消息框都稳占桌面中央。对于应用程序而言,我们希望这个消息框能显示在应用程序的主窗口正中,甚至派生窗口的正中。也就是说真正显示在 HWND 窗口正中。
这个问题,网上已有人给出过解决方案。但有的只给出解决思路,有的虽然给出了解决方案,但用了Delphi最新版本才有的功能(如TThread.ForceQueue)。因此,我用最基本的Delphi方法给出一个实用的解决方案。作为实用的方案,考虑到了 MessageBox 不能移出屏幕边界、HNWD 窗口不存在即显示在桌面中央等实际情况。
解决的原理很简单,就是想法将 MessageBox 移动到指定的位置。但 Windows 系统的 MessageBox 是一个模态对话框,也就是说直至用户响应才执行下一条程序指令,移动窗口的指令不可能在同一线程,必须开启另一个线程。这个线程在 MessageBox 弹出前建立且运行,等待 MessageBox 弹出后通过查找其窗口标题对应的窗口句柄(我的实验是不能通过顶层窗口或活动窗口查找 MessageBox),将其移动到指定位置后结束线程。这样就实现了将 MessageBox 显示在 HWND 窗口中央。具体程序如下,这段程序可放在 implementation 后的任何地方,只要在调用它的程序前就行:
- implementation
- {$R *.dfm}
- type
- TMyThread = class(TThread)
- protected
- procedure Execute; override;
- end;
- var
- hMain: HWND;
- MsgTitle: string;
- procedure TMyThread.Execute;
- var
- mR, pR, sR: TRect;
- X, Y: Integer;
- hMsg: HWND;
- begin
- FreeOnTerminate := True; { 这可以让线程执行完毕后随即释放 }
- sleep(10); // 等消息框建立
- hMsg := FindWindow(nil, PChar(MsgTitle));
- GetWindowRect(hMsg, mR); // 取消息框窗口矩形位置大小
- GetWindowRect(hMain, pR); // 取 HWND 窗口矩形位置大小
- GetWindowRect(GetDesktopWindow, sR); // 取屏幕桌面矩形位置大小
- X := pR.Left + (pR.Width - mR.Width) div 2;
- Y := pR.Top + (pR.Height - mR.Height) div 2;
- if X < 0 then
- X := 0;
- if X > sR.Width - mR.Width then
- X := sR.Width - mR.Width;
- if Y < 0 then
- Y := 0;
- if Y > sR.Height - mR.Height then
- Y := sR.Height - mR.Height;
- SetWindowPos(hMsg, HWND_TOP, X, Y, 0, 0, SWP_NOSIZE or SWP_SHOWWINDOW or
- SWP_NOOWNERZORDER);
- end;
- // 主函数。在HWND中央显示消息框
- function MsgBox(const HWND: HWND; const msg, title: string;
- const mbType: WORD): WORD;
- begin
- hMain := HWND;
- if not IsWindow(hMain) then
- hMain := GetDesktopWindow;
- if title <> '' then
- MsgTitle := title
- else
- MsgTitle := #32;
- TMyThread.Create(False);
- result := MessageBox(HWND, PChar(msg), PChar(MsgTitle), mbType + MB_TOPMOST);
- end;
新的消息函数名为 MsgBox ,调用它与 MessageBox 完全相同。例如:
- procedure TForm3.Button1Click(Sender: TObject);
- begin
- if MsgBox(handle, '消息内容', '消息标题', MB_YESNO) = mrYes then
- close;
- end;
运行后,MessageBox 显示在 Form3 窗口的中央而不是桌面中央(要显示桌面在中央,HWND指定为0即可)。按“是(Y)”将关闭 Form3: