News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Getting line spacing in a RichEdit control

Started by NoCforMe, July 25, 2023, 09:24:27 AM

Previous topic - Next topic

NoCforMe

I thought I'd write this up after jumping through all kinds of hoops to make this work.

I needed to know the line spacing in a RichEdit control. Reason was I'm implementing line #s in my editor (EdAsm, described elsewhere here). I needed the line spacing so the line #s would align with the lines of text in the control.

I tried 3 methods, none of which worked correctly:
  • Using GetTextMetrics() on the edit control's device context, and using the values returned (tmHeight, tmAscent, tmInternalLeading). No combination of these worked.
  • Using the line-spacing values from the PARAFORMAT2 structure, returned by EM_GETPARAFORMAT for RichEdit controls. Not only did this not work, I found out that this whole method is screwed up and doesn't work as advertised by Micro$oft.
  • The method used by Ketil0 in his line-numbering scheme (which does work for him, apparently), that uses GetTextExtentPoint32() on a buffer with 4 "W"s in it.

These methods came close (but no cigar, as they say): the line #s simply didn't line up with the control text.

I exchanged some PMs with JJ, who suggested I try EM_LINEINDEX and EM_POSFROMCHAR. At first I didn't see how this could possibly help me, but after scratching my head it dawned on me: assuming your edit control has at least 2 lines of text in it, you find one line of text in the control, get the y-coordinate of the 1st character on the line, do the same for the next line of text, subtract the 2 y-coords., and bingo, there's your line spacing.

It works. But wait a second: what if your edit control is empty? Doesn't work because there ain't no text in it. So I came up with a somewhat ugly but valid work-around: if the edit control has fewer than 2 lines of text in it, I
  • Save the "modified" status of the control
  • Extract the text, if any, from the control and store in a buffer
  • Plug in dummy text consisting of 2 lines of text ("x", CR/LF, "x")
  • Measure the line spacing as described above
  • Restore the original text, if any
  • Restore the original "modified" status
This all works well. Here's the code, including all the "doesn't work" code:

LineSpacingMeasureTxt    DB "x", $CRLF, "x", 0    ;Two lines of text.

;====================================
; GetEditLineHeight()
;
; Gets the line height of the current edit font.
;====================================

GetEditLineHeight    PROC
    LOCAL    pt:POINTL, dummyTextFlag:DWORD, modifyFlag:DWORD, buffer[1024]:BYTE

    MOV    dummyTextFlag, FALSE
    INVOKE    SendMessage, EditHandle, EM_GETLINECOUNT, 0, 0
    CMP    EAX, 2                            ;At least 2 lines?
    JAE    enough

; One or no lines in control: extract text from control, add 2 dummy lines,
; measure line spacing, then replace existing text:
    INVOKE    SendMessage, EditHandle, EM_GETMODIFY, 0, 0
    MOV    modifyFlag, EAX
    INVOKE    GetWindowText, EditHandle, ADDR buffer, SIZEOF buffer
    INVOKE    SetWindowText, EditHandle, OFFSET LineSpacingMeasureTxt
    MOV    dummyTextFlag, TRUE

;*****    This method works, but requires at least 2 lines of text in the control:  *****
enough:    INVOKE    SendMessage, EditHandle, EM_LINEINDEX, 1, 0        ;Line 2.
    INVOKE    SendMessage, EditHandle, EM_POSFROMCHAR, ADDR pt, EAX
    MOV    EAX, pt.y
    MOV    EditLineHeight, EAX
    INVOKE    SendMessage, EditHandle, EM_LINEINDEX, 0, 0        ;Line 1.
    INVOKE    SendMessage, EditHandle, EM_POSFROMCHAR, ADDR pt, EAX
    MOV    EAX, pt.y
    SUB    EditLineHeight, EAX

; See if we need to remove dummy text:
    CMP    dummyTextFlag, TRUE
    JNE    @F
    INVOKE    SetWindowText, EditHandle, ADDR buffer
    INVOKE    SendMessage, EM_SETMODIFY, EditHandle, modifyFlag, 0
@@:    RET

;*****    DOESN'T WORK!  *****
;    INVOKE    GetDC, EditHandle
;    MOV    hDC, EAX
;    INVOKE    GetTextMetrics, hDC, ADDR tm
;    MOV    EAX, tm.tmInternalLeading
;    MOV    EAX, tm.tmHeight
;    SUB    EAX, tm.tmInternalLeading
;    MOV    EditLineHeight, EAX
;    INVOKE    ReleaseDC, EditHandle, hDC

; From MSDN (https://learn.microsoft.com/en-us/windows/win32/controls/em-getparaformat):
;*****    DOESN'T WORK!  *****
;    MOV    pf2.cbSize, SIZEOF PARAFORMAT2
;    MOV    pf2.dwMask, PFM_LINESPACING
;    MOV    pf2.bLineSpacingRule, 2            ;Set spacing as twips/20.
;    MOV    pf2.dyLineSpacing, 30
;    INVOKE    SendMessage, EditHandle, EM_SETPARAFORMAT, 0, ADDR pf2
;    MOV    pf2.bLineSpacingRule, 4            ;Get spacing in twips.
;    INVOKE    SendMessage, EditHandle, EM_GETPARAFORMAT, 0, ADDR pf2
;    MOV    EAX, pf2.dyLineSpacing
;    MOV    EditLineHeight, EAX

;    invoke    wsprintf, addr buffer, offset lsfmt, EditLineHeight
;    invoke    MessageBox, NULL, addr buffer, NULL, MB_OK

; Ketil0's method:
;*****    DOESN'T WORK!  *****
;    MOV    EAX, 'WWWW'
;    MOV    DWORD PTR buffer, EAX
;    INVOKE    GetTextExtentPoint32, hDC, ADDR buffer, 4, ADDR pt
;    MOV    EAX, pt.y
;    MOV    EditLineHeight, EAX

GetEditLineHeight    ENDP
Assembly language programming should be fun. That's why I do it.