Winforms-如何使MessageBox出现在主窗体的中心?

59

Winforms-如何使对话框出现在主窗体的中心?而不是基于普通窗口默认方式,该方式将它们呈现在屏幕中央。

在我的情况下,我有一个小的主窗体,可能被放置在一个角落,当MessageBox弹出时,它似乎离得很远。


1
using(NewFormDialog newDialog = new NewFormDialog()) { newDialog.StartPosition = FormStartPosition.CenterParent; newDialog.ShowDialog(); }; - uSeRnAmEhAhAhAhAhA
@uSeRnAmEhAhAhAhAhA 什么是NewFormDialog? - still_dreaming_1
6个回答

88

通过一些 P/Invoke 的用法和 Control.BeginInvoke() 提供的魔力,这是可能的。在您的项目中添加一个新类并粘贴以下代码:

using System;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class CenterWinDialog : IDisposable {
    private int mTries = 0;
    private Form mOwner;

    public CenterWinDialog(Form owner) {
        mOwner = owner;
        owner.BeginInvoke(new MethodInvoker(findDialog));
    }

    private void findDialog() {
        // Enumerate windows to find the message box
        if (mTries < 0) return;
        EnumThreadWndProc callback = new EnumThreadWndProc(checkWindow);
        if (EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero)) {
            if (++mTries < 10) mOwner.BeginInvoke(new MethodInvoker(findDialog));
        }
    }
    private bool checkWindow(IntPtr hWnd, IntPtr lp) {
        // Checks if <hWnd> is a dialog
        StringBuilder sb = new StringBuilder(260);
        GetClassName(hWnd, sb, sb.Capacity);
        if (sb.ToString() != "#32770") return true;
        // Got it
        Rectangle frmRect = new Rectangle(mOwner.Location, mOwner.Size);
        RECT dlgRect;
        GetWindowRect(hWnd, out dlgRect);
        MoveWindow(hWnd,
            frmRect.Left + (frmRect.Width - dlgRect.Right + dlgRect.Left) / 2,
            frmRect.Top + (frmRect.Height - dlgRect.Bottom + dlgRect.Top) / 2,
            dlgRect.Right - dlgRect.Left,
            dlgRect.Bottom - dlgRect.Top, true);
        return false;
    }
    public void Dispose() {
        mTries = -1;
    }

    // P/Invoke declarations
    private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
    [DllImport("user32.dll")]
    private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
    [DllImport("kernel32.dll")]
    private static extern int GetCurrentThreadId();
    [DllImport("user32.dll")]
    private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen);
    [DllImport("user32.dll")]
    private static extern bool GetWindowRect(IntPtr hWnd, out RECT rc);
    [DllImport("user32.dll")]
    private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int w, int h, bool repaint);
    private struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
}

使用示例:

    private void button1_Click(object sender, EventArgs e) {
        using (new CenterWinDialog(this)) {
            MessageBox.Show("Nobugz waz here");
        }
    }

请注意,此代码适用于任何 Windows 对话框。MessageBox、OpenFormDialog、FolderBrowserDialog、PrintDialog、ColorDialog、FontDialog、PageSetupDialog、SaveFileDialog。


谢谢,非常好的解决方案。你只需要更改它不使用多余的 this 关键字。直接使用顶层表单即可。 - c00000fd
最棒的 +10000000000000 - Viet Nguyen
那是一个疯狂的小工具。优秀的解决方案。 - Nyerguds
5
如果主窗口被最小化,这个问题会影响消息框。你需要做一个小改动来避免这个问题。在构造函数中,在 owner.BeginInvoke 之前加入 if (owner.WindowState != FormWindowState.Minimized) 这段代码,以在这种情况下禁用它。请注意,这不会改变原有的意思。 - Nyerguds
2
我们向大师致敬! <^_^>/ 非常优秀的先生,十二个王国感谢您。 - pdp

2

这是关于Win32 API的内容,使用C语言编写。您可以根据需要进行翻译...

case WM_NOTIFY:{
  HWND X=FindWindow("#32770",NULL);
  if(GetParent(X)==H_frame){int Px,Py,Sx,Sy; RECT R1,R2;
    GetWindowRect(hwnd,&R1); GetWindowRect(X,&R2);
    Sx=R2.right-R2.left,Px=R1.left+(R1.right-R1.left)/2-Sx/2;
    Sy=R2.bottom-R2.top,Py=R1.top+(R1.bottom-R1.top)/2-Sy/2;
    MoveWindow(X,Px,Py,Sx,Sy,1);
  }
} break;

将以下代码添加到WndProc代码中...你可以将位置设置为你喜欢的任何位置,这里只是在主程序窗口中心。对于任何消息框、文件打开/保存对话框以及其他一些本机控件,它都会执行此操作。我不确定,但我认为您可能需要包括COMMCTRL或COMMDLG来使用此功能,至少,如果您想要打开/保存对话框,则需要这样做。
我试着查看NMHDR的通知代码和hwndFrom,然后决定不这样做同样有效,而且更容易。如果您确实想非常具体,请告诉FindWindow寻找您要查找的窗口所给定的唯一标题。
这将在消息框绘制在屏幕上之前触发,因此如果您设置一个全局标志以指示您的代码何时完成操作,并查找唯一标题,您可以确保您执行的操作仅发生一次(可能会有多个通知器)。我还没有详细研究过这个问题,但我设法使CreateWindow在消息框对话框上放置一个编辑框/它看起来像是将老鼠耳朵移植到克隆猪脊椎的地方,但它起作用了。以这种方式处理事情可能比自己设计要容易得多。
Crow.
编辑:进行小修正以确保正确处理窗口。确保父句柄始终一致,这样应该可以正常工作。即使在同一个程序的两个实例中,对我来说也是这样...

1
该类适用于另外两种情况。我有一个FolderBrowserDialog,我希望它更大,并且想让它出现在父对话框的左上角(靠近我点击打开它的按钮)。
我复制了CenterWinDialog类并创建了两个新类。一个类改变对话框的大小,另一个类将其位置更改为与父窗体的特定偏移量。这是用法:
        using (new OffsetWinDialog(this) { PreferredOffset = new Point(75, 75 )})
        using (new SizeWinDialog(this)   { PreferredSize   = new Size(400, 600)})
        {
            DialogResult result = dlgFolderBrowser.ShowDialog();
            if (result == DialogResult.Cancel)
                return;
        }

这是基于原始类创建的两个类。

class OffsetWinDialog : IDisposable
{
    private int mTries = 0;
    private Form mOwner;

    public OffsetWinDialog(Form owner)
    {
        mOwner = owner;
        owner.BeginInvoke(new MethodInvoker(findDialog));
    }

    public Point PreferredOffset { get; set; }

    private void findDialog()
    {
        // Enumerate windows to find the message box
        if (mTries < 0) 
            return;
        EnumThreadWndProc callback = new EnumThreadWndProc(checkWindow);
        if (EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero))
        {
            if (++mTries < 10)
                mOwner.BeginInvoke(new MethodInvoker(findDialog));
        }
    }
    private bool checkWindow(IntPtr hWnd, IntPtr lp)
    {
        // Checks if <hWnd> is a dialog
        StringBuilder sb = new StringBuilder(260);
        GetClassName(hWnd, sb, sb.Capacity);
        if (sb.ToString() != "#32770") return true;
        // Got it
        Rectangle frmRect = new Rectangle(mOwner.Location, mOwner.Size);
        RECT dlgRect;
        GetWindowRect(hWnd, out dlgRect);
        MoveWindow(hWnd,
            frmRect.Left   + PreferredOffset.X,
            frmRect.Top    + PreferredOffset.Y,
            dlgRect.Right  - dlgRect.Left,
            dlgRect.Bottom - dlgRect.Top, 
            true);
        return false;
    }
    public void Dispose()
    {
        mTries = -1;
    }

    // P/Invoke declarations
    private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
    [DllImport("user32.dll")]
    private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
    [DllImport("kernel32.dll")]
    private static extern int GetCurrentThreadId();
    [DllImport("user32.dll")]
    private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen);
    [DllImport("user32.dll")]
    private static extern bool GetWindowRect(IntPtr hWnd, out RECT rc);
    [DllImport("user32.dll")]
    private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int w, int h, bool repaint);
    private struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
}

class SizeWinDialog : IDisposable
{
    private int mTries = 0;
    private Form mOwner;

    public SizeWinDialog(Form owner)
    {
        mOwner = owner;
        mOwner.BeginInvoke(new Action(findDialog));
    }

    public Size PreferredSize { get; set; }

    private void findDialog()
    {
        // Enumerate windows to find the message box
        if (mTries < 0) 
            return;
        EnumThreadWndProc callback = new EnumThreadWndProc(checkWindow);
        if (EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero))
        {
            if (++mTries < 10) 
                mOwner.BeginInvoke(new MethodInvoker(findDialog));
        }
    }
    private bool checkWindow(IntPtr hWnd, IntPtr lp)
    {
        // Checks if <hWnd> is a dialog
        StringBuilder sb = new StringBuilder(260);
        GetClassName(hWnd, sb, sb.Capacity);
        if (sb.ToString() != "#32770") 
            return true;
        // Got it
        Rectangle frmRect = new Rectangle(mOwner.Location, mOwner.Size);
        RECT dlgRect;
        GetWindowRect(hWnd, out dlgRect);
        SetWindowPos(new HandleRef(this, hWnd), new HandleRef(), dlgRect.Left, dlgRect.Top, PreferredSize.Width, PreferredSize.Height, 20 | 2);
        return false;
    }
    public void Dispose()
    {
        mTries = -1;
    }

    // P/Invoke declarations
    private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
    [DllImport("user32.dll")]
    private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
    [DllImport("kernel32.dll")]
    private static extern int GetCurrentThreadId();
    [DllImport("user32.dll")]
    private static extern int GetClassName(IntPtr hWnd, StringBuilder buffer, int buflen);
    [DllImport("user32.dll")]
    private static extern bool GetWindowRect(IntPtr hWnd, out RECT rc);
    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    public static extern bool SetWindowPos(HandleRef hWnd, HandleRef hWndInsertAfter, int x, int y, int cx, int cy,
        int flags);

    private struct RECT { public int Left; public int Top; public int Right; public int Bottom; }
}

0

编写自己的消息框。一个窗体和一个标签就可以了。或者你还需要全球化吗?


在这种情况下,您可以向表单添加标签并使用MeasureString获取其边界。适当调整表单的大小并将其定位在任何位置。应该很快。但我必须承认,我也喜欢Nobugz的解决方案。 - Pedery
这实际上忽略了复制消息框可能具有的许多变化所涉及的复杂性,使新API成为原始API的“替代品”,修改所有现有代码以使用替换等。此外,说实话,它并没有真正回答特定的问题。 - StayOnTarget

0

不需要使用自制消息框或者GetForegroundWindow、EnumWindows、AutomationElement.RootElement.FindAll、SetWindowsHookEx等方法。

当消息框被打开或关闭时,WM_ACTIVATE消息会被发送到表单。

然后,您可以获取消息框的窗口句柄(LParam)。

protected override void WndProc(ref Message m)
{
    switch (m.Msg) {
    case Pinvoke.WM_ACTIVATE:
        Debug.WriteLine($"{MethodBase.GetCurrentMethod().Name} {DateTime.Now.ToString("HH:mm:ss.fff")} {m.ToString()}");
        if (m.LParam == IntPtr.Zero) break;
        if (_messageBoxCaption == null) break; // donot call MessageBox.Show
        if ((ushort)m.WParam.ToInt32() != 0/*WA_INACTIVE*/) break; // maybe close messagebox

        // check messagebox
        if (Pinvoke.GetWindowProcessId(m.LParam) != Process.GetCurrentProcess().Id) break;
        string className = Pinvoke.GetClassName(m.LParam);
        if (className == null || className != "#32770") break; // not dialog
        if (_messageBoxCaption != Pinvoke.GetWindowText(m.LParam)) break; // another caption

        // move messagebox
        //Debug.WriteLine("messageBox detected");
        Rectangle rect = Pinvoke.GetWindowRect(m.LParam);
        Pinvoke.MoveWindow(m.LParam, this.Left + this.Width / 2, this.Top + this.Height / 2, rect.Width, rect.Height, true);
        break;
    }

    base.WndProc(ref m);
}

GetWindowProcessId是GetWindowThreadProcessId的包装器。 根据需要添加其他Pinvoke方法。 如果您想尽可能减少P/Invoke,请使用UIAutomation替换它们。

private string _messageBoxCaption = null; // messageBox caption

_messageBoxCaption = caption;
ret = MessageBox.Show(this, text, caption, ...);
_messageBoxCaption = null;

-2

创建你自己的...

 public partial class __MessageBox : Form
   {
      public MMMessageBox(string title, string message)
      {
         InitializeComponent();
         this.Text = title;
         this.labelString.Text = message;
      }
   }

2
你的帖子中没有处理“以 MainForm 为中心”的问题。 - LarsTech
抱歉,Lars,请使用“表单属性”:StartPosition->CenterParent来设置您创建的对话框的位置。 - gameOverMan
3
现在让它处理所有图标,是/否/确定/取消按钮,以及原始消息框具有的默认选定按钮和“DialogResult”响应案例。这并不是一项微不足道的任务。 - Nyerguds

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接