Jump to content

Size label to fit text - amended 17 Dec 08


Melba23
 Share

Recommended Posts

  • Moderators

Edit 17 Dec 08: UDF amended to use _WinAPI_GetTextExtentPoint32 (many thanks to Zedna for the suggestion) which removes the need to distinguish mono/prop fonts and to measure each character in the relevant font.

There have been a number of threads recently about sizing a label or GUI to fit a given text exactly. The sort of thing that the Windows MsgBox does so nicely to the text it holds.

I have joined in many of these threads as I have been looking for something like this for quite a while. But all the various suggested solutions were only really valid for non-wrapped text - while I wanted something that would accept a maximum width limitation so that the resulting label/GUI was not as wide as the screen! I did not want to have to guess at line lengths to fit - too often it did not work or looked pretty awful. After numerous attempts, including SWAG*ing the line length and using GDI to draw the lines (some of which have appeared on the forums), I believe I have found a good working solution. By which I mean I have not been able to break it yet!

The function takes 4 parameters: the text to display, the font size and face, and the max width. It returns an array which gives the size of the label needed to hold the text and the height of a single line in that font size and face.

; #include-once

#include <WindowsConstants.au3>
#include <SendMessage.au3>
#include <WinAPI.au3>

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


~~~~~
; Example

Global $hTest_GUI, $iTest_Width

$hTest_GUI = GUICreate("Size Label Test", 600, 800)
GUISetState()

$iTest_Width = 350
_Labels()

$iTest_Width = 500
_Labels()

$iTest_Width = 150
_Labels()

GUIDelete($hTest_GUI)

Exit

; --------

Func _Labels()

    Local $sMsg

    $sMsg  = "This is a long line - this is a long line - this is a long line!" & @CRLF & @CRLF
    $sMsg &= "This is an even longer long line - this is an even longer long line - this is an even longer long line!" & @CRLF & @CRLF
    $sMsg &= "And this is a little one"
    _Choose_Font($sMsg)

    $sMsg  = "This is a short line" & @CRLF & @CRLF
    $sMsg &= "This is a very long line indeed - this is a very long line indeed - this is a very long line indeed - this is a very long line indeed - "
    $sMsg &= "this is a very long line indeed - this is a very long line indeed - this is a very long line indeed - and this is the end!"
    _Choose_Font($sMsg)

    $sMsg  = "This line contains a long word - generalisation" & @CRLF & @CRLF
    $sMsg &= "If the width is narrow and the font size large, it may not fit in the label" & @CRLF & @CRLF
    $sMsg &= "In that case, the label will not be displayed and the error will be set to 2 to indicate that there is a font size / width mismatch" & @CRLF & @CRLF
    $sMsg &= "There are now a number of blank lines" & @CRLF & @CRLF & @CRLF & @CRLF & @CRLF & @CRLF & @CRLF & @CRLF 
    $sMsg &= "But the UDF still manages to cope, wrapping the lines as required to fit into the width requested and determining the correct height"
    _Choose_Font($sMsg)

    $sMsg  = "This is only a short line" & @CRLF & @CRLF
    $sMsg &= "This is only a short line" & @CRLF & @CRLF
    $sMsg &= "This is only a short line" & @CRLF & @CRLF
    $sMsg &= "This is only a short line"
    _Choose_Font($sMsg)

EndFunc

; -------

Func _Choose_Font($sMsg)
    
    Local $sFont

    $sFont = "Segoe UI"
    _Choose_Size($sMsg, $sFont)

    $sFont = "Tahoma"
    _Choose_Size($sMsg, $sFont)

    $sFont = "Arial"
    _Choose_Size($sMsg, $sFont)

    $sFont = "Microsoft Sans Serif"
    _Choose_Size($sMsg, $sFont)

    $sFont = "Comic Sans MS"
    _Choose_Size($sMsg, $sFont)

    $sFont = "Courier New"
    _Choose_Size($sMsg, $sFont)

    $sFont = "Lucida Console"
    _Choose_Size($sMsg, $sFont)

EndFunc

; -------

Func _Choose_Size($sMsg, $sFont)
    
    Local $iSize

    $iSize = 9
    _Size_Label($sMsg, $iSize, $sFont)

    $iSize = 10
    _Size_Label($sMsg, $iSize, $sFont)

    $iSize = 11
    _Size_Label($sMsg, $iSize, $sFont)

    $iSize = 12
    _Size_Label($sMsg, $iSize, $sFont)

    $iSize = 13
    _Size_Label($sMsg, $iSize, $sFont)

    $iSize = 14
    _Size_Label($sMsg, $iSize, $sFont)

    $iSize = 15
    _Size_Label($sMsg, $iSize, $sFont)

EndFunc

; -------

Func _Size_Label($sMsg, $iSize, $sFont)
    
    Local $aLabel_Info[4], $error, $hLabel

    GUISetFont($iSize, 400, 0, $sFont, $hTest_GUI)

    $aLabel_Info =_Label_Size($sMsg, $iSize, $sFont, $iTest_Width)

    $error = @error
    If $error Then
        ConsoleWrite($sFont & " " & $iSize & " : Error = " & @error & @CRLF)
        Sleep(1000)
        Return
    Else
        ConsoleWrite($sFont & " " & $iSize & @CRLF)
        $hLabel = GUICtrlCreateLabel($sMsg, 10, 10, $aLabel_Info[2], $aLabel_Info[3])
        If $iTest_Width < 200 Then
            GUICtrlSetBkColor(-1, 0xFFFF00)
        ElseIf $iTest_Width < 400 Then
            GUICtrlSetBkColor(-1, 0x80FF80)
        Else
            GUICtrlSetBkColor(-1, 0x80FFFF)
        EndIf
        Sleep(1000)
        GUICtrlDelete($hLabel)
    EndIf

EndFunc

; End of example
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


~~~~~

; #FUNCTION# =======================================================================================
; Name............: _Label_Size
; Description ....: Returns size of label required for a text, even if wrapped to a set width
; Syntax..........: _Label_Size($sText, $iFont_Size, $sFont_Name[, $iWidth])
; Parameters .....: $sText      -> Text to display with @CRLF line endings
;                  $iFont_Size  -> Font size in points
;                  $sFont_Name  -> Font name
;                  $iWidth      -> Max width of the label - default is width of desktop
; Requirement(s)..: v3.2.12.1 or higher
; Return values ..: Success:    Returns array with details of label required for text
;                              $array[0] = Number of unwrapped lines in text
;                              $array[1] = Height of single line of text
;                              $array[2] = Width of label required to hold text
;                              $array[3] = Height of label required to hold text
;                  Failure: Returns 0
;                              - Error 1 - Failure to create GUI to test label size
;                              - Error 2 - Font too large for width - longest word will not fit
; Author .........: Melba23
; Example.........; Yes
;===================================================================================================



Func _Label_Size($sText, $iFont_Size, $sFont_Name, $iWidth = @DesktopWidth)
    
    Local $hWnd, $hFont, $hDC, $tSize, $hGUI, $hText_Label, $sTest_Line
    Local $iStart_Code, $iEnd_Code, $iFont_Width, $iLine_Count, $iLine_Width, $iWrap_Count, $iLast_Word
    Local $aLines[1], $aLabel_Info[4], $aPos[4], $aInfo[3]

  ; Create GUI
    $hGUI = GUICreate("", 800, 1000, 800, 10)
        If $hGUI = 0 Then Return SetError(1, 0, 0)
        GUISetFont($iFont_Size, 400, 0, $sFont_Name)

  ; Break text into lines
    $aLines = StringSplit($sText, @CRLF, 1)
  ; Store number of lines
    $aLabel_Info[0] = $aLines[0]
    
  ; Draw label with unwrapped lines to check on max width
    $hText_Label = GUICtrlCreateLabel($sText, 10, 10)
    $aPos = ControlGetPos($hGUI, "", $hText_Label)
    GUICtrlDelete($hText_Label)
  ; Store line height for this font size after removing label padding (always 8)
    $aLabel_Info[1] = ($aPos[3] - 8)/ $aLines[0]
    $aLabel_Info[2] = $aPos[2]
    $aLabel_Info[3] = $aPos[3]
    
  ; Now see if wrapping is required
    If $aPos[2] > $iWidth Then

      ; Set width element to max allowed
        $aLabel_Info[2] = $iWidth
        
      ; Set line count to zero
        $iLine_Count = 0
        
      ; Take each line in turn
        For $j = 1 To $aLines[0]
            
          ; Size this line
            $hText_Label = GUICtrlCreateLabel($aLines[$j], 10, 10)
            $aPos = ControlGetPos($hGUI, "", $hText_Label)
            GUICtrlDelete($hText_Label)
            
          ; Check wrap status
            If $aPos[2] < $iWidth Then
              ; No wrap needed so count line and move on
                $iLine_Count += 1
            Else
              ; Wrap needed so need to count wrapped lines
                
              ; Create label
                $hText_Label = GUICtrlCreateLabel("", 0, 0)
              ; Initialise Point32 method
                $hWnd = ControlGetHandle($hGui, "", $hText_Label)
                $hFont = _SendMessage($hWnd, $WM_GETFONT)
                $hDC = _WinAPI_GetDC($hWnd)
                _WinAPI_SelectObject($hDC, $hFont)
                
              ; Zero counter
                $iWrap_Count = 0

                While 1
                    
                  ; Set line width to 0
                    $iLine_Width = 0
                  ; Initialise pointer for end of word
                    $iLast_Word = 0
                    
                    For $i = 1 To StringLen($aLines[$j])

                      ; Is this just past a word ending?
                        If StringMid($aLines[$j], $i, 1) = " " Then $iLast_Word = $i - 1
                        
                      ; Increase line by one character
                        $sTest_Line = StringMid($aLines[$j], 1, $i)
                      ; Place line in label
                        GUICtrlSetData($hText_Label, $sTest_Line)
                      ;ConsoleWrite($sTest_Line & @CRLF)
                      ; Get line length
                        $tSize = _WinAPI_GetTextExtentPoint32($hDC, $sTest_Line)
                        $iLine_Width = DllStructGetData($tSize, "X")
                      ;ConsoleWrite($iLine_Width & @CRLF)
                      ; If too long exit the loop
                        If $iLine_Width > $iWidth Then ExitLoop
                    Next
                        
                  ; End of the line of text?
                    If $i > StringLen($aLines[$j]) Then
                      ; Yes, so add final line to count
                        $iWrap_Count += 1
                        ExitLoop
                    Else
                      ; No, but add line just completed to count
                        $iWrap_Count += 1
                      ; Check at least 1 word completed or return error
                        If $iLast_Word = 0 Then
                            GUIDelete($hGUI)
                            Return SetError(2, 0, 0)
                        EndIf
                      ; Strip string to point reached
                        $aLines[$j] = StringTrimLeft($aLines[$j], $iLast_Word)
                      ; Trim leading whitespace
                        $aLines[$j] = StringStripWS($aLines[$j], 1)
                      ; Repeat bulid with remaining characters in line
                    EndIf
                    
                WEnd
                
              ; Add the number of wrapped lines to the count
                $iLine_Count += $iWrap_Count
                
              ; Clean up
                _WinAPI_SelectObject($hDC, $hFont)
                _WinAPI_ReleaseDC($hWnd, $hDC)
                GUICtrlDelete($hText_Label)
                
            EndIf
            
        Next
        
      ; Convert lines to pixels and add normal padding
        $aLabel_Info[3] = ($iLine_Count * $aLabel_Info[1]) + 8
        
    EndIf

  ; Clean up
    GUIDelete($hGUI)
    
  ; Return array
    Return $aLabel_Info

EndFunc; => _Label_Size
Edited by Melba23

Public_Domain.png.2d871819fcb9957cf44f4514551a2935.png Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind

Open spoiler to see my UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Link to comment
Share on other sites

Respect here!

Imagine my dilemma

#include <XSkin.au3>

; folder of skin
$Skin_Folder = @ScriptDir & "\Skins\Universal"

$XSkinGui = XSkinGUICreate("My GUI", 400, 450, $Skin_Folder)

$button_1 = XSkinButton("Button 1", 145, 100, 100, 35, "Set_Tray")

GUISetState()

While 1
    MouseOver()

    Sleep(10)
WEnd

Func _Labels()

    Local $sMsg

    $sMsg = "This is a long line - this is a long line - this is a long line" & @CRLF & @CRLF
    $sMsg &= "This is an even longer long line - this is an even longer long line - this is an even longer long line" & @CRLF & @CRLF
    $sMsg &= "And this is a little one"
    XSkinMsgBox("XSkin", $sMsg)
    
    $sMsg = "This is a short line" & @CRLF & @CRLF
    $sMsg &= "This is a very long line indeed - this is a very long line indeed - this is a very long line indeed - this is a very long line indeed - "
    $sMsg &= "this is a very long line indeed - this is a very long line indeed - this is a very long line indeed - and this is the end!"
    XSkinMsgBox("XSkin", $sMsg)

    $sMsg = "This line contains a long word - generalisation" & @CRLF & @CRLF
    $sMsg &= "If the width is narrow and the font size large, it may not fit in the label" & @CRLF & @CRLF
    $sMsg &= "In that case, the label will not be displayed and the error will be set to 2 to indicate that there is a font size / width mismatch" & @CRLF & @CRLF
    $sMsg &= "There are now a number of blank lines" & @CRLF & @CRLF & @CRLF & @CRLF & @CRLF & @CRLF & @CRLF & @CRLF
    $sMsg &= "But the UDF still manages to cope, wrapping the lines as required to fit into the width requested and determining the correct height"
    XSkinMsgBox("XSkin", $sMsg)
    
    $sMsg = "This is only a short line" & @CRLF & @CRLF
    $sMsg &= "This is only a short line" & @CRLF & @CRLF
    $sMsg &= "This is only a short line" & @CRLF & @CRLF
    $sMsg &= "This is only a short line"
    XSkinMsgBox("XSkin", $sMsg)
    
    $sMsg = "This is a long line - this is a long line - this is a long line"
    XSkinTrayBox("XSkin", $sMsg)

EndFunc   ;==>_Labels

Func Set_Tray()
    _Labels()
EndFunc   ;==>Set_Tray

I needed not only the GUI size and label size... but also the relational button location

I think your UDF could be more beneficial to others with this info... rather than create a GUI to test in

8)

NEWHeader1.png

Link to comment
Share on other sites

  • Moderators

@Valuater and AlmarM,

Thank you for the kind comments.

Valuater,

I am afraid that as I do not use XSkin (a lacuna on my part I accept!) I do not understand what you mean by "relational button location". The UDF returns the size of the label needed - can you not work out the locations of other controls in the GUI from that?

Could you please rephrase the requirement - only too happy to oblige if I can.

M23

Public_Domain.png.2d871819fcb9957cf44f4514551a2935.png Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind

Open spoiler to see my UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Link to comment
Share on other sites

  • Moderators

@Zedna,

Thanks for the pointer to _WinAPI_GetTextExtentPoint32. There are so many functions in that helpfile that it is sometimes easy to overlook the obvious! But then that is why we have a forum, is it not?

Improved version of the UDF now in first post.

Děkuju moc,

M23

Public_Domain.png.2d871819fcb9957cf44f4514551a2935.png Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind

Open spoiler to see my UDFs:

Spoiler

ArrayMultiColSort ---- Sort arrays on multiple columns
ChooseFileFolder ---- Single and multiple selections from specified path treeview listing
Date_Time_Convert -- Easily convert date/time formats, including the language used
ExtMsgBox --------- A highly customisable replacement for MsgBox
GUIExtender -------- Extend and retract multiple sections within a GUI
GUIFrame ---------- Subdivide GUIs into many adjustable frames
GUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView items
GUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeView
Marquee ----------- Scrolling tickertape GUIs
NoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxes
Notify ------------- Small notifications on the edge of the display
Scrollbars ----------Automatically sized scrollbars with a single command
StringSize ---------- Automatically size controls to fit text
Toast -------------- Small GUIs which pop out of the notification area

 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...