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

时间:2009-12-23 21:56:31

标签: windows winapi custom-draw

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

Example of cue banner in edit control

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

我研究过只需更改编辑控件的文本,将字体格式更改为

Dark Gray Italic Text

但它会抛出我无法避免的更改事件(component wrapper provided by higher component library)。

所以我转而自定义绘制文本,在控件未聚焦且为空时绘制Cue Banner文本,否则依赖于默认绘画。

“编辑”控件不能很好地公开自定义绘图机制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编辑控件?


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

5 个答案:

答案 0 :(得分:9)

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

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

最好只编写自己的编辑控件。我知道这是一项繁重的工作,但从长远来看,它不会试图破解你接管所有Edit控件的绘图代码。

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

答案 1 :(得分:5)

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

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

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

答案 2 :(得分: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控件。

答案 3 :(得分:3)

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

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

答案 4 :(得分:0)

  

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

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

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