这是“一个答案”,因为对我来说它似乎效果最好,但我不会称它为“正确答案”,因为它有点代码密集。我对以下所有内容感到失望:
Graphics.MeasureString()
TextRenderer.MeaureText()
Graphics.MeasureCharacterRanges()
我已经尝试了 CompositingQuality、TextRenderingHint 和 SmoothingMode,直到我脸色发青。对于不同的字体大小、单位和重要的是,这些方法似乎都没有产生一致的结果,用户的 Windows 显示设置为小、中或大文本。
在我看来,问题在于所有这些方法都使用“绘制”文本来估计文本的宽度,但在控件中呈现的文本并未完成以同样的方式。因此,如果这是真的,使用绘制的文本来估计控件中文本的宽度永远不会完美。我本以为微软会提供一些东西来轻松完美地做到这一点,我可能错了,但据我观察,微软还没有做到这一点.
以下是我发现无论窗口设置、字体单位或任何与图形对象有关的代码都非常准确的代码。它创建一个不可见的多行 TextBox,然后将其 Width 属性一次缩小一个像素,直到它强制文本换行,并记下结果宽度。
要使用此代码:
- 在您的文本框和组合框中使用等宽字体。我使用的 Lucida Console 看起来不错,可以容纳仍然支持的任何 Windows 版本。
- 调用 ResizeControlForFont 为 TextBox 或 ComboBox 调整其预设字体的大小。
- 如果您想一步调整控件的字体大小和宽度,请调用 SetFontSizeAndResizeControl。
.
Public Sub ResizeControlForFont(ByVal Cont As Control, Optional ByVal WidthInCharacters As Integer = 0)
'This for single or multiline textboxes and comboboxes.
'A MONOSPACED font must first be assigned to the control before calling this.
'If you prefer to do it in a single step, call SetFontSizeAndResizeControl, which will call this procedure.
'If WidthInCharacters is not specified, it will use the MaxLength property of the control, if possible.
'For multiline textboxes, ALWAYS specify the number of columns desired when calling this procedure. You don't want to use the MaxLength property of a multiline textbox.
Dim CharacterWidth As Single = GetWidthOfCharacter(Cont.Font)
Dim AddedWidth As Integer = (CharacterWidth * 0.3) + 2 'I've chosen 30% of a character's width plus 2 pixels and I like the results.
'If not supplied, use the MaxLength property if available and appropriate.
If WidthInCharacters = 0 Then
If TypeOf Cont Is TextBox Then
Dim tmpItem As TextBox = Cont
If tmpItem.Multiline = False Then
'Don't do this for multiline textboxes.
WidthInCharacters = tmpItem.MaxLength
End If
ElseIf TypeOf Cont Is ComboBox Then
Dim tmpItem As ComboBox = Cont
WidthInCharacters = tmpItem.MaxLength
End If
End If
If TypeOf Cont Is TextBox Then
'For multiline textboxes, Always specify the number of columns desired.
Dim tmpItem As TextBox = Cont
If tmpItem.Multiline = True Then
'Only do this for multiline textboxes.
AddedWidth = AddedWidth + SystemInformation.VerticalScrollBarWidth
End If
ElseIf TypeOf Cont Is ComboBox Then
Dim tmpItem As ComboBox = Cont
AddedWidth = AddedWidth + SystemInformation.VerticalScrollBarWidth
End If
Cont.Width = (WidthInCharacters * CharacterWidth) + AddedWidth
End Sub
Public Sub SetFontSizeAndResizeControl(ByVal Cont As Control, ByVal NewFontSize As Single, Optional ByVal WidthInCharacters As Integer = 0)
'Sets the font size and resizes it in one step.
Cont.Font = New Font(Cont.Font.Name, NewFontSize, Cont.Font.Style, Cont.Font.Unit, Cont.Font.GdiCharSet, Cont.Font.GdiVerticalFont)
Call ResizeControlForFont(Cont, WidthInCharacters)
End Sub
Public Function GetWidthOfCharacter(ByVal CtrlFont As Font) As Single
Static LastResult As Single
Static LastCtrlFont As Font
'OPTIMIZING. Don't repeat what you don't have to.
'Sending the same font to this function twice in a row will skip the test and return the last result.
'To take advantage of this, resize all controls with the same font and size all together.
If Not LastCtrlFont Is Nothing Then
If CtrlFont.Name = LastCtrlFont.Name And CtrlFont.Size = LastCtrlFont.Size And CtrlFont.Style = LastCtrlFont.Style And CtrlFont.GdiCharSet = LastCtrlFont.GdiCharSet And CtrlFont.GdiVerticalFont = LastCtrlFont.GdiVerticalFont Then
Return LastResult 'Speeds things up if the same font was used last time. Useful when sizing many controls with the same font.
Exit Function
End If
End If
Dim TinyString As String = StrDup(51, "W") 'Two strings of aribitrary length, ample for the precision we need.
Dim BigString As String = StrDup(99, "W") '
Dim tmpTxt As New TextBox 'Temporarily created textbox.
With tmpTxt
.Font = CtrlFont
.Height = .Height * 2.5 'Just ensures it's tall enough to work.
.Multiline = True
.ScrollBars = ScrollBars.Vertical
.Font = CtrlFont
'Do this twice. First with TinyString.
.Width = TextRenderer.MeasureText(TinyString, CtrlFont).Width + SystemInformation.VerticalScrollBarWidth 'Start by making it a little too big using MeasureText
.Text = TinyString
Do Until .GetLineFromCharIndex(.TextLength) > 0 Or .Width = SystemInformation.VerticalScrollBarWidth
.Width = .Width - 1
Loop
Dim TinyWidth As Integer = .Width
'Do it again with BigString.
.Width = TextRenderer.MeasureText(BigString, CtrlFont).Width + SystemInformation.VerticalScrollBarWidth 'Start by making it a little too big using MeasureText
.Text = BigString
Do Until .GetLineFromCharIndex(.TextLength) > 0 Or .Width = SystemInformation.VerticalScrollBarWidth
.Width = .Width - 1
Loop
Dim BigWidth As Integer = .Width
'We did it twice to isolate the width of characters from width added by padding, borders and the scrollbar.
'We compare the two results, leaving only a difference in width created by the characters themselves.
'The Denominator here is the difference in the number of characters.
LastResult = (BigWidth - TinyWidth) / (Len(BigString) - Len(TinyString))
LastCtrlFont = CtrlFont 'Remember this so we can skip it if we try the same font again next time
Return LastResult
End With
End Function
这是正确的方法吗?好吧,这有点代码密集型。但这是我发现的唯一能产生完全正确和一致结果的方法。如果微软有更好的方法,我愿意接受。但我对 Graphics.MeasureString()、TextRenderer.MeaureText() 和 很失望>Graphics.MeasureCharacterRanges()
虽然我开始使用单行文本框,但它可以与组合框和多行文本框一起使用,它会为下拉按钮或垂直滚动条添加宽度。在后一种情况下,您需要传递所需的列数。在所有情况下,如果您不提供字符宽度,它就会使用控件的 MaxLength 属性。 (这对多行文本框不利。)
要以最佳方式使用此代码,所有具有相同字体大小的控件都应一起通过它,这样它将跳过不必要的计算和文本框渲染。