11
votes

i need to implement the functionality of EM_SETCUEBANNER, where a text hint appears inside an Edit control:

Example of cue banner in edit control

The catch is that i cannot use version 6 of the Common Controls, which is what is required to get the Microsoft supplied implementation of a cue banner.

i've looked into simply changing the text of the edit control, and the font format to

Dark Gray Italic Text

but it will throw Change events (component wrapper provided by higher component library) that i can't find a way to avoid.

So i was instead going to custom draw the text, drawing the Cue Banner text when the control is unfocused and empty, and rely on default painting otherwise.

The Edit control doesn't nicely expose a custom drawing mechanism, like ListView, TreeView and others provide.

Other people have looked into it, but it seems to be an nearly impossible task:

From the way things are looking, I'll have to handle the following messages:

  • WM_ERASEBKGND, WM_PAINT (for obvious reasons)
  • WM_SETFOCUS, WM_KILLFOCUS (to prevent the white bar from displaying -- described above)
  • WM_CHAR (to process and update the text in the control)

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

This is going to be fun. :rolleyes:

Given that the Windows Edit control was never meant to be custom drawn: does anyone know how to custom draw a Windows Edit control?


Note: i will also accept answers that solve my problem, rather than answering my question. But anyone else wanting to custom draw an Edit control, coming across this question, would probably like an answer.

5
You could just fake it like I did with the instant search box in Aero.Controls bitbucket.org/factormystic/aero.controlsFactor Mystic
i had tried your approach before. i was curious how you solved the problem of drawing the "SearchBox" themed. But i see you didn't solve the problem.Ian Boyd
6 years on, I say just CreateWindow with some text as default. When the user clicks the control you erase the text. You can also set the font to italic as in the example and change it on click.kundrata

5 Answers

10
votes

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

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

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

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

5
votes

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

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

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

5
votes

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

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

3
votes

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

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

0
votes

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

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

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