Jump to content
Melba23

StringSize - M23 - New version 16 Aug 11

Recommended Posts

I am using StringSize() to set the size of a GUI dynamically, depending on the text in a read-only edit control.

It works with the default Font and Font Size, but I have found out that the font is actually MS Shell Dlg. This is on Windows 7.

Why did you choose Tahoma 8.5 pt ? Perhaps this is because your experience is that choosing this combination, with a bit of padding, always works. I understand that the font and size depend on the Windows version, the resolution of the monitor, and probably other factors.

Later  Kafu has kindly provided a UDF written by Kafu and ProgAndy, so I can specify the font and size more precisely to StringSize().

Edited by c.haslam

Spoiler

CDebug Dumps values of variables including arrays and DLL structs, to a GUI, to the Console, and to the Clipboard

 

Share this post


Link to post
Share on other sites

Hi @Melba23

I'm trying to use the _StringSize() function to get dimensions to create a RichEdit control, but something goes wrong with the RichEdit.
with a normal Label control Instead It's all ok.
How can I use _StringSize() correctly also with the RichEdit control?
Thanks in advance for any hint

here is my attempt

#include <GuiRichEdit.au3>
#include "StringSize.au3"

; sample text 40 chars by 30 lines
Local $text = "         1         2         3         4" & @CRLF & _
        "1234567890123456789012345678901234567890" & @CRLF & "----------------------------------------" & @CRLF & _
        "4" & @CRLF & "5" & @CRLF & "6" & @CRLF & "7" & @CRLF & "9" & @CRLF & "10" & @CRLF & "11" & @CRLF & _
        "12" & @CRLF & "13" & @CRLF & "14" & @CRLF & "15" & @CRLF & "16" & @CRLF & "17" & @CRLF & "18" & @CRLF & _
        "19" & @CRLF & "20" & @CRLF & "21" & @CRLF & "22" & @CRLF & "23" & @CRLF & "24" & @CRLF & "25" & @CRLF & _
        "26" & @CRLF & "27" & @CRLF & "28" & @CRLF & "29" & @CRLF & "30"

Local $iFontSize = 12
Local $sFontName = "Courier New"
Local $iWeight = 400 ; normal

Local $aSize = _StringSize($text, $iFontSize, $iWeight, 0, $sFontName) ; get required sizes

Local $hGui = GUICreate("RichEdit test", $aSize[2] + 20, $aSize[3] + 20) ; creates a gui slightly larger of the control

#cs ; -- In a normal Label control, text fits like a sharm
    GUICtrlCreateLabel($text, 10, 10, $aSize[2], $aSize[3])
    GUICtrlSetFont(-1, $iFontSize, $iWeight, 0, $sFontName)
    GUICtrlSetBkColor(-1, 0x80FF80)
#ce

; a RichEdit control, doesn't fit well instead
Local $hRichEdit1 = _GUICtrlRichEdit_Create($hGui, $text, 10, 10, $aSize[2], $aSize[3], BitXOR($ES_READONLY, $ES_MULTILINE))
_GUICtrlRichEdit_SetSel($hRichEdit1, 0, -1, True) ; select all content
_GUICtrlRichEdit_SetFont($hRichEdit1, $iFontSize, $sFontName) ; and set Font

GUISetState()

MsgBox(0, "Pause", "click OK to end")

 


small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Share this post


Link to post
Share on other sites
Local $aSize2 = _StringSize(" ", $iFontSize, $iWeight, 0, $sFontName)
for $i = 0 to 3
$aSize[$i] += $aSize2[$i]
Next

I believe this would work as a workaround

Reading through MSDN I found this

https://msdn.microsoft.com/library/windows/desktop/bb761596

Under certain conditions, EM_GETRECT might not return the exact values that EM_SETRECT or EM_SETRECTNP set—it will be approximately correct, but it can be off by a few pixels.

Rich Edit: Supported in Microsoft Rich Edit 1.0 and later. The formatting rectangle does not include the selection bar, which is an unmarked area to the left of each paragraph. When clicked, the selection bar selects the line. For information about the compatibility of rich edit versions with the various system versions, see About Rich Edit Controls.

Share this post


Link to post
Share on other sites

Hi @Bilgus

your fix works very well (at least with a monospaced font)
but It fails anyway with some font widths (11, 17 for example)
Since seems  that the "problem" affects only the width, I just 'corrected' element [2] of the returned array
Many thanks for your 'workaround' and the explanation. very appreciated :)
Here is the 'patched' snippet

#include <GuiRichEdit.au3>
#include "StringSize.au3"

; sample text a square 40 chars by 31 lines
Local $text = "         1         2         3         4" & @CRLF & _
        "1234567890123456789012345678901234567890" & @CRLF & "+--------------------------------------+" & @CRLF
        For $i = 4 to 30
            $text &=  "| " & StringFormat("% 2s", $i) & "                                   |" & @CRLF
        Next
         $text &= "+ 31 ----------------------------------+"

Local $iFontSize = 12
Local $sFontName = "Courier New"
Local $iWeight = 400 ; normal

Local $aSize = _StringSize($text, $iFontSize, $iWeight, 0, $sFontName) ; get required sizes

; workaround by Bilgus (thank you) see his notes in post #64
Local $aSize2 = _StringSize(" ", $iFontSize, $iWeight, 0, $sFontName) ; get width of a single char
$aSize[2] +=  $aSize2[2] ; increase horizontal dimension by the width of 1 char

Local $hGui = GUICreate("RichEdit test", $aSize[2] + 20, $aSize[3] + 20) ; creates a gui slightly larger of the control

; a RichEdit control now fit well thanks to Bilgus's workaround
Local $hRichEdit1 = _GUICtrlRichEdit_Create($hGui, $text, 10, 10, $aSize[2], $aSize[3], BitXOR($ES_READONLY, $ES_MULTILINE))
_GUICtrlRichEdit_SetSel($hRichEdit1, 0, -1, True) ; select all content
_GUICtrlRichEdit_SetFont($hRichEdit1, $iFontSize, $sFontName) ; and set Font

GUISetState()

MsgBox(0, "Pause", "click OK to end")

 

Edited by Chimp
there are 31 lines not 30

small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Share this post


Link to post
Share on other sites

Hei @Melba23, i'm using your UDF to center my rich edit txts, the problem is that i have different font types and sizes, how could i deal with it? to center it, it only works with equal font sizes.

 

I'm using this UDF too:

For example, center the text of the preview:

#include <GUIConstantsEx.au3>
#include <GUIEdit.au3>
#include <ComboConstants.au3>
#include <WindowsConstants.au3>

#include "GUIRichLabel.au3"

Global $iLast_Focused_Ctrl = 0
Global $iEdit_Changed = 0

$hGUI = GUICreate("Formatted Labels Editor", 650, 400)

#Region Formate text panel

GUICtrlCreateLabel("Size:", 10, 8, -1, 15)
$nSize_Combo = GUICtrlCreateCombo("", 40, 5, 80, 20, BitOr($GUI_SS_DEFAULT_COMBO, $CBS_DROPDOWNLIST))
GUICtrlSetData(-1, "None|8|8.5|9|10|11|12|14|16|18|20|22|24|26|28|36|48|72", "None")

GUICtrlCreateLabel("Attrib:", 10, 33, -1, 15)
$nAttrib_Combo = GUICtrlCreateCombo("", 40, 30, 155, 20, BitOr($GUI_SS_DEFAULT_COMBO, $CBS_DROPDOWNLIST))
GUICtrlSetData(-1, "None|bold|italic|underline|strike|bold+italic+underline+strike|bold+italic+underline|bold+italic|bold+underline|bold+strike|italic+underline+strike|italic+underline|italic+strike|underline+strike", "None")

GUICtrlCreateLabel("Name:", 230, 15, 50, 15)
$nName_Combo = GUICtrlCreateCombo("", 230, 30, 160, 20, BitOr($GUI_SS_DEFAULT_COMBO, $CBS_DROPDOWNLIST))
GUICtrlSetData(-1, "None|Arial|Comic Sans Ms|Tahoma|Times|Georgia|Lucida Sans Unicode|Verdana|Times New Roman|Courier New", "None")

GUICtrlCreateLabel("Color:", 425, 15, 50, 15)
$nColor_Combo = GUICtrlCreateCombo("", 425, 30, 90, 20, BitOr($GUI_SS_DEFAULT_COMBO, $CBS_DROPDOWNLIST))
GUICtrlSetData(-1, "None|Red|Green|Blue|Yellow|Orange|Gray|Brown|White", "None")

GUICtrlCreateLabel("Bk Color:", 550, 15, 50, 15)
$nBkColor_Combo = GUICtrlCreateCombo("", 550, 30, 90, 20, BitOr($GUI_SS_DEFAULT_COMBO, $CBS_DROPDOWNLIST))
GUICtrlSetData(-1, "None|Red|Green|Blue|Yellow|Orange|Gray|Brown|White", "None")

#EndRegion Formate text panel

GUICtrlCreateGroup("Select text and then select the formatting parameters from the above panel:", 10, 60, 630, 150)

$nSrcText_Edit = GUICtrlCreateEdit("", 20, 80, 610, 120)

GUICtrlCreateGroup("Preview:", 10, 230, 630, 130)
GUICtrlSetFont(-1, 10, 800)

$nClose_Button = GUICtrlCreateButton("Close", 10, 370, 60, 20)
$nCopy_Button = GUICtrlCreateButton("Copy", 90, 370, 60, 20)

GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")
GUISetState(@SW_SHOW, $hGUI)

While 1
    $nMsg = GUIGetMsg()

    Switch $nMsg
        Case $GUI_EVENT_CLOSE, $nClose_Button
            Exit
        Case $nSize_Combo, $nAttrib_Combo, $nName_Combo, $nColor_Combo, $nBkColor_Combo
            $iLast_Focused_Ctrl = $nMsg

            Local $sParam = StringLower(StringRegExpReplace(GUICtrlRead($nMsg - 1), '\h+|:', ''))
            Local $sValue = GUICtrlRead($nMsg)

            _SetFormattedText_Proc($sParam, $sValue)
        Case $nCopy_Button
            Local $sText = GUICtrlRead($nSrcText_Edit)

            If $sText <> "" Then
                ClipPut($sText)
            EndIf
    EndSwitch

    If $iEdit_Changed Then
        _GUICtrlRichLabel_Destroy(-1)
        _GUICtrlRichLabel_Create($hGUI, GUICtrlRead($nSrcText_Edit), 20, 245, 610, 110)

        If $iEdit_Changed = 1 Then
            GUICtrlSetState($nSrcText_Edit, $GUI_FOCUS)
        Else
            GUICtrlSetState($iLast_Focused_Ctrl, $GUI_FOCUS)
        EndIf

        $iEdit_Changed = 0
    EndIf
WEnd

Func _SetFormattedText_Proc($sParam, $sValue)
    If $sParam = "attrib" And Not StringRegExp($sValue, '(?i)\A(None)?\z') Then
        Local $aSplit = StringSplit($sValue, "+")
        $sValue = ""

        For $i = 1 To $aSplit[0]
            $sValue &= StringLeft($aSplit[$i], 1)

            If $i < $aSplit[0] Then
                $sValue &= "+"
            EndIf
        Next
    EndIf

    Local $sSelectionData = ControlCommand($hGUI, "", $nSrcText_Edit, "GetSelected")
    Local $sAddParamValue = ' ' & $sParam & '="' & $sValue & '"'

    If $sSelectionData = '' Then
        Return
    EndIf

    If StringRegExp($sSelectionData, '(?i)<font.*>.*</font>') Then
        $sSelectionData = StringRegExpReplace($sSelectionData, '(?i)(<font.*)( ' & $sParam & '=".*?")(.*>.*</font>)', '\1\3')

        If Not StringRegExp($sValue, '(?i)\A(None)?\z') Then
            $sSelectionData = StringRegExpReplace($sSelectionData, '(?i)(<font.*)(>.*</font>)', '\1' & $sAddParamValue & '\2')
        EndIf

        If StringRegExp($sSelectionData, '(?i)<font\h*>.*</font>') Then
            $sSelectionData = StringRegExpReplace($sSelectionData, '(?i)<font.*>(.*)</font>', '\1')
        EndIf
    ElseIf $sAddParamValue <> '' Then
        $sSelectionData = '<font' & $sAddParamValue & '>' & $sSelectionData & '</font>'
    EndIf

    Local $iStart, $iEnd

    GUIRegisterMsg($WM_COMMAND, "")

    _GUICtrlEdit_ReplaceSel($nSrcText_Edit, $sSelectionData)
    $iStart = StringInStr(GUICtrlRead($nSrcText_Edit), $sSelectionData)-1
    $iEnd = $iStart + StringLen($sSelectionData)
    GUICtrlSendMsg($nSrcText_Edit, $EM_SETSEL, $iStart, $iEnd)

    $iEdit_Changed = 2
    GUIRegisterMsg($WM_COMMAND, "WM_COMMAND")
EndFunc

Func WM_COMMAND($hWnd, $nMsg, $wParam, $lParam)
    Local $nNotifyCode = BitShift($wParam, 16)
    Local $nID = BitAND($wParam, 0xFFFF)
    Local $hCtrl = $lParam

    Switch $nID
        Case $nSrcText_Edit
            Switch $nNotifyCode
                Case $EN_UPDATE
                    $iEdit_Changed = 1
            EndSwitch
    EndSwitch

    Return $GUI_RUNDEFMSG
EndFunc

 

Edited by x_bennY

Share this post


Link to post
Share on other sites

Hi Melba23, I was just testing your StringSize.au3 for use in making a small DebugMessageBox and threw some junk strings at and broke your UDF.

Apparently it will crash on a continuous string without breaks and throws a:

"==> Subscript used on non-accessible variable

GUICtrlCreateLabel($aSize[0], 10, 90, $aSize[2], $aSize[3])
GUICtrlCreateLabel($aSize^ ERROR"

$sText = " IamaverylonglineandIamnotformattedinanywaysothatIwillnotfitwithinthewidthoftheGUIthatsurroundsme!"

Throw the above string at it. Maybe a catch to break the string if no <space> occurs with width limits.

P.S. It's is a well written UDF, and thank for all your hard work.


"Writing code to carry out an intended set of tasks is relatively easy.
Writing code to carry out ONLY an intended set of tasks, well that can be a little more challenging."

Alex Maddern

Share this post


Link to post
Share on other sites

Spinwiz,

Quote

Maybe a catch to break the string if no <space> occurs with width limits

There is already one in there - you should get @error set to 3 if "Font too large for chosen max width - a word will not fit" and the UDF then returns 0. So if you are trying to use the elements of a non-existent array in your label I return the compliment and suggest you need a catch in your code to check for a valid return value:

#include "StringSize.au3"

#include <MsgBoxConstants.au3>
#include <Array.au3>

; This will not fit
$sText = " IamaverylonglineandIamnotformattedinanywaysothatIwillnotfitwithinthewidthoftheGUIthatsurroundsme!"

$vRet = _StringSize($sText, Default, Default, Default, Default, 100) ; Set small $iWidth value
$iError = @error ; And we get an error!
If @error Then
    MsgBox($MB_SYSTEMMODAL, "UDF Return", $vRet & @CRLF & @CRLF & $iError)
Else
    _ArrayDisplay($vRet, "UDF Success", Default, 8)
EndIf

; This will fit
$sText = " I am a very long line and I am formatted so that I will fit within the width of the GUI that surrounds me!"

$vRet = _StringSize($sText, Default, Default, Default, Default, 100) ; Set small $iWidth value
$iError = @error ; And we do not!
If @error Then
    MsgBox($MB_SYSTEMMODAL, "UDF Return", $vRet & @CRLF & @CRLF & $iError)
Else
    _ArrayDisplay($vRet, "UDF Success", Default, 8)
    MsgBox($MB_SYSTEMMODAL, "Formatted Text", $vRet[0])
EndIf

M23

Edited by Melba23
Machine posted before I was ready!

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

 

Share this post


Link to post
Share on other sites

Thanks Melba32, that was my next step.. To catch and break up an unusually long strings before sending them to the function. I'm perusing the most efficient way for that atm. Its a bit of a catch 22 working out the Max points length of the string section without using the function to get the correct max length :s other than sending multiple samples to build up to an appropriate length. I let it buzz around in my head for a while and let you know what I come up with :)


"Writing code to carry out an intended set of tasks is relatively easy.
Writing code to carry out ONLY an intended set of tasks, well that can be a little more challenging."

Alex Maddern

Share this post


Link to post
Share on other sites

Nah, its looks like catch 22. I can't see a way to get required lengths of an unformatted text string outside of the Func without without adding one char at a time until it errors.

I think it would be far more efficient to break up long unbroken strings within the func, even an input flag for that case. The output wouldn't be pretty in such cases, but at least it wouldn't error and still produce an output. The other alternative is we can never seam to be able to pass any large word to the function if the width is set to low.

Maybe I am wrong and I'm missing something here and if so I apologize.

Alternatively as a question how would I predict the required "$iWidth  - [optional] Max width for rectangle - (default = 0 => width of original string)" of a long word in an unknown string?

Edited by Spinwiz
Correction to unformatted

"Writing code to carry out an intended set of tasks is relatively easy.
Writing code to carry out ONLY an intended set of tasks, well that can be a little more challenging."

Alex Maddern

Share this post


Link to post
Share on other sites

Spinwiz,

Quote

I think it would be far more efficient to break up long unbroken strings within the func, even an input flag for that case

I am sure you will appreciate that I do not share that opinion. The UDF returns the size of control required to fit a given string and, if requested, format the string using existing spacing to fit within a specific width. I do not see it as the task of the UDF to break long strings - if the user thinks that these might occur in the text then they need to do the necessary "hyphenation" beforehand as only they can determine exactly what form of break is appropriate.

Quote

how would I predict the required "$iWidth  - [...]" of a long word in an unknown string?

Simple. Split the string into "words" using StringSplit or a RegEx, then use the UDF on each word to determine the longest:

#include "StringSize.au3"

#include <Array.au3>

; Here is a line with a long "word"
$sText = "I am a very long line and IamnotformattedinanywaysothatIwillnotfitwithinthewidthoftheGUI that surrounds me!"

; Split the line into "words"
$aWords = StringSplit($sText, " ")
; Just to show what we get
_ArrayDisplay($aWords, "", Default, 8)

; Now loop through the "words" to find the longest
$iMaxWidth = 0
For $i = 1 To $aWords[0]
    ; Call the function and do NOT set a $iWidth value
    $vRet = _StringSize($aWords[$i])

    ; If no error then check if width is greater then the current maximum
    If Not @error Then
        If $vRet[2] > $iMaxWidth Then
            $iMaxWidth = $vRet[2]
            ConsoleWrite("Max width: " & $iMaxWidth & " - word: " & $aWords[$i] & @CRLF)
        EndIf
    EndIf
Next

; Now you have the max width required and can format the whole line
; Note it is a good idea to add a bit of a buffer to prevent any rounding error within the UDF
$vRet = _StringSize($sText, Default, Default, Default, Default, $iMaxWidth + 10)
If Not @error Then
    MsgBox($MB_SYSTEMMODAL, "Formatted", $vRet[0])
Else
    MsgBox($MB_SYSTEMMODAL, "Error", @error)
EndIf

All clear?

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

 

Share this post


Link to post
Share on other sites

Thanks Melba23, I see your method is workable :), as I said , would have to split the strings at a bit of an inside safe guess and creep up on it in a width constrained usage.

20 hours ago, Spinwiz said:

I can't see a way to get required lengths of an unformatted text string outside of the Func without without adding one char at a time until it errors.

I'm not going to ask you to modify, nor will I modify your UDF... I was rolling my own until until I encountered your UDF, so no stress, I'm back to rolling my own =)

Getting the correct string/font conversions using recommended MSDN methods (GetTextExtentPoint32W, GetDeviceCaps, GetTextExtentExPointW, Etc.)

https://docs.microsoft.com/en-us/windows/desktop/gdi/string-widths-and-heights

All appear to give overstated or inaccurate results in testing the length of a a sample string. You've been through multiple methods upon your way to StringSize and Its been a traditionally problematic issue. I did a work around in C a few years back, so I'll take a look at that, or maybe I can get a more accurate result in WinAPI Directly, and I'm Trying few "Outside of the box" ideas at the moment. I enjoy a challenge, so I'm out to try n nail this one down my myself lol. I'll let you know if get anything worthy =)


"Writing code to carry out an intended set of tasks is relatively easy.
Writing code to carry out ONLY an intended set of tasks, well that can be a little more challenging."

Alex Maddern

Share this post


Link to post
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

×
×
  • Create New...