我正在使用visual studio 2013作为表单应用。
对于短名称输入字段,例如“First Name”,我喜欢在文本框中使用等宽字体,其宽度足以容许允许的字符数。 (MaxLength属性)这样用户可以直观地看到允许的字符数。
但是......我的最新前端允许用户“放大或缩小”以获得不同的屏幕分辨率和可见度。因此,要实现相同的结果,我必须根据字体大小更改文本框的宽度。
我原以为MeasureString就是答案,(有一些额外的宽度可以容纳文本光标和边框)但是我的结果好坏参半。我理解MeasureString的一个问题是它的设计是为了适应字距调整。 ...但这就是我使用等宽字体的原因。就我所知,Kerning无关紧要。
我需要的是一个函数,给定MONOSPACED字体,返回单个字符的宽度(以像素为单位)。听起来比现在更容易。
任何指导?
答案 0 :(得分:0)
这是"答案",因为对我来说它似乎效果最好,但我不会称之为"正确答案"因为它有点代码密集。我对以下所有内容感到失望:
Graphics.MeasureString()
TextRenderer.MeaureText()
Graphics.MeasureCharacterRanges()
我已经尝试过CompositingQuality,TextRenderingHint和SmoothingMode,直到我脸红了。这些方法似乎都不能为不同的字体大小,单位和 重要 产生一致的结果,用户的小型,中型或大型文本的Windows显示设置
在我看来,问题是所有这些方法都使用" draw"文本到 估计 文本的宽度,但在控件中呈现的文本不会以相同的方式完成。因此,如果这是真的,使用绘制的文本来估计控件中文本的宽度永远不会完美。我本以为微软会提供一些容易做到这一点 和完美 ,而我可能错了,但从我观察到的情况来看,微软还没有得到了它。
下面是我发现的代码非常准确,无论Windows设置,字体单位或与图形对象有什么关系。它会创建一个不可见的多线TextBox,然后一次将其Width属性缩小一个像素,直到它强制文本换行,并记下结果宽度。
使用此代码:
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() 强>
虽然我是针对单行文本框启动的,但它可以与ComboBoxes以及多行TextBox一起使用,它将为下拉按钮或垂直滚动条添加宽度。在后一种情况下,您希望传递所需数量的列。在所有情况下,如果您没有提供字符宽度,它将使用控件的MaxLength属性。 (这对多行文本框不利。)
要以最佳方式使用此代码,所有具有相同字体大小的控件应该一起传递,以便跳过不必要的计算和文本框渲染。