21

有没有一种简单的方法可以在 VB.NET 中显示带有自定义按钮标题的消息框?我遇到了在托管 C++ 中创建带有自定义按钮文本的 MessageBox 的简单方法是什么?,在堆栈溢出档案中,但它适用于托管 C++

4

8 回答 8

27

MessageBox 使用一个普通的窗口,它可以像任何其他窗口一样被弄乱。这在 Windows 中已经实现了很长时间,已经超过 20 年了。然而,这些技术变得晦涩难懂,太多友好的类包装器隐藏了本机 winapi 并且没有暴露你可以用它做的所有事情。如此之多,以至于程序员现在自动假设这是不可能的,正如您可以从赞成的答案中看出的那样。这是 Petzold 在其开创性的“Programming Windows”一书中教给我们的那种编程。用自定义的 Form 或 Window 替换 MessageBox 实际上是相当困难的,它会自动布局以适应文本并在没有帮助的情况下支持本地化。虽然这正是你似乎不喜欢的:)

Anyhoo,消息框窗口很容易找回。它由 UI 线程拥有,并具有使其独一无二的特殊类名。EnumThreadWindows() 枚举线程拥有的窗口,GetClassName() 让您检查窗口的类型。然后只需使用 SetWindowText() 将文本插入按钮。

向您的项目添加一个新类并粘贴如下所示的代码。使用如下代码调用它:

Nobugz.PatchMsgBox(New String() {"Da", "Njet"})
MsgBox("gack", MsgBoxStyle.YesNo)

这是代码:

Imports System.Text
Imports System.Runtime.InteropServices

Public Class Nobugz
  Private Shared mLabels() As String    '' Desired new labels
  Private Shared mLabelIndex As Integer '' Next caption to update

  Public Shared Sub PatchMsgBox(ByVal labels() As String)
    ''--- Updates message box buttons
    mLabels = labels
    Application.OpenForms(0).BeginInvoke(New FindWindowDelegate(AddressOf FindMsgBox), GetCurrentThreadId())
  End Sub

  Private Shared Sub FindMsgBox(ByVal tid As Integer)
    ''--- Enumerate the windows owned by the UI thread
    EnumThreadWindows(tid, AddressOf EnumWindow, IntPtr.Zero)
  End Sub

  Private Shared Function EnumWindow(ByVal hWnd As IntPtr, ByVal lp As IntPtr) As Boolean
    ''--- Is this the message box?
    Dim sb As New StringBuilder(256)
    GetClassName(hWnd, sb, sb.Capacity)
    If sb.ToString() <> "#32770" Then Return True
    ''--- Got it, now find the buttons
    mLabelIndex = 0
    EnumChildWindows(hWnd, AddressOf FindButtons, IntPtr.Zero)
    Return False
  End Function

  Private Shared Function FindButtons(ByVal hWnd As IntPtr, ByVal lp As IntPtr) As Boolean
    Dim sb As New StringBuilder(256)
    GetClassName(hWnd, sb, sb.Capacity)
    If sb.ToString() = "Button" And mLabelIndex <= UBound(mLabels) Then
      ''--- Got one, update text
      SetWindowText(hWnd, mLabels(mLabelIndex))
      mLabelIndex += 1
    End If
    Return True
  End Function

  ''--- P/Invoke declarations
  Private Delegate Sub FindWindowDelegate(ByVal tid As Integer)
  Private Delegate Function EnumWindowDelegate(ByVal hWnd As IntPtr, ByVal lp As IntPtr) As Boolean
  Private Declare Auto Function EnumThreadWindows Lib "user32.dll" (ByVal tid As Integer, ByVal callback As EnumWindowDelegate, ByVal lp As IntPtr) As Boolean
  Private Declare Auto Function EnumChildWindows Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal callback As EnumWindowDelegate, ByVal lp As IntPtr) As Boolean
  Private Declare Auto Function GetClassName Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal name As StringBuilder, ByVal maxlen As Integer) As Integer
  Private Declare Auto Function GetCurrentThreadId Lib "kernel32.dll" () As Integer
  Private Declare Auto Function SetWindowText Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal text As String) As Boolean
End Class
于 2008-10-24T22:37:59.330 回答
19

不可以。
您必须使用FormBorderType = FixedDialog.
这是一个小教程:

在 .NET 中创建对话框

James D. Murray 于 2007 年 6 月 12 日,在 70-526 下

Microsoft 认证考试:70-526 (MCTS)
目标:在 Windows 窗体应用程序中创建和使用自定义对话框。
语言:Visual Basic 2005(点击此处查看本条目的 C# 版本)

我记得我第一次需要在我用 C# 编写的 .NET 应用程序中创建一个对话框。作为一名资深的 Visual Basic 程序员,我认为这可以通过使用 Visual Studio.NET 中包含的对话框模板轻松完成。令我惊讶的是,C# 没有这样的表单模板,尽管 Visual Basic 2005 有。在翻阅了几本关于 Windows Forms 2.0 编程信息的书籍和网页后,一组基本步骤对我来说变得很明显,用于手动转换.NET 窗体进入 Windows 对话框:

第 1 步:向您的 .NET 项目添加一个表单并将其命名为“DialogBoxForm”。

第 2 步:将两个按钮放在表单的右下方区域,并将它们命名为“OKButton”和“CancelButton”。

第 3 步:更改 Form 的以下属性以将其外观和行为调整为类似于标准对话框:

    属性值说明
    -------------------------------------------------- -------------------------------------------------- -------------------------
    AcceptButton OK 按钮实例 使表单返回 DialogResult.OK 值。仅用于模式对话框。
    CancelButton 取消按钮实例 使表单返回值DialogResult.Cancel。仅用于模式对话框。
    FormBorderStyle FixedDialog 创建一个在标题栏上没有控制框的大小不一的表单。
    HelpButton True 帮助按钮出现在关闭按钮旁边的标题栏中。ControlBox 属性必须为 True 才能使这些按钮可见。
    MaximizeBox False 隐藏标题栏中的最大化按钮。
    MinimizeBox False 隐藏标题栏中的最小化按钮。
    ShowIcon False 标题栏图标在对话框中不可见。
    ShowInTaskBar False 不指示窗体在 Windows 任务栏上的存在。
    起始位置 CenterParent 对话框的初始位置在其父窗体之上。
    所需大小 对话框所需的固定大小。

可以使用表单的“属性”窗口或使用放置在表单的 Load 事件中的代码来设置这些属性:

    Me.AcceptButton = OKButton
    Me.CancelButton = CancelButton
    Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedDialog
    Me.HelpButton = True
    Me.MaximizeBox = False
    Me.MinimizeBox = False
    Me.ShowInTaskbar = False
    Me.ShowIcon = False
    Me.StartPosition = FormStartPosition.CenterParent

第 4 步:将以下按钮单击事件处理程序添加到表单:

    Private Sub OKButton_Click(ByVal sender As Object, _ByVal e As EventArgs)
        ' 用户点击确定按钮
        Me.DialogResult = Windows.Forms.DialogResult.OK
    结束子
    
    Private Sub CancelButton_Click(ByVal sender As Object, _ByVal e As EventArgs)
        ' 用户点击取消按钮
        Me.DialogResult = Windows.Forms.DialogResult.Cancel
    结束子

第 5 步:添加您需要将数据移入和移出对话框的属性,就像对任何表单一样:

    私有 _LoginName 作为字符串
    私有 _LoginPassword 作为字符串

    公共属性 LoginName() 作为字符串
        得到
            返回_登录名
        结束获取
        设置(ByVal 值作为字符串)
            _LoginName = 值
        结束集
    结束属性

    公共属性 LoginPassword() 作为字符串
        得到
            返回_登录密码
        结束获取
        设置(ByVal 值作为字符串)
            _LoginPassword = 值
        结束集
    结束属性

第 6 步:通过调用表单的 ShowDialog() 以模态方式显示对话框:

    公共子 ShowDialogBox()
        暗淡对话框作为新的 DialogBoxForm

        dialog.LoginName = "JDMurray"
        dialog.LoginPassword = String.Empty

        如果 dialog.ShowDialog() = Windows.Forms.DialogResult.OK 那么
            Debug.WriteLine("登录名:" & dialog.LoginName)
            Debug.WriteLine("密码:" & dialog.LoginPassword)
        别的
            ' 用户点击取消按钮
        万一
    结束子

第 7 步:要无模式地显示对话框,请调用 DialogBoxForm 的 Show() 方法。您需要向 DialogBoxForm 的 Close 事件添加一个事件处理程序,以了解用户何时关闭对话框:

    公共子 ShowDialogBox()
        暗淡对话框 As DialogBoxForm = New DialogBoxForm
        dialog.LoginName = "JDMurray"
        dialog.Password = String.Empty
        AddHandler dialog.FormClosed, AddressOf dialog_FormClosed
        对话框.Show()

        ' Show() 方法立即返回
    结束子

    Private Sub dialog_FormClosed(ByVal sender As Object, _

     ByVal e As FormClosedEventArgs)
        ' 当用户关闭对话框时调用此方法
    结束子
于 2008-10-24T18:49:32.957 回答
7

不,没有方法可以访问或重定向消息框的默认按钮文本。

做到这一点的唯一方法是编写自己的代码,或者只使用互联网上许多免费的代码之一:

免费 MsgBoxGo!

于 2008-10-24T19:02:26.223 回答
2

有一个解决方案。通过安装 CBT 挂钩,可以即时调整各种 MessageBox 视觉设置:消息和按钮字体、对话框背景、对话框定位、图标、按钮标题、超时,甚至插入其他控件。

完整的解决方案:扩展 MessageBox .NET 程序集 http://www.news2news.com/vfp/?solution=5

它是一个功能齐全的试用版,普通版包含完整的 C# 源代码。

于 2010-05-16T23:38:07.283 回答
2

Daniel Nolan 的解决方案,VB.Net 中的代码

<DllImport("kernel32.dll")> _
Private Shared Function GetCurrentThreadId() As UInteger
End Function

<DllImport("user32.dll", CharSet:=CharSet.Auto)> _
Private Shared Function CallNextHookEx(ByVal idHook As Integer, ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
End Function

<DllImport("user32.dll", CharSet:=CharSet.Auto)> _
Private Shared Function UnhookWindowsHookEx(ByVal idHook As Integer) As Boolean
End Function

<DllImport("user32.dll", CharSet:=CharSet.Auto)> _
Private Shared Function SetWindowsHookEx(ByVal idHook As Integer, ByVal lpfn As HookProc, ByVal hInstance As IntPtr, ByVal threadId As Integer) As Integer
End Function

<DllImport("user32.dll")> _
Private Shared Function SetDlgItemText(ByVal hWnd As IntPtr, ByVal nIDDlgItem As Integer, ByVal lpString As String) As Boolean
End Function

Private Delegate Function HookProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer

Shared dlgHookProc As HookProc

Private Const WH_CBT As Long = 5
Private Const HCBT_ACTIVATE As Long = 5

Private Const ID_BUT_OK As Integer = 1
Private Const ID_BUT_CANCEL As Integer = 2
Private Const ID_BUT_ABORT As Integer = 3
Private Const ID_BUT_RETRY As Integer = 4
Private Const ID_BUT_IGNORE As Integer = 5
Private Const ID_BUT_YES As Integer = 6
Private Const ID_BUT_NO As Integer = 7

Private Const BUT_OK As String = "Save"
Private Const BUT_CANCEL As String = "Cancelar"
Private Const BUT_ABORT As String = "Stop"
Private Const BUT_RETRY As String = "Continue"
Private Const BUT_IGNORE As String = "Ignore"
Private Const BUT_YES As String = "Si"
Private Const BUT_NO As String = "No"

Private Shared _hook As Integer = 0

Private Shared Function DialogHookProc(ByVal nCode As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
  If nCode < 0 Then
    Return CallNextHookEx(_hook, nCode, wParam, lParam)
  End If

  If nCode = HCBT_ACTIVATE Then
    SetDlgItemText(wParam, ID_BUT_OK, BUT_OK)
    SetDlgItemText(wParam, ID_BUT_CANCEL, BUT_CANCEL)
    SetDlgItemText(wParam, ID_BUT_ABORT, BUT_ABORT)
    SetDlgItemText(wParam, ID_BUT_RETRY, BUT_RETRY)
    SetDlgItemText(wParam, ID_BUT_IGNORE, BUT_IGNORE)
    SetDlgItemText(wParam, ID_BUT_YES, BUT_YES)
    SetDlgItemText(wParam, ID_BUT_NO, BUT_NO)
  End If

  Return CallNextHookEx(_hook, nCode, wParam, lParam)
End Function

Private Sub btn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn.Click
  dlgHookProc = New HookProc(AddressOf DialogHookProc)

  _hook = SetWindowsHookEx(CInt(WH_CBT), dlgHookProc, IntPtr.op_Explicit(0), CInt(GetCurrentThreadId()))

  Dim dlgEmptyCheck As DialogResult = MessageBox.Show("Text", "Caption", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button3)


  If dlgEmptyCheck = DialogResult.Abort Then
  End If

  UnhookWindowsHookEx(_hook)
End Sub
于 2016-02-09T17:29:04.023 回答
1

可以在 MSDN 论坛下的一篇文章中找到完成相同操作的 C# 代码,https: //forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3087899&SiteID=1 。

于 2008-10-24T19:10:37.830 回答
1

将此添加到您希望从中显示对话框的按钮。这是一个自定义表单messageBox;

    private void DGroup_Click(object sender, EventArgs e)
    {
        messageBox m = new messageBox();
        m.ShowDialog();
        if (m.DialogResult == DialogResult.Yes)
        {
            //del(groups.php?opt=del&amp;id=613','asdasd');
            String[] asd = new String[2];
            asd[0] = "groups.php?opt=del&amp;id=613";
            asd[1] = "asdasd";
            addgroup.Document.InvokeScript("del",asd);
        }
        else
            if (m.DialogResult == DialogResult.No)
            {
                MessageBox.Show("App won´t close");
            }
    }

将此代码添加到消息框。

    private void deleteGroupOnly_Click(object sender, EventArgs e)
    {
        this.DialogResult = DialogResult.Yes;
        this.Close();
    }

    private void deleteAll_Click(object sender, EventArgs e)
    {
        this.DialogResult = DialogResult.No;
        this.Close();
    }

    private void cancel_Click(object sender, EventArgs e)
    {
        this.DialogResult = DialogResult.Cancel;
        this.Close();
    }
于 2010-12-22T08:26:41.977 回答
0

这是一个 C# 片段,它使用 Win32 挂钩来更改按钮标题(来自http://icodesnip.com/snippet/csharp/custom-messagebox-buttons):

        [DllImport("kernel32.dll")]
        static extern uint GetCurrentThreadId();

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern bool UnhookWindowsHookEx(int idHook);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

        [DllImport("user32.dll")]
        private static extern bool SetDlgItemText(IntPtr hWnd, int nIDDlgItem, string lpString);

        delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

        static HookProc dlgHookProc;

        private const long WH_CBT = 5;
        private const long HCBT_ACTIVATE = 5;

        private const int ID_BUT_OK = 1;
        private const int ID_BUT_CANCEL = 2;
        private const int ID_BUT_ABORT = 3;
        private const int ID_BUT_RETRY = 4;
        private const int ID_BUT_IGNORE = 5;
        private const int ID_BUT_YES = 6;
        private const int ID_BUT_NO = 7;

        private const string BUT_OK = "Save";
        private const string BUT_CANCEL = "Cancel";
        private const string BUT_ABORT = "Stop";
        private const string BUT_RETRY = "Continue";
        private const string BUT_IGNORE = "Ignore";
        private const string BUT_YES = "Yeeh";
        private const string BUT_NO = "Never";

        private static int _hook = 0;

        private static int DialogHookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode < 0)
            {
                return CallNextHookEx(_hook, nCode, wParam, lParam);
            }

            if (nCode == HCBT_ACTIVATE)
            {
                SetDlgItemText(wParam, ID_BUT_OK, BUT_OK);
                SetDlgItemText(wParam, ID_BUT_CANCEL, BUT_CANCEL);
                SetDlgItemText(wParam, ID_BUT_ABORT, BUT_ABORT);
                SetDlgItemText(wParam, ID_BUT_RETRY, BUT_RETRY);
                SetDlgItemText(wParam, ID_BUT_IGNORE, BUT_IGNORE);
                SetDlgItemText(wParam, ID_BUT_YES, BUT_YES);
                SetDlgItemText(wParam, ID_BUT_NO, BUT_NO);
            }

            return CallNextHookEx(_hook, nCode, wParam, lParam);
        }

        private void Button_Click(object sender, EventArgs e)
        {
            dlgHookProc = new HookProc(DialogHookProc);

            _hook = SetWindowsHookEx((int)WH_CBT, dlgHookProc, (IntPtr)0, (int)GetCurrentThreadId());

            DialogResult dlgEmptyCheck = MessageBox.Show("Text", "Caption", MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button3);

            if (dlgEmptyCheck == DialogResult.Abort)
            {

            }

            UnhookWindowsHookEx(_hook);
        }
于 2011-09-15T11:39:17.890 回答