[ACCEPTED]-Win32: How to custom draw an Edit control?-custom-draw

Accepted answer
Score: 10

Custom drawing an Edit control is essentially 21 impossible. There are a few specialized 20 cases were you are doing so little that 19 can get away with it, but you risk breaking 18 badly in the next revision of windows (or 17 when someone runs your app on an older version, or 16 via terminal services, etc).

Just taking 15 over WM_PAINT and WM_ERASEBKGROUND aren't 14 good enough, because the control will sometimes 13 paint on other messages as well.

You are 12 better off just writing your own edit control. I 11 know that's a huge amount of work, but in 10 the long run it will be less work than trying 9 to hack your way into taking over all of 8 the Edit controls' drawing code.

I remember 7 back in the good old days when everyone 6 use to subclass the button control to add 5 color and graphics, etc. The thing is, one 4 day I sat down and just wrote my own button 3 window class. and it was LESS CODE than 2 what we had in our source tree to subclass 1 and custom draw the Windows button.

Score: 5

Create a window class of your own that looks 15 like and empty edit control, that draws 14 the cue text and shows a caret and has focus. Create 13 the edit control also, but position it behind 12 your window. (or leave it hidden)

Then when 11 you get the first WM_CHAR message (or WM_KEYDOWN?). You 10 put your window behind the edit conrol, give 9 focus to the edit, and pass the WM_CHAR 8 message on. From then on the edit control 7 will take over.

You can listen to EN_CHANGE 6 notifications from the edit control if you 5 need to go back to showing your cue text 4 when the edit gets empty. But I'd think 3 that it would be fine to go back to the 2 cue text only when the edit looses focus 1 AND is empty.

Score: 5

Subclassing the EDIT control worked well 29 for me - needed to display some formatting 28 information to the user when editing object 27 attributes (and some attributes could be 26 multiple lines). The important thing, like 25 Adrian said in his answer too, is to call 24 the EDIT control's procedure before your own drawing. Calling 23 it afterward or issuing your own BeginPaint/EndPaint 22 (with return 0 or DefWindowProc) caused 21 issues for me from the text not displaying 20 at all, to it displaying only when resizing 19 but not after editing, to leaving screen 18 litter of the leftover caret. With that, I 17 haven't had any issues regardless of the 16 EDIT control's other repaint times.

Some 15 setup:

SetWindowSubclass(attributeValuesEdit, &AttributeValueEditProcedure, 0, reinterpret_cast<DWORD_PTR>(this));

// Not only do multiline edit controls fail to display the cue banner text,
// but they also ignore the Edit_SetCueBannerText call, meaning we can't
// just call GetCueBannerText in the subclassed function. So store it as
// a window property instead.
SetProp(attributeValuesEdit, L"CueBannerText", L"<attribute value>");

The callback:

LRESULT CALLBACK AttributeValueEditProcedure(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam,
    UINT_PTR subclassId,
    DWORD_PTR data
    )
{

...

case WM_PRINTCLIENT:
case WM_PAINT:
    {
        auto textLength = GetWindowTextLength(hwnd);
        if (textLength == 0 && GetFocus() != hwnd)
        {
            // Get the needed DC with DCX_INTERSECTUPDATE before the EDIT
            // control's WM_PAINT handler calls BeginPaint/EndPaint, which
            // validates the update rect and would otherwise lead to drawing
            // nothing later because the region is empty. Also, grab it from
            // the cache so we don't mess with the EDIT's DC.
            HDC hdc = (message == WM_PRINTCLIENT)
                ? reinterpret_cast<HDC>(wParam)
                : GetDCEx(hwnd, nullptr, DCX_INTERSECTUPDATE|DCX_CACHE|DCX_CLIPCHILDREN | DCX_CLIPSIBLINGS);

            // Call the EDIT control so that the caret is properly handled,
            // no caret litter left on the screen after tabbing away.
            auto result = DefSubclassProc(hwnd, message, wParam, lParam);

            // Get the font and margin so the cue banner text has a
            // consistent appearance and placement with existing text.
            HFONT font = GetWindowFont(hwnd);
            RECT editRect;
            Edit_GetRect(hwnd, OUT &editRect);

            // Ideally we would call Edit_GetCueBannerText, but since that message
            // returns nothing when ES_MULTILINE, use a window property instead.
            auto* cueBannerText = reinterpret_cast<wchar_t*>(GetProp(hwnd, L"CueBannerText"));

            HFONT previousFont = SelectFont(hdc, font);
            SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT));
            SetBkMode(hdc, TRANSPARENT);
            DrawText(hdc, cueBannerText, int(wcslen(cueBannerText)), &editRect, DT_TOP|DT_LEFT|DT_NOPREFIX|DT_NOCLIP);
            SelectFont(hdc, previousFont);

            ReleaseDC(hwnd, hdc);

            // Return the EDIT's result (could probably safely just return zero here,
            // but seems safer to relay whatever value came from the edit).
            return result;
        }
    }
    break;

Writing your own EDIT 14 control (which I've actually done more than 13 once, to partial degrees of completeness 12 compared to the built-in one) is not much 11 work if you do the bare minimum (maybe English 10 only with basic caret support), but it's 9 a LOT of work to get correct if you want 8 caret navigation over complex scripts with 7 variable sized clusters, selection over 6 ranges, IME support, context menus with 5 copy and paste, high contrast modes, and 4 accessibility features such as text to speech. So 3 unlike so many other answers, I recommend 2 not implementing your own EDIT control merely 1 for cue banner text.

Score: 3

Subclass the edit control. Handle WM_PAINT by first 14 calling the original window procedure and 13 then, if it's empty and not in focus, draw 12 the cue text. Pass every other message 11 to the original window procedure.

I've done 10 this--it works. The problem the CodeGuru 9 person had doesn't seem to apply to your 8 situation. I believe he's trying to do 7 more to the appearance. For performance, it 6 looks like the edit control is doing some 5 updates outside of WM_PAINT processing (probably 4 for performance). That's going to make 3 it nearly impossible to take complete control 2 of the appearance. But you CAN draw the 1 cue prompt.

Score: 0

And I also need to find a way to display 15 the caret in the control, since I haven't 14 found a way to allow Windows to do that 13 for me without also painting the white 12 bar I mentioned.

If you want to handle WM_PAINT 11 by yourself without forwarding the message 10 to the original windowproc of your superclass, you 9 should not forget to call DefWindowProc. So 8 that the caret will be drawn. To avoid the 7 white bar you should remove class brush 6 with SetClassLongPtr. And somehow keep your 5 DC's clipping region to clip Edit controt's 4 ExtTextOut outputs. The white bar may be 3 the result of OPAQUE option passed to ExtTextOut 2 by Edit control.

Conclusion: Write your own 1 control. No pain, no gain.

More Related questions