MessageBox与自定义字体?

时间:2013-10-06 02:03:06

标签: .net vb.net winforms fonts messagebox

是否有一种简单的方法来显示MessageBox的自定义字体?

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

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

我希望如果可能添加一个通用参数来实例这个伟大的自定义消息框设置所需的字体:

原始代码是 @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的代码片段为中心的消息框,我需要启动它(实例化)但是使用自定义字体。

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

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

3 个答案:

答案 0 :(得分:3)

我已经回答了这个问题,答案为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是唯一的解决方法。

答案 1 :(得分:1)

首先,我为没有在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

答案 2 :(得分:0)

这是代码!

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

' 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