如何使 MessageBox 居中在其父窗体上






4.87/5 (32投票s)
使 MessageBox 居中在其父窗体上
引言
每个人都会使用 MessageBox
- 它一直是 Windows 的一个组成部分,自第一天起,其格式和调用方式几乎没有变化。
对我来说,MessageBox
最大的缺点就是它在屏幕上居中显示,而不是在其父窗体上居中显示,而且没有办法告诉它在其父窗体上居中显示。
这就是我想要的
你可能会认为至少有一个 MessageBox.Show()
的 21 个重载会有一些方法可以做到这一点,或者可能有一个 MessageBoxOptions.CenterOnParent
标志,但没有这样的好运。
本文介绍了一种简单的机制,用于实现显示在其父窗体上居中的 MessageBox
。
背景
我的技术使用了一个自定义类 - 我称之为 MsgBox
- 它用 Windows 钩子包装了标准的 MessageBox.Show
调用。 在弹出 MessageBox
之前设置钩子,hookproc 在 MessageBox
首次显示之前找到并将其居中,然后释放钩子。
为了简单起见,我的示例仅适用于单个线程。 如果你有多个线程以不协调的方式弹出 MessageBox
,你需要添加一些代码来处理这种情况。
对于其他参考资料/文章,CodeProject 上有许多关于钩子的文章。 搜索 "SetWindowsHookEx
"。
Microsoft 的文档可在此链接中找到。
Using the Code
如上所述,核心机制是 Windows 钩子。 对于新手来说,这是一种 Windows 机制,允许你的代码访问一些低级 Windows 功能; 你本质上是将你的应用程序代码注入到 Windows 的内部运作中。
有多种类型的钩子; 我的代码使用 WH_CBT
钩子并作用于 HCBT_ACTIVATE
事件。 (阅读上面链接的 Microsoft 页面以了解 WH_CBT
的详细信息。)
钩子不是 .NET 的一部分。 要使用它们,你必须使用 PInvoke 访问 Win32 钩子 API SetWindowsHookEx()
、CallNextHookEx()
和 UnhookWindowsHookEx()
。 这些设置本地钩子,这意味着它们仅在我们的进程中的窗口上运行。 这正是我们想要的 - 我们不想处理其他应用程序显示的消息框,仅处理我们自己的。
当你设置 WH_CBT
钩子时,你的回调将收到窗口事件的通知,例如创建、激活、移动/调整大小和销毁。 我们对激活感兴趣:当消息框首次激活时(但在它最初可见之前),我们将重新定位它,然后我们就完成了。
下面的代码片段是从附加的示例中提取的。 在其中,你将看到我使用 Win32.*
语法 - 在示例中,我已将所有 P/Invoke 方法和定义收集在一个名为 Win32
的单独类中,这是我为我的项目采用的常见做法。
准备工作
首先,你需要导入钩子 API
using System.Runtime.InteropServices;
public class Win32
{
public const int WH_CBT = 5;
public const int HCBT_ACTIVATE = 5;
public delegate int WindowsHookProc(int nCode, IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook,
WindowsHookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode,
IntPtr wParam, IntPtr lParam);
}
并定义一些变量来管理钩子
private int _hHook = 0;
private Win32.WindowsHookProc _hookProcDelegate;
private static string _title = null;
private static string _msg = null;
设置、处理和释放钩子
创建一个回调委托,然后调用 SetWindowsHookEx()
来设置钩子。 它返回一个钩子 ID,你将在你的回调中使用它,并在你释放钩子时使用它。
// Remember the title & message that we'll look for.
// The hook sees *all* windows, so we need
// to make sure we operate on the right one.
_msg = msg;
_title = title;
Win32.WindowsHookProc hookProcDelegate =
new Win32.WindowsHookProc(HookCallback);
_hHook = Win32.SetWindowsHookEx(Win32.WH_CBT, hookProcDelegate,
IntPtr.Zero, AppDomain.GetCurrentThreadId());
你的钩子回调看起来像这样。 完成处理通知后,你必须通过 CallNextHookEx()
将事件传递给下一个钩子。 (请注意此处使用你的钩子 ID _hHook
。)
private static int HookCallback(int code, IntPtr wParam, IntPtr lParam)
{
if (code == Win32.HCBT_ACTIVATE)
{
// wParam is the handle to the Window being activated.
if(TestForMessageBox(wParam))
{
CenterWindowOnParent(wParam);
Unhook(); // Release hook - we've done what we needed
}
}
return Win32.CallNextHookEx(_hHook, code, wParam, lParam);
}
然后,最后,当你完成查找消息框后,你释放钩子
private static void Unhook()
{
Win32.UnhookWindowsHookEx(_hHook);
_hHook = 0;
_hookProcDelegate = null;
_title = null;
_msg = null;
}
查找你的消息框
只需注意具有正确标题和消息的对话框
private static bool TestForMessageBox(IntPtr hWnd)
{
string cls = Win32.GetClassName(hWnd);
if (cls == "#32770") // MessageBoxes are Dialog boxes
{
string title = Win32.GetWindowText(hWnd);
string msg = Win32.GetDlgItemText(hWnd, 0xFFFF); // -1 aka IDC_STATIC
{
if ((title == _title) && (msg == _msg))
{
return true;
}
}
}
return false;
}
在父窗体上居中显示消息框
在一个窗口上居中显示另一个窗口 - 这里没有什么特别的
private static void CenterWindowOnParent(IntPtr hChildWnd)
{
// Get child (MessageBox) size
Win32.RECT rcChild = new Win32.RECT();
Win32.GetWindowRect(hChildWnd, ref rcChild);
int cxChild = rcChild.right - rcChild.left;
int cyChild = rcChild.bottom - rcChild.top;
// Get parent (Form) size & location
IntPtr hParent = Win32.GetParent(hChildWnd);
Win32.RECT rcParent = new Win32.RECT();
Win32.GetWindowRect(hParent, ref rcParent);
int cxParent = rcParent.right - rcParent.left;
int cyParent = rcParent.bottom - rcParent.top;
// Center the MessageBox on the Form
int x = rcParent.left + (cxParent - cxChild) / 2;
int y = rcParent.top + (cyParent - cyChild) / 2;
uint uFlags = 0x15; // SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE;
Win32.SetWindowPos(hChildWnd, IntPtr.Zero, x, y, 0, 0, uFlags);
}
就是这样!
在你的调用代码中,只需调用 MsgBox.Show()
而不是 MessageBox.Show()
,你将获得父级居中的弹出窗口。
关注点
我经常想知道为什么消息框显示在屏幕上居中,而不是在其父窗体上居中。 我认为这一定有某种方法 - 微软不会让像这样的错误持续 20 年! (好吧,也许他们会的... :) )
在开发此代码时,我找到了一个合理的解释:例如,你的窗口位于显示边界之外,并且发生错误弹出消息框。 在这种情况下,如果消息框在其父级上居中,你将永远看不到它。 我认为这是一个有效的担忧,你在使用居中的消息框时应该考虑它,也许选择不对某些错误消息进行父级居中,以防万一。
历史
- 2010 年 2 月 17 日:初始版本