Announcement

Collapse
No announcement yet.

Physical Length of Text in RichEdit Control

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Physical Length of Text in RichEdit Control

    I would like to know how long a string would be if it were displayed in a richedit control - not how many characters, but physical width.

    I'm trying to create a text string of length X, rather than measure a string that already exists in the control. Font selection needs to be taken into account.

    I can get text physical size using XPrint and Graphic commands, but I'm not finding an equivalent one for a richedit control. I can attach a printer and use XPrint commands, or create a hidden graphic control and attach to it. Their length information can applied to text in a richedit control, but I wanted to avoid both alternatives - just use code specific to a richedit control (or any generic control that displays text).

    For my purposes, the text would be on a single line (no multi-line issues).
    Last edited by Gary Beene; 20 Aug 2009, 02:38 PM.

  • #2
    paint the re to a very width emf and when done explore the objects by enumming.
    hellobasic

    Comment


    • #3
      Oh btw, you can measure the h-scrollbars range.
      hellobasic

      Comment


      • #4
        Call GetTextExtentPoint32 using hDC and text string. That will give you width and height in pixels.

        Divide Length in pixels by GetDeviceCaps (hDC, HORZRES) and that gives you logical inches.

        Soemthing like that, anyway.

        MCM
        Michael Mattias
        Tal Systems (retired)
        Port Washington WI USA
        [email protected]
        http://www.talsystems.com

        Comment


        • #5
          GetTextExtentPoint32 is not of any use when dealing with rich edit text.
          You will need to use COM for this if you do not want to create a window.
          Do a search on windowless rich edit controls, the CreateTextServices function
          and the ITextServices interface.
          Dominic Mitchell
          Phoenix Visual Designer
          http://www.phnxthunder.com

          Comment


          • #6
            MCM,
            I did a search on GetTextExtentPoint32 and found a variety of versions. I built this function based on what I read. It seems to work.

            Code:
            Function GetTextWidth(buf$, hWnd as Dword) as Single
                  Local hDC as Dword, R as SizeL
                  hDC = GetDC(hWnd)
                  hFont = SendMessage (hWnd, %WM_GETFONT, 0, 0)
                  SelectObject hDc, hFont
                  GetTextExtentPoint32 (hDc, ByVal StrPTR(buf$), Len(buf$), R)
                  Function = R.cx/GetDeviceCaps(hdc, %LOGPIXELSX)
                  ReleaseDC hWnd, hDC
            End Function
            The compilable version is below.

            Dominic, your statement suggests there's a problem with using GetTextExtentPoint32 with a richedit control. In my situation, I have a richedit control but wanted to build text of a certain size before putting it in the control. Also, I'm working with the RE plain text, not the RE RTF text - perhaps that was your concern?

            In this example I load text into the RE control so I can physically measure it with a ruler. But the text that I use to calculate the answer is simply programmatically assigned.

            Code:
            'Compilable Example:
            #Compile Exe
            #Dim All
            #Include "Win32API.inc"
            #Include "RichEdit.inc"
            #Include "CommCtrl.inc"
            Global hDlg as Dword, hRichEdit as Dword, hFont as Dword
            %ID_RichEdit = 500
            Function PBMain() As Long
               Local style&, buf$
               buf$ =  "This" + $Tab + "text" + $Tab + "rocks!" + $crlf + String$(6,"x") +$crlf + String$(12,"x")
               style& = %WS_Child Or %WS_Visible Or %ES_MultiLine Or %WS_VScroll Or %ES_AutoHScroll _
                         Or %WS_HScroll Or %ES_AutoVScroll Or %ES_WantReturn Or %ES_NoHideSel Or %WS_TabStop
               Dialog New Pixels, 0, "Test Code",300,300,200,150, %WS_OverlappedWindow To hDlg
               Control Add Button, hDlg, 100,"Push", 30,10,140,20
               LoadLibrary("riched32.dll") : InitCommonControls
               Control Add "RichEdit", hDlg, %ID_RichEdit, buf$,20,40,160,100, style&, %WS_EX_ClientEdge
               Control Handle hDlg, %ID_RichEdit To hRichEdit
               Font New "Comic Sans MS", 10,1 To hFont
               Control Set Font hDlg, %ID_RichEdit, hFont
               Dialog Show Modal hDlg Call DlgProc
            End Function
            
            CallBack Function DlgProc() As Long
               If CB.Msg = %WM_Command AND CB.Ctl = 100 AND CB.Ctlmsg = %BN_Clicked Then
                  Local buf$
                  buf$ = String$(6,"x")
                  MsgBox Str$( GetTextWidth(buf$, hRichEdit) )
                  buf$ = String$(12,"x")
                  MsgBox Str$( GetTextWidth(buf$, hRichEdit) )
               End If
            End Function
            
            Function GetTextWidth(buf$, hWnd as Dword) as Single
                  Local hDC as Dword, R as SizeL
                  hDC = GetDC(hWnd)
                  hFont = SendMessage (hWnd, %WM_GETFONT, 0, 0)
                  SelectObject hDc, hFont
                  GetTextExtentPoint32 (hDc, ByVal StrPTR(buf$), Len(buf$), R)
                  Function = R.cx/GetDeviceCaps(hdc, %LOGPIXELSX)
                  ReleaseDC hWnd, hDC
            End Function
            Last edited by Gary Beene; 20 Aug 2009, 10:22 PM.

            Comment


            • #7
              GetTextExtentPoint32 as you used it will work if the text does not contain a mixture of
              fonts, has no kerning, tabs, or overhang.
              Compare the values returned by your GetTextWidth function for the first and third lines
              of text in your sample.

              Some of the other functions you might wish to consider are GetTabbedTextExtent and
              DrawTextEx(with the DT_CALCRECT and DT_EXPANDTABS flags).

              Also, restore the device context to its original state.
              Code:
                    hFontOld = SelectObject(hDc, hFont)
                    ...
                    SelectObject hDc, hFontOld
              Dominic Mitchell
              Phoenix Visual Designer
              http://www.phnxthunder.com

              Comment


              • #8
                Dominic,

                Thanks for the clarifying comments and information.

                In the following code, I reposition tab'd text using GetTextExtentPoint32 and can visually see that the text shifts slightly (from its original tab'd position) when my code is used. There shouldn't be any shift.

                I'll write an equivalent code using DrawTextEx and post the results here later.

                Code:
                #Compile Exe
                #Dim All
                #Include "Win32API.inc"
                #Include "RichEdit.inc"
                #Include "CommCtrl.inc"
                Global hDlg As Dword, hRichEdit As Dword, TabLoc() As Single, hFont As Dword, hDC As Dword
                %ID_RichEdit = 500
                Function PBMain() As Long
                   Local style&, buf$
                   buf$ = "This is" + $Tab + $Tab + "an example" + $Tab + "of a string with an embedded tab character."
                   buf$ = buf$ + $CrLf + "2This is" + $Tab + "2an example" + $Tab + "2of a string with an embedded tab character."
                   style& = %WS_Child Or %WS_Visible Or %ES_MultiLine Or %WS_VScroll Or %ES_AutoHScroll _
                             Or %WS_HScroll Or %ES_AutoVScroll Or %ES_WantReturn Or %ES_NoHideSel Or %WS_TabStop
                   Dialog New Pixels, 0, "Test Code",300,300,550,150, %WS_OverlappedWindow To hDlg
                   Control Add Button, hDlg, 100,"Print", 30,10,140,20
                   LoadLibrary("riched32.dll") : InitCommonControls
                   Control Add "RichEdit", hDlg, %ID_RichEdit, buf$,20,40,510,100, style&, %WS_Ex_ClientEdge
                      Control Handle hDlg, %ID_RichEdit To hRichEdit
                      Font New "Comic Sans MS", 10, 0 To hFont
                      Control Set Font hDlg, %ID_RichEdit, hFont
                      hDC = GetDC(hRichEdit)
                      hFont = SendMessage (hRichEdit, %WM_GETFONT, 0, 0)
                      SelectObject hDc, hFont
                   Dialog Show Modal hDlg Call DlgProc
                      ReleaseDC hRichEdit, hDC
                End Function
                
                CallBack Function DlgProc() As Long
                   If Cb.Msg = %WM_Command And Cb.Ctl = 100 And Cb.CtlMsg = %BN_Clicked Then
                      Local temp$, w As Single, h As Single, i As Long
                      ReDim TabLoc(50)  'tab locations
                      For i = 0 To 50 : TAbLoc(i) = i * 0.5 : Next i   '0.5" tab locations
                      Control Get Text hDlg, %ID_RichEdit To temp$
                      temp$ = ConvertTABtoSpace (temp$)    'converted TABs to spaces
                      Control Set Text hDlg, %ID_RichEdit, temp$
                   End If
                End Function
                
                Function ConvertTABtoSpace(ByVal text$) As String
                   'build string with variable # of spaces to reach TAB stops
                   Local i As Long, j As Long, temp$, iSpaces&, iSpaceWidth!, result$, tempWidth!, ncWidth!, ncHeight!
                   Dim D(ParseCount(text$,$CrLf)-1) As String, iTab&, R As SizeL
                
                   'split text into lines, put in array D()
                   Parse text$, D(), $CrLf
                
                   'get/save width of space for current font
                   temp$ = " "
                   GetTextExtentPoint32 (hDc, ByVal StrPtr(temp$), Len(temp$), R)
                   iSpaceWidth! = R.cx/GetDeviceCaps(hdc, %LOGPIXELSX)
                
                   'build replacement string, substituting tabs with variable # of spaces (to reach tab locations)
                   For j = 0 To UBound(D)
                      temp$ = ""
                      For i = 1 To ParseCount(D(j), $Tab)
                         temp$ = temp$ + Parse$(D(j),$Tab, i)
                         GetTextExtentPoint32 (hDc, ByVal StrPtr(temp$), Len(temp$), R)
                         tempWidth! = R.cx/GetDeviceCaps(hdc, %LOGPIXELSX)
                         iTab& = Fix(tempWidth!/0.5)+1              'next tab location after current endpoint
                         iSpaces& = (TabLoc(iTab&)-tempWidth!) / iSpaceWidth!   '#spaces to widen to next tab location
                         temp$ = temp$ + Space$(iSpaces&)
                           'add extra single space as needed to ensure current tab position is crossed
                            GetTextExtentPoint32 (hDc, ByVal StrPtr(temp$), Len(temp$), R)
                            tempWidth! = R.cx/GetDeviceCaps(hdc, %LOGPIXELSX)
                            If tempWidth! < TabLoc(iTab&) Then temp$ = temp$ + " "
                      Next i
                      result$ = result$ + $CrLf + temp$
                   Next J
                   Function = Mid$(result$,3)
                End Function
                Last edited by Gary Beene; 21 Aug 2009, 08:13 AM.

                Comment


                • #9
                  Well now, this is interesting.

                  I updated the code to use DrawTextEx and it, too, causes a shift (to the left) in the position of tab'd text. It looks to be about the same shift as I saw with GetTextExtentPoint32.

                  Perhaps there are some left edge values (borders?) that I am not taking correctly into account? I'll have to look further.


                  Code:
                  'Compilable Example:
                  'In this example, the Function above is broken apart so that some
                  'items do not have to be recalculated.
                  #Compile Exe
                  #Dim All
                  #Include "Win32API.inc"
                  #Include "RichEdit.inc"
                  #Include "CommCtrl.inc"
                  Global hDlg As Dword, hRichEdit As Dword, TabLoc() As Single, hFont As Dword, hDC As Dword
                  %ID_RichEdit = 500
                  Function PBMain() As Long
                     Local style&, buf$
                     buf$ = "This is" + $Tab + $Tab + "an example" + $Tab + "of a string with an embedded tab character."
                     buf$ = buf$ + $CrLf + "2This is" + $Tab + "2an example" + $Tab + "2of a string with an embedded tab character."
                     style& = %WS_Child Or %WS_Visible Or %ES_MultiLine Or %WS_VScroll Or %ES_AutoHScroll _
                               Or %WS_HScroll Or %ES_AutoVScroll Or %ES_WantReturn Or %ES_NoHideSel Or %WS_TabStop
                     Dialog New Pixels, 0, "Test Code",300,300,550,150, %WS_OverlappedWindow To hDlg
                     Control Add Button, hDlg, 100,"Print", 30,10,140,20
                     LoadLibrary("riched32.dll") : InitCommonControls
                     Control Add "RichEdit", hDlg, %ID_RichEdit, buf$,20,40,510,100, style&, %WS_Ex_ClientEdge
                        Control Handle hDlg, %ID_RichEdit To hRichEdit
                        Font New "Comic Sans MS", 10, 0 To hFont
                        Control Set Font hDlg, %ID_RichEdit, hFont
                        hDC = GetDC(hRichEdit)
                        hFont = SendMessage (hRichEdit, %WM_GETFONT, 0, 0)
                        SelectObject hDc, hFont
                     Dialog Show Modal hDlg Call DlgProc
                        ReleaseDC hRichEdit, hDC
                  End Function
                  
                  CallBack Function DlgProc() As Long
                     If Cb.Msg = %WM_Command And Cb.Ctl = 100 And Cb.CtlMsg = %BN_Clicked Then
                        Local temp$, w As Single, h As Single, i As Long
                        ReDim TabLoc(50)  'tab locations
                        For i = 0 To 50 : TAbLoc(i) = i * 0.5 : Next i   '0.5" tab locations
                        Control Get Text hDlg, %ID_RichEdit To temp$
                        temp$ = ConvertTABtoSpace (temp$)    'converted TABs to spaces
                        Control Set Text hDlg, %ID_RichEdit, temp$
                     End If
                  End Function
                  
                  Function ConvertTABtoSpace(ByVal text$) As String
                     'build string with variable # of spaces to reach TAB stops
                     Local i As Long, j As Long, temp$, iSpaces&, iSpaceWidth!, result$, tempWidth!, ncWidth!, ncHeight!
                     Dim D(ParseCount(text$,$CrLf)-1) As String, iTab&, R As Rect
                  
                     'split text into lines, put in array D()
                     Parse text$, D(), $CrLf
                  
                     'get/save width of space for current font
                     temp$ = " "
                  '   GetTextExtentPoint32 (hDc, ByVal StrPTR(temp$), Len(temp$), R)
                     DrawTextEx hDC, ByVal StrPtr(temp$), Len(temp$), R, %DT_CalcRect, ByVal 0
                     iSpaceWidth! = (R.nright - R.nleft)/GetDeviceCaps(hdc, %LOGPIXELSX)
                  
                     'build replacement string, substituting tabs with variable # of spaces (to reach tab locations)
                     For j = 0 To UBound(D)
                        temp$ = ""
                        For i = 1 To ParseCount(D(j), $Tab)
                           temp$ = temp$ + Parse$(D(j),$Tab, i)
                  '         GetTextExtentPoint32 (hDc, ByVal StrPTR(temp$), Len(temp$), R)
                           DrawTextEx hDC, ByVal StrPtr(temp$), Len(temp$), R, %DT_CalcRect, ByVal 0
                           tempWidth! = (R.nright - R.nleft)/GetDeviceCaps(hdc, %LOGPIXELSX)
                           iTab& = Fix(tempWidth!/0.5)+1              'next tab location after current endpoint
                           iSpaces& = (TabLoc(iTab&)-tempWidth!) / iSpaceWidth!   '#spaces to widen to next tab location
                           temp$ = temp$ + Space$(iSpaces&)
                             'add extra single space as needed to ensure current tab position is crossed
                  '            GetTextExtentPoint32 (hDc, ByVal StrPTR(temp$), Len(temp$), R)
                              DrawTextEx hDC, ByVal StrPtr(temp$), Len(temp$), R, %DT_CalcRect, ByVal 0
                              tempWidth! = (R.nright - R.nleft)/GetDeviceCaps(hdc, %LOGPIXELSX)
                              If tempWidth! < TabLoc(iTab&) Then temp$ = temp$ + " "
                        Next i
                        result$ = result$ + $CrLf + temp$
                     Next J
                     Function = Mid$(result$,3)
                  End Function

                  Comment


                  • #10
                    If you want to print all your text with tabstops, why bother with a richedit control, or any edit control?

                    Just use a static control with SS_OWNERDRAW style and draw the text on the WM_OWNERDRAW notfication using any of the "Draw" functions you want.

                    If you need multiple lines with scrolling, take a look at a listbox control with LBS_OWNERDRAW{FIXED|VARIABLE} style and ditto. (There's a demo of this here somewhere).

                    Or, use a listview control in report mode, where subitems=columns ..... that will handle it, too. There's lots of demos of this here, including at least one where different columns are drawn in different colors. (and if it does different colors, it can do different fonts, too).

                    Basically I cannot reconcile in my mind the concept of using an edit or richedit control to accept tabbed or columnar user input. I think that's why "grid" controls are a popular offering.

                    MCM
                    Last edited by Michael Mattias; 21 Aug 2009, 09:18 AM.
                    Michael Mattias
                    Tal Systems (retired)
                    Port Washington WI USA
                    [email protected]
                    http://www.talsystems.com

                    Comment


                    • #11
                      MCM,

                      If you want to print all your text with tabstops, why bother with a richedit control, or any edit control?
                      I don't (want to print all text with tabstops). The information I'm using has some content tab'd for alignment and some not.

                      As for needing the text length, there are lots of reasons why it might be needed. For example, the XPrint command does not handle tab characters, so if the original variable width font content has them I have to work around XPrint's limitations. Another example is converting content to HTML. Browsers don't handle tabs, so I have to work around it by converting to appropriate variable-width-font spaces (another utility I use converts the text to HTML).

                      Your comments about all-columnar data are appropriate, but that's the type of content I'm working with.

                      As for this broad stroke:

                      Basically I cannot reconcile in my mind the concept of using an edit or richedit control to accept tabbed or columnar user input.
                      What about a letter, where the address is placed in a tab position on the right side of the page? You'd want a user to space over to the position, instead of allowing the use of tabs?

                      Comment


                      • #12
                        I saw your XPRINT code in the source code forum and while I didn't run it, it looks nicely thought out (that means it's usable and easily adaptable).

                        First of all, if you are working with a printer, that's an OUTPUT device.... edit controls are INPUT devices.

                        Second, re your letter example
                        What about a letter, where the address is placed in a tab position on the right side of the page? You'd want a user to space over to the position, instead of allowing the use of tabs?
                        No, but I would force the user to put in the correct number of tabs or spaces. I give him separate controls for each field....
                        Code:
                        Return Address  [                       ]                 ' line one
                                        [                       ]                 ' line 2 
                                        [                       ]  [ ] [     ]   ' city, state zip
                        .. and tabs? If your screen is done correctly, the <TAB> key goes to the next field. Sounds pretty straightforward to me.

                        It's kind of like database design: you never design one field to hold multiple pieces of information; one field holds exactly one piece of information.

                        Bottom line for me is, input is not output, and one should use controls designed for input or controls designed for output as appropriate. Edit controls are input devices.


                        MCM
                        Last edited by Michael Mattias; 21 Aug 2009, 11:43 AM.
                        Michael Mattias
                        Tal Systems (retired)
                        Port Washington WI USA
                        [email protected]
                        http://www.talsystems.com

                        Comment


                        • #13
                          Your code does not mimic the behaviour of both the edit and rich edit controls.
                          A hint for what could be happening is provided by the documentation for the GetTabbedTextExtent
                          and TabbedTextOut functions and the EM_SETTABSTOPS message.

                          The docs for GetTabbedTextExtent and TabbedTextOut says the following:
                          If the nTabPositions parameter is zero and the lpnTabStopPositions parameter is NULL,
                          tabs are expanded to eight times the average character width.
                          The doc for EM_SETTABSTOPS says the same thing in a different way.
                          Specifies the number of tab stops contained in the array. If this parameter is zero, the lParam
                          parameter is ignored and default tab stops are set at every 32 dialog template units.
                          To calculate average character width, use the function below.
                          Do not use the tmAveCharWidth member of the TEXTMETRIC structure.
                          Code:
                          SUB phnxGetAverageCharDim _
                            ( _
                            BYVAL hFont   AS DWORD, _  ' handle of font
                                  cxChar  AS LONG, _   ' average character width of font
                                  cyChar  AS LONG _    ' height of characters in the font
                            )
                          
                            LOCAL ttm            AS TEXTMETRIC
                            LOCAL tsize          AS SIZEL
                            LOCAL hDC            AS DWORD
                            LOCAL hFontOld       AS DWORD
                            LOCAL lMapModeOld    AS LONG
                          
                            ' Get the device context for the screen
                            hDC         = GetDC(%NULL)
                            lMapModeOld = SetMapMode(hDC, %MM_TEXT)
                            hFontOld    = SelectObject(hDC, hFont)
                            ' Average metrics for dialog font
                            GetTextExtentPoint hDC,"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, tsize
                            GetTextMetrics hDC, ttm
                            ' This is the wacky formula used by Microsoft
                            cxChar = (tsize.cx / 26)
                            ' Round up the average character width
                            cxChar = (cxChar + 1) / 2
                            cyChar = ttm.tmHeight
                            SelectObject hDC, hFontOld
                            SetMapMode hDC, lMapModeOld
                            ReleaseDC %NULL, hDC
                          
                          END SUB
                          Dominic Mitchell
                          Phoenix Visual Designer
                          http://www.phnxthunder.com

                          Comment


                          • #14
                            I had an idea to handle various fonts and stuff...

                            1. Make a guess or estimate how wide, using GetTextExtentPoint32 or something.

                            2. Starting with that guess as the formatting rectangle in the CHARRANGE structure, do some kind of loop sending the EM_FORMATRANGE message to the control, adjusting the formatting rectangle until the message returns exactly the number of characters in the string. At that point your formatting rectangle will be the size of the text.

                            That could be a tad frustrating, as you will have to convert back and forth between twips and pixels, but I'm sure it could be done.

                            MCM
                            Michael Mattias
                            Tal Systems (retired)
                            Port Washington WI USA
                            [email protected]
                            http://www.talsystems.com

                            Comment

                            Working...
                            X