【问题标题】:MessageBox with custom font?带有自定义字体的消息框?
【发布时间】:2013-10-06 02:03:05
【问题描述】:

有没有一种简单的方法可以为 MessageBox 显示自定义字体?

对于“简单的方法”,我的意思是使用 WinAPI 或其他技术,但不是从头开始编写整个消息框。

我见过很多自定义消息框,但大多数只是不保留默认消息框附加参数的表单,其他自定义消息框的大小/边界错误,因此“确定”按钮被剪切或不正确对齐,并且其他自定义消息框有自己的问题/错误。

我希望是否可以添加一个通用参数来实例化这个很棒的自定义消息框设置所需的字体:

原始代码是 @Hans Passant 的 C# 自定义消息框类,我很久以前从这里 Winforms-How can I make MessageBox appear centered on MainForm? 获取并使用在线翻译器对其进行了翻译:

' [ Centered Messagebox ]
'
' Examples :
'
' Using New MessageBox_Centered(Me)
'     MessageBox.Show("Test Text", "Test Title", MessageBoxButtons.OK)
' End Using

#Region " Centered MessageBox Class"

Imports System.Runtime.InteropServices
Imports System.Text

Class MessageBox_Centered
Implements IDisposable

' P/Invoke
Public Class NativeMethods

    Delegate Function EnumThreadWndProc(hWnd As IntPtr, lp As IntPtr) As Boolean

    <DllImport("user32.dll")> _
    Shared Function EnumThreadWindows(tid As Integer, callback As EnumThreadWndProc, lp As IntPtr) As Boolean
    End Function

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

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> _
    Shared Function GetClassName(hWnd As IntPtr, buffer As StringBuilder, buflen As Integer) As Integer
    End Function

    <DllImport("user32.dll")> _
    Shared Function GetWindowRect(hWnd As IntPtr, ByRef rc As RECT) As Boolean
    End Function

    <DllImport("user32.dll")> _
    Shared Function MoveWindow(hWnd As IntPtr, x As Integer, y As Integer, w As Integer, h As Integer, repaint As Boolean) As Boolean
    End Function

    Structure RECT
        Public Left As Integer
        Public Top As Integer
        Public Right As Integer
        Public Bottom As Integer
    End Structure

End Class

Private mTries As Integer = 0
Private mOwner As Form

Public Sub New(owner As Form)
    mOwner = owner
    owner.BeginInvoke(New MethodInvoker(AddressOf findDialog))
End Sub

Private Sub findDialog()

    ' Enumerate windows to find the message box
    If mTries < 0 Then Return

    Dim callback As New NativeMethods.EnumThreadWndProc(AddressOf checkWindow)
    If NativeMethods.EnumThreadWindows(NativeMethods.GetCurrentThreadId(), callback, IntPtr.Zero) Then
        If System.Threading.Interlocked.Increment(mTries) < 10 Then
            mOwner.BeginInvoke(New MethodInvoker(AddressOf findDialog))
        End If
    End If

End Sub

Private Function checkWindow(hWnd As IntPtr, lp As IntPtr) As Boolean

    ' Checks if <hWnd> is a dialog
    Dim sb As New StringBuilder(260)
    NativeMethods.GetClassName(hWnd, sb, sb.Capacity)
    If sb.ToString() <> "#32770" Then Return True

    ' Got it
    Dim frmRect As New Rectangle(mOwner.Location, mOwner.Size)
    Dim dlgRect As NativeMethods.RECT
    NativeMethods.GetWindowRect(hWnd, dlgRect)
    NativeMethods.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

End Function

Public Sub Dispose() Implements IDisposable.Dispose
    mTries = -1
End Sub

End Class

#End Region

更新:

试图适应@Pete 假设的解决方案,但我做不到。

Class MessageBox_Centered : Implements IDisposable

Public Class NativeMethods

    Delegate Function EnumThreadWndProc(hWnd As IntPtr, lp As IntPtr) As Boolean
    Delegate Function EnumWindowsProc(hWnd As IntPtr, lp As IntPtr) As Boolean

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Shared Function EnumThreadWindows(tid As Integer, callback As EnumThreadWndProc, lp As IntPtr) As Boolean
    End Function

    <DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Shared Function GetCurrentThreadId() As Integer
    End Function

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Shared Function GetClassName(hWnd As IntPtr, buffer As StringBuilder, buflen As Integer) As Integer
    End Function

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Shared Function GetWindowRect(hWnd As IntPtr, ByRef rc As RECT) As Boolean
    End Function

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Shared Function MoveWindow(hWnd As IntPtr, x As Integer, y As Integer, w As Integer, h As Integer, repaint As Boolean) As Boolean
    End Function


    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Shared Function GetWindowText(ByVal hwnd As IntPtr, ByVal lpString As StringBuilder, ByVal cch As Integer) As Integer
    End Function

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Shared Function EnumChildWindows(hwndParent As IntPtr, lpEnumFunc As EnumWindowsProc, lParam As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Shared Function SendMessage(hWnd As IntPtr, Msg As UInt32, wParam As IntPtr, lParam As IntPtr) As IntPtr
    End Function

    Structure RECT
        Public Left As Integer
        Public Top As Integer
        Public Right As Integer
        Public Bottom As Integer
    End Structure

End Class

Private mTries As Integer = 0
Private mOwner As Form

Public Sub New(owner As Form)
    mOwner = owner
    owner.BeginInvoke(New MethodInvoker(AddressOf findDialog))
End Sub

Private Sub findDialog()

    ' Enumerate windows to find the message box
    If mTries < 0 Then Return

    Dim callback As New NativeMethods.EnumThreadWndProc(AddressOf checkWindow)
    If NativeMethods.EnumThreadWindows(NativeMethods.GetCurrentThreadId(), callback, IntPtr.Zero) Then
        If System.Threading.Interlocked.Increment(mTries) < 10 Then
            mOwner.BeginInvoke(New MethodInvoker(AddressOf findDialog))
        End If
    End If

End Sub

Private Function checkWindow(hWnd As IntPtr, lp As IntPtr) As Boolean

    ' Checks if <hWnd> is a dialog
    Dim sb As New StringBuilder(260)
    NativeMethods.GetClassName(hWnd, sb, sb.Capacity)
    If sb.ToString() <> "#32770" Then
        Return True
    End If


    ' Got it
    Dim frmRect As New Rectangle(mOwner.Location, mOwner.Size)
    Dim dlgRect As NativeMethods.RECT
    NativeMethods.GetWindowRect(hWnd, dlgRect)
    NativeMethods.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)

    ' Dim wndText As New StringBuilder()
    ' NativeMethods.GetWindowText(hWnd2, wndText, 1000)
    ' SendMessage(hWnd2, WM_SETFONT, f.ToHfont(), new IntPtr(1))

    Return False

End Function

Public Sub Dispose() Implements IDisposable.Dispose
    mTries = -1
End Sub

End Class

更新 2:

这是对我需要做的事情的解释。

以@Hans Passant 的代码 sn-p 为中心的消息框,我需要使用自定义字体启动它(实例化它)。

一个例子可能是创建一个通用函数到居中的消息框可能使用类的“新”块来传递所需的字体作为参数,然后用该字体做必要的事情来显示消息框居中 + 自定义字体。

所以我需要通过添加使用自定义字体的可能性来扩展类。

【问题讨论】:

  • 您违反了本网站的许可,您不能在没有署名的情况下复制某人的答案。我已经回答了这个问题,你复制了错误的代码。
  • @Hans Passant 我不敢相信你对我说的话,代码是我很久以前在我的这个问题中发布的一个 sn-p:stackoverflow.com/questions/ 15904610/show-a-messagebox-centered-in-form ,该代码不是我的,但显然我不知道原始代码是哪个,我无法知道代码是否来自 StackOverflow 答案或者谁是作者,你认为我需要搜索整个谷歌结果才能找到sn-p的作者吗?只是我不敢相信,我太尊重你了,但这并不严重。原谅我的英语。
  • 链接到您也违反了许可的另一个帖子是不够的。显然,您最初找到代码没有问题,它在该问题中作为重复链接。你必须纠正这些错误。
  • 哦,我现在明白你在说什么了,我看到我已经翻译了你的代码,当然你需要明白从很久以前我做这个问题到今天,我我不记得你或其他任何人是作者,我当然会汇总你的归属,因为现在我通过阅读我链接的帖子的 cmets 知道了。请原谅我的英语并理解我所说的话
  • 我明白了。它发生了。简单地指出,始终将代码归于个人用途是一种很好的做法,因为您永远不知道何时需要这些信息。我总是将 URL 放在评论中,而且我经常发现自己使用这些 URL 来返回源代码。

标签: .net vb.net winforms fonts messagebox


【解决方案1】:

我已经回答了这个问题,答案是is here。您只需要稍微调整一下,因为您对更改现有字体不感兴趣:

if (hText != IntPtr.Zero) {
  // Get the current font
  IntPtr hFont = SendMessage(hText, WM_GETFONT, IntPtr.Zero, IntPtr.Zero);
  Font font = Font.FromHfont(hFont);
  mFont = new Font(new FontFamily("Arial"), font.SizeInPoints, FontStyle.Normal);
  SendMessage(hText, WM_SETFONT, mFont.ToHfont(), (IntPtr)1);
}

只有第 5 行不同。更改所需的字体系列。这段代码存在同样的基本问题,虽然没有那么严重,但您选择的新字体必须适合静态控件的计算大小。为原始字体进行的计算。如果您的新字体是“宽”的,那么它不适合,减少 SizeInPoints 是唯一的解决方法。

【讨论】:

  • 谢谢你,这就是为什么你是上帝,即使你不会。最后,我可以合并两个 MessageBox 类版本来获得我需要的东西,再次感谢并对归属感到抱歉。
  • 你能用简单的话指导我如何开始自己尝试解决大字体的问题吗?我的意思是我可以使用任何 P/Invoke 函数来更改 MessageBox 窗口的大小吗?知道这就足够了,谢谢。
  • 真的吗?那么不调查也不努力,你是说唯一的解决方案是创建一个自己的表格来改变表格的大小?你是说真的没有办法使用消息框默认类来修复它吗?我很抱歉我的坚持,因为我很高兴我的错误,我不能要求你更多,但我真的需要完全确保我可以尝试和不能尝试的事情。
  • @Hand Passant 不需要这样做,这对我和所有其他提出问题的人来说都太冒犯了。我只问过你是否知道调整消息框窗口大小的方法,我认为没有必要创建一个新问题只是为了问一些你可以在评论中只用几句话回答我的问题,因为我've ask 与你的回答有关,而且在问之前我已经接受了你的回答,我想我不是变色龙。
【解决方案2】:

首先,对于我的答案不在 VB 中,我深表歉意(更新 - 通过 C# 到 VB 转换器运行代码)。当然,您可以很好地阅读 C# 以理解这一点,我很乐意回答您对此提出的任何问题。

就您如何查找窗口和静态控件而言,此解决方案不是通用的。您需要根据自己的情况对其进行调整,但关于如何设置字体的重要部分是可重复使用的。

线程开头的Thread.Sleep() 有点随意。您可能需要稍等片刻(半秒肯定太长了),但是消息框显示需要一些时间,并且消息框会阻止执行。所以,我关闭了线程,让它等到消息框确实打开了,然后我开始寻找它。

另外,请务必最终在 HFont 上致电DeleteObject()

Public Partial Class Form1
    Inherits Form
    Private Const WM_SETFONT As UInt32 = &H30

    Private Delegate Function EnumThreadDelegate(hwnd As IntPtr, lParam As IntPtr) As Boolean
    Private Delegate Function EnumWindowsProc(hWnd As IntPtr, lParam As IntPtr) As Boolean

    <DllImport("user32.dll")> _
    Private Shared Function EnumThreadWindows(dwThreadId As UInteger, lpfn As EnumThreadDelegate, lParam As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

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

    <DllImport("user32.dll", SetLastError := True, CharSet := CharSet.Auto)> _
    Private Shared Function GetClassName(hWnd As IntPtr, lpClassName As StringBuilder, nMaxCount As Integer) As Integer
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function EnumChildWindows(hwndParent As IntPtr, lpEnumFunc As EnumWindowsProc, lParam As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("user32.dll", CharSet := CharSet.Auto, SetLastError := True)> _
    Private Shared Function GetWindowText(hWnd As IntPtr, lpString As StringBuilder, nMaxCount As Integer) As Integer
    End Function

    <DllImport("user32.dll", CharSet := CharSet.Auto)> _
    Private Shared Function SendMessage(hWnd As IntPtr, Msg As UInt32, wParam As IntPtr, lParam As IntPtr) As IntPtr
    End Function

    Shared threadId As UInteger = GetCurrentThreadId()

    Public Sub New()
        InitializeComponent()
    End Sub

    Private Sub button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim t As New Thread(New ThreadStart(AddressOf FixMsgBoxFont))
        t.Start()
        MessageBox.Show(Me, "MyMsg", "Test")
    End Sub

    Private Sub FixMsgBoxFont()
        Thread.Sleep(500)
        EnumThreadWindows(threadId, New EnumThreadDelegate(Function(hWnd, lParam) 
        Dim className As New StringBuilder()
        GetClassName(hWnd, className, 1000)

        ' Look for the message box window
        If className.ToString() <> "#32770" Then
            Return True
        End If

        EnumChildWindows(hWnd, New EnumWindowsProc(Function(hWnd2, lParam2) 
        Dim wndText As New StringBuilder()
        GetWindowText(hWnd2, wndText, 1000)

        ' Look for the static control with our text
        If wndText.ToString() = "MyMsg" Then
            ' Replace the font being used with 8pt Comix Sans MS
            Dim f As New Font(New FontFamily("Comic Sans MS"), 8, FontStyle.Bold, GraphicsUnit.Pixel)

            ' In real life, you'll eventually want to eventually call 
            ' the Windows API DeleteObject() on the font handle
            ' below or it will leak.
            Dim fontHandle As IntPtr = f.ToHfont()
            SendMessage(hWnd2, WM_SETFONT, f.ToHfont(), New IntPtr(1))
            Return False
        End If
        Return True

End Function), IntPtr.Zero)

        Return False

End Function), IntPtr.Zero)
    End Sub
End Class

【讨论】:

  • 真的谢谢你,但我觉得你的工作帮不了我太多,原因是我与复杂的C#代码无关。
  • 试图理解代码我认为您将等待“X”时间(500 毫秒)来检查一次是否显示消息框,我认为这是一种不安全的方法,因为这是怎么回事电脑真的很慢还是操作系统执行大操作时发生的大延迟? msgbox 可以显示 10-20 秒或更长时间。试图了解更多(我根本不能)我想你会得到消息框窗口的类名来检索文本并替换它发送一个windows消息,也许这部分可以像你所说的那样重复使用,但我可以找不到在VB中适应它的方法。谢谢
  • @ElektroHacker 好了。现在在 VB 中。
  • @ElektroHacker 有其他方法可以处理这个问题。您可以运行一个线程,该线程会一直查看直到窗口出现。如果您需要一个如何做到这一点的示例,请在此处发布一个关于 SO 的问题,我很乐意对此进行尝试。我无法想象显示消息框需要超过半秒的时间。使用 Windows 20 多年,我从未见过需要这么长时间。
  • (评论已删除,我没有注意到这不是通用的,并且可以从按钮启动新线程)我找不到调整我感兴趣的“FixMsgBoxFont”部分的方法我发布的原始自定义消息框,这就是我所要求的,非常感谢您抽出宝贵的时间,我会自己尝试,如果我得到它,我会标记这个有用的答案。再次感谢并原谅我的英语
【解决方案3】:

这是代码!

它有我需要解决的字体大小问题,但现在已经解决了!

' The author of this code is Hand Passant: 
' http://stackoverflow.com/questions/2259027/bold-text-in-messagebox/2259213#2259213
'
' I've just translated it to VB.NET and made very little modifications.

Imports System.Drawing
Imports System.Runtime.InteropServices
Imports System.Text
Imports System.Windows.Forms

Class CustomMessageBox : Implements IDisposable

Private mTries As Integer = 0
Private mOwner As Form
Private mFont As Font

' P/Invoke declarations
Private Const WM_SETFONT As Integer = &H30
Private Const WM_GETFONT As Integer = &H31

Private Delegate Function EnumThreadWndProc(hWnd As IntPtr, lp As IntPtr) As Boolean

<DllImport("user32.dll")> _
Private Shared Function EnumThreadWindows(tid As Integer, callback As EnumThreadWndProc, lp As IntPtr) As Boolean
End Function

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

<DllImport("user32.dll")> _
Private Shared Function GetClassName(hWnd As IntPtr, buffer As StringBuilder, buflen As Integer) As Integer
End Function

<DllImport("user32.dll")> _
Private Shared Function GetDlgItem(hWnd As IntPtr, item As Integer) As IntPtr
End Function

<DllImport("user32.dll")> _
Private Shared Function SendMessage(hWnd As IntPtr, msg As Integer, wp As IntPtr, lp As IntPtr) As IntPtr
End Function

<DllImport("user32.dll")> _
Shared Function GetWindowRect(hWnd As IntPtr, ByRef rc As RECT) As Boolean
End Function

<DllImport("user32.dll")> _
Shared Function MoveWindow(hWnd As IntPtr, x As Integer, y As Integer, w As Integer, h As Integer, repaint As Boolean) As Boolean
End Function

Structure RECT
    Public Left As Integer
    Public Top As Integer
    Public Right As Integer
    Public Bottom As Integer
End Structure

Public Sub New(owner As Form, Optional Custom_Font As Font = Nothing)
    mOwner = owner
    mFont = Custom_Font
    owner.BeginInvoke(New MethodInvoker(AddressOf findDialog))
End Sub

Private Sub findDialog()

    ' Enumerate windows to find the message box
    If mTries < 0 Then
        Return
    End If

    Dim callback As New EnumThreadWndProc(AddressOf checkWindow)

    If EnumThreadWindows(GetCurrentThreadId(), callback, IntPtr.Zero) Then
        If System.Threading.Interlocked.Increment(mTries) < 10 Then
            mOwner.BeginInvoke(New MethodInvoker(AddressOf findDialog))
        End If
    End If

End Sub

Private Function checkWindow(hWnd As IntPtr, lp As IntPtr) As Boolean

    ' Checks if <hWnd> is a dialog
    Dim sb As New StringBuilder(260)
    GetClassName(hWnd, sb, sb.Capacity)
    If sb.ToString() <> "#32770" Then Return True

    ' Got it, get the STATIC control that displays the text
    Dim hText As IntPtr = GetDlgItem(hWnd, &HFFFF)

    Dim frmRect As New Rectangle(mOwner.Location, mOwner.Size)
    Dim dlgRect As RECT
    GetWindowRect(hWnd, 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)
    If hText <> IntPtr.Zero Then

        If mFont Is Nothing Then
            ' Get the current font
            mFont = Font.FromHfont(SendMessage(hText, WM_GETFONT, IntPtr.Zero, IntPtr.Zero))
        End If

        SendMessage(hText, WM_SETFONT, mFont.ToHfont(), New IntPtr(1))

    End If

    ' Done
    Return False

End Function

Public Sub Dispose() Implements IDisposable.Dispose
    mTries = -1
    mOwner = Nothing
    If mFont IsNot Nothing Then mFont.Dispose()
End Sub

End Class

用法:

Using New CustomMessageBox(Me, New Font(New FontFamily("Lucida Console"), Font.SizeInPoints, FontStyle.Bold))
    MessageBox.Show("Test Text", "Test Title", MessageBoxButtons.OK)
End Using

【讨论】:

  • 当我将字体大小增加到 14 时。有些线条不可见。我在要显示的消息中有 3 行?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多