Win32:如何自定义绘制编辑控件?

发布于 2024-08-15 16:31:02 字数 1362 浏览 7 评论 0原文

我需要实现 EM_SETCUEBANNER 的功能,其中出现文本提示在编辑控件内:

编辑控件中提示横幅的示例

问题是我无法使用通用控件的版本 6,这是获得 Microsoft 提供的提示横幅实现所需的内容。

我研究过简单地更改编辑控件的文本和字体格式,

Dark Gray Italic Text

但它会抛出 Change 事件(由更高组件库提供的组件包装)我找不到办法避免。

因此,我打算自定义绘制文本,在控件未聚焦且为空时绘制提示横幅文本,否则依赖默认绘制。

编辑控件不能很好地公开自定义绘图机制,例如 ListView、TreeView 和其他人提供。

其他人已经研究过,但似乎这是一项几乎不可能完成的任务:

从目前的情况来看,我会 必须处理以下事项 消息:

  • WM_ERASEBKGND、WM_PAINT(出于显而易见的原因)
  • WM_SETFOCUS、WM_KILLFOCUS(防止 白色条不再显示—— 如上所述)
  • WM_CHAR(处理和更新 控件中的文本)

我还需要找到一种方法 在控件中显示插入符号, 因为我还没有找到一种方法允许 Windows 也可以为我做到这一点 画我提到的白色条。

这会很有趣。 :翻白眼:

鉴于 Windows 编辑控件从来都不是自定义绘制的:有谁知道如何自定义绘制 Windows 编辑控件吗?


注意:我也会接受解决我的问题的答案,而不是回答我的问题。但是任何其他想要自定义绘制编辑控件的人,遇到这个问题时,可能会想要一个答案。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

只为一人 2024-08-22 16:31:02

自定义绘制编辑控件基本上是不可能的。有一些特殊情况,您做得很少,可以逃脱惩罚,但您可能会在 Windows 的下一个版本中严重破坏(或者当有人在旧版本上或通过终端服务运行您的应用程序时)。

仅仅接管 WM_PAINT 和 WM_ERASEBKGROUND 还不够好,因为该控件有时也会在其他消息上绘制。

您最好只编写自己的编辑控件。我知道这是一项巨大的工作量,但从长远来看,这比试图破解所有编辑控件的绘图代码要少。

我记得在过去的美好时光,当时每个人都使用子类化按钮控件来添加颜色和图形等。事情是,有一天我坐下来编写了自己的按钮窗口类。与我们在源代码树中子类化和自定义绘制 Windows 按钮相比,它的代码更少。

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.

裂开嘴轻声笑有多痛 2024-08-22 16:31:02

创建一个您自己的窗口类,它看起来像空的编辑控件,它绘制提示文本并显示插入符号并具有焦点。还创建编辑控件,但将其放置在窗口后面。 (或将其隐藏)

然后当您收到第一个 WM_CHAR 消息(或 WM_KEYDOWN?)时。您将窗口放在编辑控件后面,将焦点放在编辑上,然后传递 WM_CHAR 消息。从那时起,编辑控件将接管。

如果您需要在编辑变空时返回显示提示文本,您可以收听编辑控件中的 EN_CHANGE 通知。但我认为只有当编辑失去焦点并且为空时才返回提示文本就可以了。

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.

山川志 2024-08-22 16:31:02

子类化 EDIT 控件对我来说效果很好 - 需要在编辑对象属性时向用户显示一些格式信息(某些属性可能是多行)。正如 Adrian 在他的回答中所说,重要的是在您自己的绘图之前调用 EDIT 控件的过程。之后调用它或发出您自己的 BeginPaint/EndPaint (返回 0 或 DefWindowProc)给我带来了一些问题,从根本不显示文本,到仅在调整大小时显示但在编辑后不显示,到留下剩余插入符的屏幕垃圾。这样,无论 EDIT 控件的其他重绘时间如何,我都没有遇到任何问题。

一些设置:

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>");

回调:

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;

如果您只做最低限度的工作(也许只使用英语),那么编写您自己的 EDIT 控件(实际上我已经不止一次地完成了,与内置控件相比,达到了部分完整性)并没有太多工作。基本插入符号支持),但如果您希望在具有可变大小簇的复杂脚本上进行插入符号导航、范围选择、IME 支持、具有复制和粘贴功能的上下文菜单、高对比度模式以及辅助功能(例如文本到语音。因此,与许多其他答案不同,我建议不要仅针对提示横幅文本实现您自己的编辑控件。

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.

灯下孤影 2024-08-22 16:31:02

对编辑控件进行子类化。通过首先调用原始窗口过程来处理WM_PAINT,然后,如果它是空的并且不在焦点中,则绘制提示文本。将所有其他消息传递给原始窗口过程。

我已经这样做了——它有效。 CodeGuru 人遇到的问题似乎不适用于您的情况。我相信他正在努力在外表上做更多的事情。出于性能考虑,编辑控件似乎正在 WM_PAINT 处理之外进行一些更新(可能是为了性能)。这将使得几乎不可能完全控制外观。但你可以画出cue提示。

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.

骄兵必败 2024-08-22 16:31:02

而且我还需要找到一种在控件中显示插入符号的方法,
因为我还没有找到一种方法可以让 Windows 为我做到这一点
还画了我提到的白色条。

如果你想自己处理 WM_PAINT 而不将消息转发到你的超类的原始 windowproc,你不应该忘记调用 DefWindowProc。这样插入符号就会被绘制出来。
为了避免出现白条,您应该使用 SetClassLongPtr 删除类画笔。
并以某种方式保持 DC 的剪辑区域来剪辑编辑控件的 ExtTextOut 输出。
白条可能是编辑控件传递给 ExtTextOut 的 OPAQUE 选项的结果。

结论:编写自己的控件。没有痛苦,就没有收获。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文