1

I gave an English explanation of my problem below but it is a visual issue so if you don't want to read it all just look at the picture at the bottom).

I'm working on building a reverse polish notation calculator for my class and I just completed having the button controls on my GUI be able to append their values to the edit control which works fine, but the caret is doing something weird and I can't find any information on it.

I send a custom message to the edit control in which it finds the length of the current text in the control and then places the caret at the end of the text so I can then add what text needs to be added (it is right aligned with ES_RIGHT), which again works just fine, but when the caret is in the right most place it can be, it is placed practically right through the middle of most any number.

This only seems to happen in the right most place the caret can be (i.e. anywhere else the caret sits directly to the right of the preceding char, as it should) and I have tried replacing the caret all the way to the right using code, placing it using my keyboard/mouse, and tried adjusting the dimensions of the window in hopes that it was just an offset of the width I defined for it that caused the last place to be off slightly, but the problem persists and it makes it hard to read the last char in the text field.

Relevant Code:

LRESULT CALLBACK EditBoxClass::WinProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch( msg )
    {
    case WM_COMMAND:
        break;
    case WM_APPEND_EDIT:
        /* Get current length of text in the box */
        index = new int( GetWindowTextLength (hWnd) );
        SetFocus( hWnd );
        /* Set the caret to the end of the text in the box */
        SendMessage( hWnd, EM_SETSEL, (WPARAM)index, (LPARAM)index );
        /* "Replace" the selection (the selection is actually targeting 
            nothing and just sits at the end of the text in the box) 
            with the passed in TCHAR* from the button control that 
            sent the WM_APPEND_EDIT message */
        SendMessage( hWnd, EM_REPLACESEL, 0, lParam );
        break;
    }
    return CallWindowProc( EditClassStruct.GetOldProc(), hWnd, msg, wParam, lParam );
}

Picture of problem:

image

Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
user3010974
  • 61
  • 1
  • 6
  • `index = new int( GetWindowTextLength (hWnd) );` creates a pointer to an int, which is then leaked, and probably not what you wanted anyway. Where's the declaration for `index`? – Adrian McCarthy Nov 16 '15 at 23:43

2 Answers2

2

After facing the same problem and presenting my first approach in this answer, I'll now provide two well working solutions. I think there is no other way to fix this glitch properly (unless you're a Microsoft programmer who is responsible for this part of the WinAPI).

I was wondering how to fix this problem on edit controls created with ES_MULTILINE but this glitch seems to be only a problem on single-line edit controls (tested on Windows 7 64-bit). Enabling Visual Styles is also helpful but the problem still remains (the offset is at least not so obvious).

Explanation

Normally, when the caret is at the farthest right position it's x value (provided by GetCaretPos ()) should be equal to the rect.right value provided by EM_GETRECT (when the edit control was created with ES_RIGHT). Due to unknown reasons this is not the case. So you have to check if the caret position is at least in the near of the rect.right value (but not farther away than the last letter is wide). So you have two possibilities to fulfill this task:

  1. You must calculate the width of the outer right character using GetTextExtentPoint32 (), subtract it from the rect.right value provided by calling SendMessage () with EM_GETRECT and check whether the x position of the caret is bigger than the result or not OR
  2. You must calculate the margin between the rect.right value and the outer right caret position (3 in my case) and use this value as a hardcoded offset to do a simple check.

After those steps (regardless which one you have chosen) you have to reposition the caret when necessary.

1. Approach (recommended)

    case WM_LBUTTONDOWN: {
        TRACKMOUSEEVENT tme = {sizeof (tme), TME_LEAVE, hwnd, HOVER_DEFAULT};
        TrackMouseEvent (&tme);
    }
    break;

    case WM_KEYDOWN:
    case WM_MOUSELEAVE:
    case WM_SETCURSOR: {
        DefSubclassProc (hwnd, message, wParam, lParam);

        DWORD end;
        SendMessage (hwnd, EM_GETSEL, (WPARAM) NULL, (LPARAM) &end);
        int len = GetWindowTextLength (hwnd);
        if (end < len || len <= 0)
            return TRUE;

        wchar_t *buffer = new wchar_t[len + 1];
        GetWindowText (hwnd, buffer, len + 1);
        wchar_t lastChar[] = {buffer[len - 1], '\0'};
        delete[] buffer;

        SIZE size;
        HDC hdc = GetDC (hwnd);
        if (hdc == NULL)
            return TRUE;

        GetTextExtentPoint32 (hdc, lastChar, 1, &size);
        ReleaseDC (hwnd, hdc);

        POINT pt;
        RECT rect;

        GetCaretPos (&pt);
        SendMessage (hwnd, EM_GETRECT, (WPARAM) 0, (LPARAM) &rect);
        if ((rect.right - size.cx) <= pt.x)
            SetCaretPos (rect.right, pt.y);

        return TRUE;
    }
    break;

2. Approach (improved original version)

    case WM_LBUTTONDOWN: {
        TRACKMOUSEEVENT tme = {sizeof (tme), TME_LEAVE, hwnd, HOVER_DEFAULT};
        TrackMouseEvent (&tme);
    }
    break;

    case WM_KEYDOWN:
    case WM_MOUSELEAVE:
    case WM_SETCURSOR: {
        DefSubclassProc (hwnd, message, wParam, lParam);

        POINT pt;
        RECT rect;

        GetCaretPos (&pt);
        SendMessage (hwnd, EM_GETRECT, (WPARAM) 0, (LPARAM) &rect);
        if ((rect.right - pt.x) <= 3)
            SetCaretPos (rect.right, pt.y);

        return TRUE;
    }
    break;

You have to subclass the edit controls. Then use this code in their window procedures and enjoy. In both cases, tracking the mouse event is not absolutely necessary but recommended to completly avoid this glitch. Calling DefSubclassProc () will ensure that the cursor is changed on mouse over.

  • The documentation says `EM_GETRECT`, `EM_SETRECT`, and `EM_SETRECTNP` "[apply only to multiline edit controls](https://learn.microsoft.com/en-us/windows/win32/controls/about-edit-controls#changing-the-formatting-rectangle)." It also says `EM_GETRECT` is approximate and "[can be off by a few pixels](https://learn.microsoft.com/en-us/windows/win32/controls/em-getrect#remarks)." – Adrian McCarthy May 10 '21 at 12:55
1

This may or may not be the cause, but you are misusing EM_SETSEL. You are dynamically allocating (and leaking) an int on the heap and passing a pointer to it as the message parameters, but EM_SETSEL does not expect or use pointers to begin with. So get rid of the dynamic allocation.

Also, the default window proc is not going to know how to handle your WM_APPEND_EDIT message, so there is no point in passing the message to CallWindowProc().

Try this instead:

case WM_APPEND_EDIT:
{
    /* Get current length of text in the box */
    int index = GetWindowTextLength( hWnd );
    SetFocus( hWnd );
    /* Set the caret to the end of the text in the box */
    SendMessage( hWnd, EM_SETSEL, (WPARAM)index, (LPARAM)index );
    /* "Replace" the selection (the selection is actually targeting 
        nothing and just sits at the end of the text in the box) 
        with the passed in TCHAR* from the button control that 
        sent the WM_APPEND_EDIT message */
    SendMessage( hWnd, EM_REPLACESEL, 0, lParam );
    return 0;
}

That being said, try using EM_GETRECT/EM_SETRECT to expand the right edge of the edit control's formatting rectangle by a few pixels. That should give the caret some extra room to work with.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I guess I didn't put in all of the relevant code because I have the poniter stored in the class that I made in my framework. The pointer doesn't cause a memory leak because it exists while the object exists and the object's destructor cleans it up. I am passing it the dereferenced pointer because the value that it points to is the size of the current window text. I have it set up as a pointer so I don't have to initialize it during the creation of the object but I'll try changing it to a variable and see if that works. I imagine the expanding of the rectangle is more likely to work though. – user3010974 May 31 '14 at 22:45
  • I've tried making index a straight int instead of an int*, sending EM_SETRECT after editing the returned RECT from EM_GETRECT, and manually adjusting the margins using EM_SETMARGINS. I saw that changes take effect so I know they were implemented correctly but the right most position of the caret still lays directly in the middle of the number "4" as well as several other numbers. I guess I just have to deal with it. – user3010974 May 31 '14 at 23:24
  • 1
    You do have a memory leak because you are allocating a new `int` every time you receive a `WM_APPEND_TEXT` message, and not freeing the previous `int`. But the issue is moot since you should not be using a pointer in the first place because `EM_SETSEL` does not support `int*` pointers. – Remy Lebeau Jun 01 '14 at 02:03
  • Fair enough, but the function seems to be working with the int* for some reason. But yes it is a moot point because I also tried it with just an int variable after your first reply and it changed nothing. As I said in my second reply, I have tried adjusting the rect size, the margin size, manually placing the caret, and using both int and int* to place the caret but nothing fixes the problem. Every method tried will still leave the right most place the caret can occupy lying directly over most any character that is in the right most character position. – user3010974 Jun 01 '14 at 17:03