11

我需要实现EM_SETCUEBANNER的功能,其中一个文本提示出现在 Edit 控件中:

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

问题是我不能使用第 6 版的通用控件,这是获得 Microsoft 提供的提示横幅实现所必需的。

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

Dark Gray Italic Text

但它会抛出我无法避免的更改事件(更高组件库提供的组件包装器)。

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

Edit 控件并没有很好地公开自定义绘图机制,如 ListView、TreeView 和其他提供的 .

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

从事情的方式来看,我将不得不处理以下消息:

  • WM_ERASEBKGND、WM_PAINT(原因很明显)
  • WM_SETFOCUS、WM_KILLFOCUS(防止白条显示——如上所述)
  • WM_CHAR(处理和更新控件中的文本)

而且我还需要找到一种在控件中显示插入符号的方法,因为我还没有找到一种方法来允许 Windows 为我做到这一点,而无需同时绘制我提到的白条。

这将会非常好玩。:翻白眼:

鉴于 Windows Edit 控件从未打算自定义绘制:有谁知道如何自定义绘制 Windows Edit 控件?


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

4

5 回答 5

10

自定义绘制 Edit 控件基本上是不可能的。有一些特殊的情况是你做的太少以至于可以侥幸逃脱,但是你有可能在下一个版本的 windows 中破坏严重(或者当有人在旧版本上运行你的应用程序时,或者通过终端服务等)。

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

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

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

于 2009-12-24T04:50:24.747 回答
5

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

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

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

于 2009-12-24T04:32:50.547 回答
5

将 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 支持、带有复制和粘贴的上下文菜单、高对比度模式以及文本到语音等可访问性功能,则需要做很多工作才能做到正确。因此,与许多其他答案不同,我建议不要仅仅为提示横幅文本实现您自己的 EDIT 控件。

于 2015-07-17T23:35:34.800 回答
3

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

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

于 2009-12-23T22:41:20.067 回答
0

而且我还需要找到一种在控件中显示插入符号的方法,因为我还没有找到一种方法来允许 Windows 为我做到这一点,而无需同时绘制我提到的白条。

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

结论:编写自己的控件。一分耕耘一分收获。

于 2012-10-06T11:27:19.717 回答