【问题标题】:Win32: How to custom draw an Edit control?Win32:如何自定义绘制编辑控件?
【发布时间】:2010-12-29 15:47:32
【问题描述】:

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

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

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

Dark Gray Italic Text

但它会引发我无法避免的 Change 事件 (component wrapper provided by higher component library)。

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

Edit 控件没有很好地公开自定义绘图机制,like ListView, TreeView and others provide

Other people have looked into it,但这似乎是一项几乎不可能完成的任务:

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

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

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

这会很有趣。 :rolleyes:

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


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

【问题讨论】:

  • 你可以像我在 Aero.Controls bitbucket.org/factormystic/aero.controls987654325@ 中使用即时搜索框那样伪造它
  • 我之前尝试过你的方法。我很好奇你是如何解决绘制“SearchBox”主题的问题的。但我看到你没有解决问题。
  • 6 年过去了,我只说 CreateWindow,默认带有一些文本。当用户单击控件时,您会擦除文本。您还可以像示例中那样将字体设置为斜体并在单击时更改它。

标签: windows winapi custom-draw


【解决方案1】:

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

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

【讨论】:

  • 问题是并非所有的绘画都发生在/来自/在WM_PAINT 中。有时EDIT 控件会在WM_KILLFOCUS 之后绘制自己 - 并且不会向控件发送 WM_PAINT 消息(即它不会使自身无效)。这意味着您还必须处理 WM_KILLFOCUS 并在它之后触发您自己的绘制。还有WM_SETFOCUSWM_KEYUP', and WM_KEYDOWN, and other events that cause a paint without WM_PAINT`。
  • @IanBoyd:我承认编辑控件在其他时候会作弊和绘画,但我已经使用这种技术实现了提示文本,并且效果很好。
  • @AdrianMcCarthy BeginPaint,在调用原始窗口过程后有一个空的无效矩形区域(因为它已经处理)。在调用原始 Windows 过程之前调用 BeginPaint 将不起作用。创建我自己的设备上下文会导致闪烁(特别是在右键单击事件上),并且优化字符串绘制似乎相当困难(因为它需要将其限制为在任意区域进行更新)。简而言之,我想不出一个简单的方法来绘制提示横幅并且没有闪烁;不过很有趣:)
  • @Pooven:“创建我自己的设备上下文”——我假设您的意思是 GetDC。对闪烁感到不快。我不记得在我的实现中看到过。
  • @AdrianMcCarthy 是的,我使用了GetDC。在控件之间更改焦点时,闪烁并不是那么糟糕。它主要是由右键单击事件引起的。大多数实现会在控件获得焦点时清除横幅;也许你也是这种情况?这或许可以解释我们不同的经历。不过谢谢你的想法:)
【解决方案2】:

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

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

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

【讨论】:

  • 有趣的方法,而不仅仅是子类化。它需要两个单独的窗口,并在覆盖控件和父窗口上处理代码,但它会起作用。我想知道您使用什么功能来复制 EDIT 控件的必要框架和样式,包括任何活动主题(ThemeDrawText?)。创建第二个编辑控件以匹配外观可能会更容易,然后创建另一个您实际用于保存内容的编辑控件,注意父级中两者的 EN_CHANGE。
【解决方案3】:

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

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

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

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

【讨论】:

  • 你似乎是对的。如果我想绘制提示文本,我将编辑子类化,处理 WM_PAINT。有时(使用鼠标选择,在空编辑中按退格键)控件将自身绘制为空而不调用 WM_PAINT。另一方面,编辑中存在 LOT 的功能,我无法证明对代码的所有投资都是合理的。 (剪切/复制/粘贴、撤消缓冲区、IME、RTL 等)
  • 是的,无论哪种方式都不会是一大堆代码。我只是说不要自欺欺人地认为用更少的代码进行子类化。从长远来看,它可能不会。
  • 我最终对编辑进行了子类化,触发了绘制以响应其他消息,而不仅仅是WM_PAINT。导致内部绘制发生的一些其他消息,没有任何WM_PAINT 发生,并要求我重新绘制它们的重新绘制:WM_SETFOCUSWM_KILLFOCUS', WM_KEYUP, WM_KEYDOWN`,以及鼠标进入和离开时。
  • @Ian:那么,如果您最终将控件子类化,为什么您选择这个建议从头开始编写自己的控件的答案?
  • @Adrian McCarthy:因为它不能完全工作。你必须层层破解——你仍然无法让它正常工作。更糟糕的是它非常脆弱;对下面的编辑控件的任何更改都可能会中断。子类化编辑控件不能被视为问题的答案。 最好它可以称为一种解决方法。让这成为正确答案(基本上不可能
【解决方案4】:

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

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

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

【讨论】:

    【解决方案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 控件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-09
      • 1970-01-01
      • 1970-01-01
      • 2014-04-01
      • 1970-01-01
      相关资源
      最近更新 更多