简体   繁体   中英

EM_SETSEL swaps parameters

I use EM_SETSEL message to select text in edit control. I need to select some text from the end to the middle, so that caret position is in the middle of the text. MSDN documentation states the following:

The start value can be greater than the end value. The lower of the two values specifies the character position of the first character in the selection. The higher value specifies the position of the first character beyond the selection.

The start value is the anchor point of the selection, and the end value is the active end. If the user uses the SHIFT key to adjust the size of the selection, the active end can move but the anchor point remains the same.

But it seems that lesser value always becomes an anchor, eg I cannot achieve the desired behaviour.

Code sample (where "parent" is CWnd*):

TRACE("EM_SETSEL(%d, %d)\n", pos1, pos2);
parent->SendMessage(EM_SETSEL, pos1, pos2);
parent->SendMessage(EM_GETSEL, (WPARAM)&pos1, (LPARAM)&pos2);
TRACE("EM_GETSEL(%d, %d)\n", pos1, pos2);

produces the output:

EM_SETSEL(5, 1)
EM_GETSEL(1, 5)

Is there another way to get the desired selection?

This can be done via a rather ugly kludge. Basically there's no way to tell the control to select text and leave the cursor at the left, but you can make the control do it itself by simulating key presses.

void EditSelSel(HWND hwndEdit, int iFirst, int iSecond)
{
    if (iFirst <= iSecond)
        SendMessage(hwndEdit, EM_SETSEL, iFirst, iSecond);
    else
    {
        SendMessage(hwndEdit, EM_SETSEL, iFirst, iFirst);

        BYTE bState[256]{}, bNewState[256]{};
        if (GetKeyboardState(bState))
        {
            memcpy(bNewState, bState, sizeof(bNewState));
            bNewState[VK_SHIFT] |= 128;
            if (SetKeyboardState(bNewState))
            {
                int i = iFirst - iSecond;
                while (i-- > 0)
                {
                    SendMessage(hwndEdit, WM_KEYDOWN, VK_LEFT, 0);
                }
                SendMessage(hwndEdit, WM_KEYUP, VK_LEFT, 0);
                SetKeyboardState(bState);
            }
        }
    }
}

This works by positioning the cursor at the right end of the selection range. We then use SetKeyboardState to trick the control into thinking the Shift key is held down, and then simulate enough Left key presses to move the range to the left end.

Ugly, but it works, so hopefully someone finds it useful.

Regarding EM_GETSEL/EM_SETSEL:

  • EM_GETSEL retrieves left/right positions
  • EM_SETSEL sets anchor/active positions

EM_SETSEL uses anchor/active positions, allowing you to easily place the caret at the left/right of the selection, so I'm not sure why a kludge was used in the other answer.

EM_GETSEL is the awkward window message, for which a kludge is necessary. This kludge temporarily changes the selection to 0 characters, in order to retrieve the active position, however, when I've used it I haven't seen any visible change.

To retrieve anchor/active positions:

  • use EM_GETSEL to retrieve the left/right positions
  • use EM_SETSEL to temporarily set the selection to 0 characters, leaving the caret at the active position
  • use EM_GETSEL to retrieve the active position
  • use EM_SETSEL to restore the original selection

Some example AutoHotkey code for setting the selection:

q:: ;Notepad - set active position (caret) at right
PostMessage, 0xB1, 5, 10, Edit1, A ;EM_SETSEL := 0xB1
return

w:: ;Notepad - set active position (caret) at left
PostMessage, 0xB1, 10, 5, Edit1, A ;EM_SETSEL := 0xB1
return

Some example AutoHotkey functions for getting/setting the selection:

JEE_EditGetRange(hCtl, ByRef vPos1, ByRef vPos2)
{
    VarSetCapacity(vPos1, 4), VarSetCapacity(vPos2, 4)
    SendMessage, 0xB0, % &vPos1, % &vPos2,, % "ahk_id " hCtl ;EM_GETSEL := 0xB0 ;(left, right)
    vPos1 := NumGet(&vPos1, 0, "UInt"), vPos2 := NumGet(&vPos2, 0, "UInt")
}

;==================================================

JEE_EditSetRange(hCtl, vPos1, vPos2, vDoScroll:=0)
{
    SendMessage, 0xB1, % vPos1, % vPos2,, % "ahk_id " hCtl ;EM_SETSEL := 0xB1 ;(anchor, active)
    if vDoScroll
        SendMessage, 0xB7, 0, 0,, % "ahk_id " hCtl ;EM_SCROLLCARET := 0xB7
}

;==================================================

;note: although this involves deselecting and selecting it seems to happen invisibly
JEE_EditGetRangeAnchorActive(hCtl, ByRef vPos1, ByRef vPos2)
{
    ;get selection
    VarSetCapacity(vPos1, 4), VarSetCapacity(vPos2, 4)
    SendMessage, 0xB0, % &vPos1, % &vPos2,, % "ahk_id " hCtl ;EM_GETSEL := 0xB0
    vPos1 := NumGet(&vPos1, 0, "UInt"), vPos2 := NumGet(&vPos2, 0, "UInt")
    if (vPos1 = vPos2)
        return
    vPos1X := vPos1, vPos2X := vPos2

    ;set selection to 0 characters and get active position
    SendMessage, 0xB1, -1, 0,, % "ahk_id " hCtl ;EM_SETSEL := 0xB1
    VarSetCapacity(vPos2, 4)
    SendMessage, 0xB0, % &vPos2, 0,, % "ahk_id " hCtl ;EM_GETSEL := 0xB0
    vPos2 := NumGet(&vPos2, 0, "UInt")

    ;restore selection
    vPos1 := (vPos2 = vPos2X) ? vPos1X : vPos2X
    SendMessage, 0xB1, % vPos1, % vPos2,, % "ahk_id " hCtl ;EM_SETSEL := 0xB1 ;(anchor, active)
}

LINKS:

The functions above that I originally posted at the AutoHotkey forums:
GUI COMMANDS: COMPLETE RETHINK - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=25893&p=138292#p138292

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM