Results 1 to 9 of 9

Thread: [RESOLVED] I don't understand why this function doesn't work properly.

  1. #1

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Skunthorpe, North Lincolnshire, England.
    Posts
    1,871

    Resolved [RESOLVED] I don't understand why this function doesn't work properly.

    Hi,
    I can't understand why this function doesn't give the correct result.

    It's supposed to generate a label of a specific size. (it does)
    It's supposed to set a specific font, family and size. (it does)
    It's supposed to add text. (it does)
    It's supposed to add the label to Form1 and show it. (it does)
    It's supposed to measure the length of the text. (it seems to)
    It's supposed to reduce the font size until the text fits inside the Label. (it seems to)
    It's supposed to return the font size. (it seems to)
    It's supposed to show that the same text inside the Label with the same font and font size, fits inside the Label. (it fails miserably)


    Code:
        Private Function Fitter(ByVal Siz As Int32) As Int32
            Dim Flag As Boolean = True
            Dim Measured, TxtSize As Int32, Box As New Label
    
            Box.Width = Siz
            Box.Height = Siz
            Box.Text = "90"
            Box.BorderStyle = BorderStyle.FixedSingle
            Box.TextAlign = ContentAlignment.MiddleCenter
            Box.AutoSize = False
            Box.Location = New Point(5, 5)
            Me.Controls.Add(Box)
            Box.Show()
            TxtSize = 60
    
            While Flag
                Box.Font = New Font("Arial Narrow", TxtSize, FontStyle.Bold, GraphicsUnit.Pixel)
                Using gr As Graphics = Box.CreateGraphics
                    Measured = CInt(gr.MeasureString(Box.Text, Box.Font).Width)
                End Using
                If Measured < Siz Or TxtSize < 21 Then Flag = False
                If TxtSize > 20 Then TxtSize -= 1
            End While
    
            'Me.Controls.Remove(Box)       'Temporarily excluded for the test.
    
            Return TxtSize '( 1 px less than measured.)
    
        End Function
    
       'Quick Sub to test the function.
        Private Sub Ninety()
            Dim a, b, c, sz, x, y As Integer
    
            a = (Me.Width) / 10
            b = a - 5
            c = 0
            x = 20
            y = 10
    
            sz = Fitter(b)
            TextBox1.Text = sz.ToString
    
        End Sub
    Poppa.


    PS. Further testing has shown that changing the set text from '90' to '90.' gives the result that I'm after, but I still don't understand why it doesn't work properly.

    Pop.
    Last edited by Poppa Mintin; Sep 23rd, 2016 at 06:57 AM. Reason: PS Added
    Along with the sunshine there has to be a little rain sometime.

  2. #2
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,580

    Re: I don't understand why this function doesn't work properly.

    Well, that's a very inefficient way to go about doing this, not to mention messy.

    My best guess is Fitter(), as written, ignores that MeasureString() returns a SizeF() and casts the floating-point values to Integer. That will introduce some inaccuracies that might crop up when displaying the text. For example, I modified it to print out its trials and saw this as it tried to return:
    Code:
    Fitter: 29.27409 vs. 29
    This suggests it will take 29.27 pixels to display the string. But you can't really use partial pixels, you have to use full pixels. So that's really still a measurement of 30. 30 happened to be the "test width" in my trial. So the string's at least a pixel wider than the label, and it ends up getting wrapped. When I switched the algorithm to use Math.Ceiling, I got more reliable results.

    In general, my experience is you should still add a few pixels to your width, because in various circumstances the metrics you get back from MeasureString() aren't the same. Blame MS for arbitrarily using GDI or GDI+ when rendering.

    But really, your algorithm creates and destroys a control and graphics context for no real reason. I think this is a more elegant implementation of the same thing. TextRenderer.MeasureText() lets you measure things without having to create a Graphics context of your own. Interestingly enough, it returns a Size instead of SizeF, so there's no need to worry about the rounding.

    Code:
    Function GetFontFitSize(ByVal text As String, ByVal width As Integer, ByVal basisFont As Font) As Integer
        Dim smallestFontSize As Integer = 20
        Dim startingFontSize As Integer = 100
        Dim currentFontSize As Integer = startingFontSize
    
        While True
            Dim currentMeasuredWidth As Integer = 0
            Using testFont As New Font(basisFont.FontFamily, currentFontSize, basisFont.Style, GraphicsUnit.Pixel)
                Dim desiredSize = TextRenderer.MeasureText(text, testFont)
                Console.WriteLine("Desired size @ {0}: {1}", currentFontSize, desiredSize)
                currentMeasuredWidth = desiredSize.Width
            End Using
    
            If currentMeasuredWidth > width AndAlso currentFontSize >= smallestFontSize Then
                currentFontSize -= 1
                Continue While
            End If
    
            Exit While
        End While
    
        Console.WriteLine("Returning {0}.", currentFontSize)
        Return currentFontSize
    End Function
    
    Private Sub GOOD_NAME_C2D33505_8C9E_4BD9_964D_FA20FE97FB05()
        Dim testWidth As Integer = Me.Width \ 10
        Label1.AutoSize = False
        Label1.Size = New Size(testWidth, testWidth)
        Label1.BackColor = Color.Green
        Label1.Text = "90"
    
        Dim correctFontSize = GetFontFitSize(Label1.Text, testWidth, Label1.Font)
        Label1.Font = New Font(Label1.Font.FontFamily, correctFontSize, Label1.Font.Style, GraphicsUnit.Pixel)
    End Sub
    I, too, lament the absence of an auto-fitting label in Windows (or just about any platform.)

  3. #3

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Skunthorpe, North Lincolnshire, England.
    Posts
    1,871

    Re: I don't understand why this function doesn't work properly.

    Quote Originally Posted by Sitten Spynne View Post
    Well, that's a very inefficient way to go about doing this, not to mention messy.

    My best guess is Fitter(), as written, ignores that MeasureString() returns a SizeF() and casts the floating-point values to Integer. That will introduce some inaccuracies that might crop up when displaying the text. For example, I modified it to print out its trials and saw this as it tried to return:
    Fitter: 29.27409 vs. 29
    This suggests it will take 29.27 pixels to display the string. But you can't really use partial pixels, you have to use full pixels. So that's really still a measurement of 30. 30 happened to be the "test width" in my trial. So the string's at least a pixel wider than the label, and it ends up getting wrapped. When I switched the algorithm to use Math.Ceiling, I got more reliable results.

    In general, my experience is you should still add a few pixels to your width, because in various circumstances the metrics you get back from MeasureString() aren't the same. Blame MS for arbitrarily using GDI or GDI+ when rendering.

    But really, your algorithm creates and destroys a control and graphics context for no real reason. I think this is a more elegant implementation of the same thing. TextRenderer.MeasureText() lets you measure things without having to create a Graphics context of your own. Interestingly enough, it returns a Size instead of SizeF, so there's no need to worry about the rounding.

    I, too, lament the absence of an auto-fitting label in Windows (or just about any platform.)
    Thanks Sitten Spynne,

    I'm good at inefficient, and at messy too.

    But really, your algorithm creates and destroys a control and graphics context for no real reason.
    I can see that it looks that way... But: -

    1. I don't know what size the size the label will be because the form will be resized at run time.
    2. There will be ninety labels and I want the labels to be populated with their text already showing at it's largest when the form is shown.

    So I make just one label, the same size as the ninety will be, enter the widest text and measure it. Then I remove the 'test' label.
    Having slept on the problem, it did occur to me that maybe I should use a Double for the measurement, (it turns out that it should be a Single anyway) and then maybe add one, but I figured that since I returned a font height one pixel less than I'd measured, that ought to take care of that. Looks like that's wrong, it's even wrong until I return the pixel size as 5 less than measured.
    But you can't really use partial pixels, you have to use full pixels.
    I changed from Integer to Single, and tried adding 1 to the measured size, but again, I had to add 5 (and still subtract 1 from the text height) before the text fitted.
    Incidentally, it's not just the widest texts which don't fit, the single digits do but not even '11' fits the label until I doctor the result, then everything fits.

    I'm going to try your code now, I'll let you know how it performs.

    Poppa.

    PS, I forgot to mention that newly added, the Padding is set to All = 0.

    Pop.
    Last edited by Poppa Mintin; Sep 24th, 2016 at 08:32 AM. Reason: PS added.
    Along with the sunshine there has to be a little rain sometime.

  4. #4
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,580

    Re: I don't understand why this function doesn't work properly.

    Quote Originally Posted by Poppa Mintin View Post
    I can see that it looks that way... But: -

    1. I don't know what size the size the label will be because the form will be resized at run time.
    2. There will be ninety labels and I want the labels to be populated with their text already showing at it's largest when the form is shown.
    None of this matters, for a few reasons. The easiest are:
    • You could be using Me.CreateGraphics(), because the form is very likely to exist if you're writing code in the context of the form. This way you don't have to create a control, add it, and remove it. You didn't dispose it, either. That can turn out to be a problem. Same with the Font, which I didn't dispose either because it's a little awkward.
    • You could be overriding OnPaint() in a custom control and measuring/drawing the text yourself. Then you already have a Graphics context and don't need CreateGraphics. My gut tells me this is a little more reliable than TextRenderer.MeasureText(), and I don't want to go into why because it borders on superstition.
    • TextRenderer.MeasureText() doesn't ask you to provide a Graphics context, though I'm suspicious it creates one of its own.

  5. #5
    PowerPoster
    Join Date
    Oct 2010
    Posts
    2,141

    Re: I don't understand why this function doesn't work properly.

    Since you are using the Label control, there is no need for you to be measuring the text yourself. The Label's internal layout computations can be used to retrieve what width is needed to accommodate the set Text/Font properties.

    All controls expose a GetPreferredSize method. In the case of the Label control, this method is used internally to allow it to auto-size itself, but you can call it even if AutoSize is set to False. The Label control also exposes two additional properties PreferredWidth and PreferredHeight that get call GetPreferredSize and return the requested dimension.

    Here is my version of this:

    VB.Net Code:
    1. Private Function GetSquareLabel(displayText As String,
    2.                                           sideLength As Int32,
    3.                                           baseFont As Font,
    4.                                           Optional locationX As Int32 = 0,
    5.                                           Optional locationY As Int32 = 0,
    6.                                           Optional minFontHeightPx As Int32 = 20) As Label
    7.     Dim lbl As New Label
    8.     With lbl
    9.         .Text = displayText
    10.         .AutoSize = False
    11.         .Size = New Size(sideLength, sideLength)
    12.         .Location = New Point(locationX, locationY)
    13.         .TextAlign = ContentAlignment.MiddleCenter
    14.         .BorderStyle = BorderStyle.FixedSingle
    15.     End With
    16.  
    17.     ' use a bisection method to find the max font size constrained by minFontHeightPx
    18.     Dim lo As Int32 = minFontHeightPx
    19.     Dim hi As Int32 = Math.Max(sideLength, minFontHeightPx)
    20.     Dim zero As New Size    ' used to get preferred size
    21.  
    22.     Dim current As Int32
    23.     Dim last As Int32 = -1
    24.  
    25.     While current <> last
    26.         last = current
    27.         current = (lo + hi) \ 2
    28.  
    29.         Using tmpFont As Font = lbl.Font
    30.             lbl.Font = New Font(lbl.Font.FontFamily.Name, current, lbl.Font.Style, GraphicsUnit.Pixel)
    31.         End Using
    32.  
    33.         Select Case lbl.PreferredWidth
    34.             Case Is > sideLength
    35.                 hi = current
    36.                 If hi < minFontHeightPx Then hi = minFontHeightPx
    37.             Case Is < sideLength
    38.                 lo = current
    39.             Case Else ' = sideLength
    40.                 Exit While
    41.         End Select
    42.     End While
    43.  
    44.     Return lbl
    45. End Function

  6. #6

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Skunthorpe, North Lincolnshire, England.
    Posts
    1,871

    Re: I don't understand why this function doesn't work properly.

    Thanks TnTinMN,

    Just going to try your code, it looks as though it's written for a square label, just wondering if a label longer then wide, or even vice versa would prohibit it's use ?


    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  7. #7
    PowerPoster
    Join Date
    Oct 2010
    Posts
    2,141

    Re: I don't understand why this function doesn't work properly.

    Quote Originally Posted by Poppa Mintin View Post
    Just going to try your code, it looks as though it's written for a square label, just wondering if a label longer then wide, or even vice versa would prohibit it's use ?
    It was written for a square label because that is what your code implied was your goal.
    Private Function Fitter(ByVal Siz As Int32) As Int32
    Dim Flag As Boolean = True
    Dim Measured, TxtSize As Int32, Box As New Label

    Box.Width = Siz
    Box.Height = Siz
    You could modify it to impose a font height height constraint. If the height constraint code is applied after the width constraint code, I doubt that would ever have much to do as most text is wider than tall unless you are going for multiline text.

  8. #8

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Skunthorpe, North Lincolnshire, England.
    Posts
    1,871

    Re: I don't understand why this function doesn't work properly.

    Quote Originally Posted by TnTinMN View Post
    It was written for a square label because that is what your code implied was your goal.
    You could modify it to impose a font height height constraint. If the height constraint code is applied after the width constraint code, I doubt that would ever have much to do as most text is wider than tall unless you are going for multiline text.
    Yes, I guessed that's what you'd thought. It didn't matter at what size the label starts because when the form is resized according to the resolution of the screen that the application's form may be running on, the aspect ratio of the label can change. My labels are 'AutoSize = False' which probably makes 'em multiline but the texts don't get longer then 2 characters. ("1" to "90"), so although ideally the labels are square erring on the wider than higher side.

    Poppa.
    Along with the sunshine there has to be a little rain sometime.

  9. #9

    Thread Starter
    Frenzied Member Poppa Mintin's Avatar
    Join Date
    Mar 2009
    Location
    Skunthorpe, North Lincolnshire, England.
    Posts
    1,871

    Re: I don't understand why this function doesn't work properly.

    Hi, Just to close this thread.

    I've not been able to get any of these schemes to work so I've devised something else: -

    I figured that since the font size is regulated by it's height, I'd find the largest size that would fit the widest text by trial and error at design time. For this application, in a TLP cell the label is originally 64px wide by 61px high, and the largest I could set the font height for Arial Narrow turned out to be 41px, So: -

    The original ratio between label height and maximum text height is 41 / 61 (0.67213114754098360655737704918033) Say... 0.672.
    My application now measures the height of that label after it's been re-sized and multiplies it by O.672 to give the new font height. Seems to work.

    Poppa.
    Along with the sunshine there has to be a little rain sometime.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width