【问题标题】:Visual Studio: Setting Textbox Width, given a MONSPACED font and a MaxLengthVisual Studio:设置文本框宽度,给定一个 MONSPACED 字体和一个 MaxLength
【发布时间】:2017-10-14 08:38:47
【问题描述】:

我正在将 Visual Studio 2013 用于表单应用程序。

对于诸如“名字”之类的短数据输入字段,我喜欢在文本框中使用等宽字体,其宽度刚好足以容纳允许的字符数。 (MaxLength 属性)这样用户可以直观地看到允许的字符数。

但是...我最新的前端允许用户“放大或缩小”不同的屏幕分辨率和可见性。所以为了达到同样的效果,我必须根据字体大小来改变文本框的宽度。

我原以为 MeasureString 会是答案,(增加了一些宽度以适应文本光标和边框)但我得到的结果好坏参半。我了解 MeasureString 的问题之一是它旨在适应字距调整。 ...但这就是我使用等宽字体的原因。据我所知,字距调整应该无关紧要。

我只需要一个函数,给定一个 MONOSPACED 字体,返回单个字符的宽度(以像素为单位)。听起来比实际上要容易。

有什么指导吗?

【问题讨论】:

  • MeasureString 几乎不是答案。要测量 TextBox 的文本,您必须使用 TextRenderer.MeasureText()。并处理它添加字形悬垂肘部空间的诀窍,字符串越长,它就越准确。
  • 谢谢。我在想无论我使用什么方法,我都可以将“01234567890”的宽度与“00”的宽度进行比较,这应该是 9 个字符的宽度,而不需要在两端添加任何内容。 (然后除以 9。)

标签: .net vb.net visual-studio fonts


【解决方案1】:

这是“一个答案”,因为对我来说它似乎效果最好,但我不会称它为“正确答案”,因为它有点代码密集。我对以下所有内容感到失望:

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 属性。 (这对多行文本框不利。)

要以最佳方式使用此代码,所有具有相同字体大小的控件都应一起通过它,这样它将跳过不必要的计算和文本框渲染。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-03
    • 2015-06-16
    • 2011-03-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多