News:

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

Main Menu

EN_SELCHANGE code from rich text edit

Started by rc, October 30, 2019, 09:52:33 PM

Previous topic - Next topic

rc

Hello community,

i'm totally new to assembly and wanted to start with an easy text editor. The editor is working but i want to display the cursor position in the status bar. However somehow im struggeling with getting the selection change "event".

I have the following code:

CASE WM_NOTIFY
        mov edx, lParam
        mov eax, [edx].NMHDR.code
        .if eax == EN_SELCHANGE ;<---- this never gets evaluated to true but why?
            invoke SendMessage, hEdit, EM_EXLINEFROMCHAR, 0, -1 ; by the way: where does the return value of EM_EXLINEFROMCHAR get stored?

            ; just for testing (works nicely and sets the status bar text, but not when inside this if block)
            mov pbuffer1, ptr$(buffer1)
            mov pbuffer1, cat$(pbuffer1,"Test")
            invoke SendMessage,hStatus,SB_SETTEXT,0,pbuffer1
        .endif

case WM_CLOSE


As i have read from the documentation, the rich text edit control automatically sends a EL_SELCHANGE and i can get it in WM_NOTIFY. However, the condition is never true. What am i missing?

Sidequestion: in fasm i can write int3 to let the jitdebugger pop up when the program reaches this instruction. Whats the equivalent in masm? Thought this instruction isn't specific to any asm language but to the instruction set of the cpu. Somehow masm shows an error on int3.

jj2007

For me, that works perfectly. Could you post the complete code please? Zip the source, and attach it here.
Re int3: leave a blank, i.e. int 3

Welcome to the Forum :thumbsup:

TimoVJL

https://docs.microsoft.com/en-us/windows/win32/controls/en-selchange
QuoteRemarks
To receive EN_SELCHANGE notification codes, specify ENM_SELCHANGE in the mask sent with the EM_SETEVENTMASK message.
May the source be with you

rc

Thank you jj2007 :)


Quote from: TimoVJL on October 30, 2019, 10:00:53 PM
https://docs.microsoft.com/en-us/windows/win32/controls/en-selchange
QuoteRemarks
To receive EN_SELCHANGE notification codes, specify ENM_SELCHANGE in the mask sent with the EM_SETEVENTMASK message.

Ah ok. From where do i have to send this message? Thought this where done automatically by the rich text control?

TimoVJL

May the source be with you

rc

This works, thank you. Totally missed that. :)

Now i need to know where return values typically get stored.

invoke SendMessage, hEdit, EM_EXLINEFROMCHAR, 0, -1 ;

Here i get the line info, but i don't know where it is stored.

Microsoft docs do not mention anything like that as they probably assume you want to write c code.

Is there a rule of thump that says, to follow the std convention, return values are always stored in register "xxx".
As i have read, to follow the std call convention, a subroutine shall not change the ebx register, if so, it has to preserve ebx and reset it when leaving. Also i have read, that a subroutine is responsible for restoring the stack and so on.

hutch--

This is from the old win32.hlp file and it works OK.

The EM_EXLINEFROMCHAR message determines which line contains the specified character in a rich edit control.

EM_EXLINEFROMCHAR 
wParam = 0;
lParam = (LPARAM) (DWORD) ichCharPos;


Parameters

ichCharPos

Zero-based index of the character.

Return Values
Returns the zero-based index of the line.


The return value in Win32 is always in EAX.

Code something like this.

    invoke SendMessage,hEdit,EM_EXLINEFROMCHAR,0,charpos
    mov lind, eax


jj2007

Quote from: rc on October 30, 2019, 10:43:41 PMHere i get the line info, but i don't know where it is stored.

Wherever you want to store it. That can be a register, but check the Tips, Tricks and Traps section here. There are volatile and less volatile registers.

Depending on your needs, you can store it also in a local or global variable.

rc

Thank you hutch-- for the explaination. Such things like return values are always stored in eax are implied knowlege  and hard to learn for newbs.
@jj2007 thanks for the link, i have read it. Good to know, especially the stuff about local variables.

Im a bit confused know though.

All i want to get is the line where the cursor currently is located.
At the beginning of the proc, i have created some spare buffers and local variables:


WndProc proc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
   
    ; Two spare buffers + pointers
    ; for text manipulation etc..
    LOCAL buf1[260]:BYTE 
    LOCAL buf2[260]:BYTE
    LOCAL pbuf1:DWORD
    LOCAL pbuf2:DWORD
    ; temp variable for storing 32 bit values temporarily if needed
    LOCAL tmpd:DWORD

    Switch uMsg
      Case WM_COMMAND
      ...
      ...
      CASE WM_NOTIFY
        mov edx, lParam
        mov eax, [edx].NMHDR.code

        .if eax == EN_SELCHANGE
            mov pbuf1, ptr$(buf1)
            invoke SendMessage, hEdit, EM_EXLINEFROMCHAR, 0, -1
            mov tmpd, eax
            mov pbuf1, cat$(pbuf1, "Line: ", tmpd) ; <--------  crash here
           
            invoke SendMessage, hStatus, SB_SETTEXT, 0, pbuf1

        .endif

      case WM_CLOSE
      ...
      ...


The code should just get the line where the cursor currently is and set the text in the statusbar to "Line: xxx", where xxx is the line.

jj2007

Try mov pbuf1, cat$(pbuf1, "Line: ", str$(tmpd))

I write "try" because you don't post complete code.

rc

#10
Quote from: jj2007 on October 31, 2019, 12:25:53 AM
Try mov pbuf1, cat$(pbuf1, "Line: ", str$(tmpd))

Oh this works. Of course, tmpd is an integer.... thank you!
A lot to consider in assembly that leads to overlooking such trivial things.
Especially when one is used to compiler error messages like "Int cannot implicitly converted to str" :)

Thanks. :)

Quote from: jj2007 on October 31, 2019, 12:25:53 AM
I write "try" because you don't post complete code.

It's not that i don't want to post the entire code. It's just that it is a lot of code. And most of the code is generated by the "MASM32 Window Code Createion Tool" from Steve Hutchesson.

I used that as a starting point to dig into the generated code, try to understand it, extend it a little bit and so on.
As i am probably not at the point where i am able to write such editor from scratch in masm. :)




Maybe it's usefull to others, here is the working code:

Window creation and setting the eventbitmask:

; -----------------------------------------------------------------
  ; create the main window with the size and attributes defined above
  ; -----------------------------------------------------------------
    invoke CreateWindowEx,WS_EX_LEFT or WS_EX_ACCEPTFILES,
                          ADDR szClassName,
                          ADDR szDisplayName,
                          WS_OVERLAPPEDWINDOW,
                          Wtx,Wty,Wwd,Wht,
                          NULL,NULL,
                          hInstance,NULL
    mov hWnd,eax

    fn LoadLibrary,"RICHED20.DLL"
    mov hEdit, rv(RichEdit2,hInstance,hWnd,999,0)
    invoke SendMessage,hEdit,EM_EXLIMITTEXT,0,1000000000
    invoke SendMessage,hEdit,EM_SETOPTIONS,ECOOP_XOR,ECO_SELECTIONBAR
    invoke SendMessage,hEdit, EM_SETEVENTMASK, 0, ENM_SELCHANGE or ENM_CHANGE


And here reacting to the selchange event:

WndProc proc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
   
    ; Two spare buffers + pointers
    ; for text manipulation etc..
    LOCAL buf1[260]:BYTE
    LOCAL buf2[260]:BYTE
    LOCAL pbuf1:DWORD
    LOCAL pbuf2:DWORD
    ; temp variable for storing 32 bit values temporarily if needed
    LOCAL tmpd:DWORD

    Switch uMsg
      Case WM_COMMAND
      ...
      ...
      CASE WM_NOTIFY
        mov edx, lParam
        mov eax, [edx].NMHDR.code

        .if eax == EN_SELCHANGE
            mov pbuf1, ptr$(buf1)
            invoke SendMessage, hEdit, EM_EXLINEFROMCHAR, 0, -1
            inc eax ; because its 0 based
            mov pbuf1, cat$(pbuf1, "Line: ", str$(eax))
           
            invoke SendMessage, hStatus, SB_SETTEXT, 0, pbuf1 ; hStatus is the handle to the status bar

        .endif

      case WM_CLOSE
      ...
      ...



jj2007

            invoke SendMessage, hEdit, EM_EXLINEFROMCHAR, 0, -1
            mov tmpd, eax
            inc tmpd ; because its 0 based
            mov pbuf1, cat$(pbuf1, "Line: ", str$(tmpd))


Try
            invoke SendMessage, hEdit, EM_EXLINEFROMCHAR, 0, -1
            inc eax ; because its 0 based
            mov pbuf1, cat$(pbuf1, "Line: ", str$(eax))

rc

Quote from: jj2007 on October 31, 2019, 02:15:44 AM
Try
            invoke SendMessage, hEdit, EM_EXLINEFROMCHAR, 0, -1
            inc eax ; because its 0 based
            mov pbuf1, cat$(pbuf1, "Line: ", str$(eax))


This works exactly the same way :). (I've updated the post above)
I still don't know when do i have to put stuff into a variable
and when can i use the register itself as an argument.

I know this is specific to the macro engine of masm, normally one would push the arguments onto the stack in reverse order and use the call instruction to invoke a function.

hutch--

Its usually the case that you use variables first when you lay out an algorithm as registers are a scarce resource. When you have an algorithm working, you can then optimise it with any spare registers you may have available and get it faster or smaller or both but if you start with variables, you don't directly run out of registers before you get it working. As most memory to memory data exchanges (variables) must be done through registers, you must use at least some but get it working first.

rc

Thanks for this advice, i will definitly keep this in mind!