【问题标题】:WINAPI Edit control with custom border带有自定义边框的 WINAPI 编辑控件
【发布时间】:2020-05-22 02:16:40
【问题描述】:

在纯 WinAPI(无 MFC)中为 EDIT 控件实现自定义圆角边框的正确方法是什么?我需要这样的边框编辑:

我应该继承编辑控件并在 WM_NCPAINT 或类似的东西中进行自定义绘制吗?

【问题讨论】:

    标签: winapi editcontrol


    【解决方案1】:

    我猜你有两个选择:

    • 如您所说,您可以继承并覆盖WM_NCPAINT 等以提供您自己的非客户区
    • 或者,您可以简单地关闭编辑控件上的边框样式并让父窗口负责绘制框架。

    使用选项#1,您需要覆盖WM_NCCALCSIZE 以使编辑控件的非客户区更大(即使客户区更小),然后WM_NCPAINT 以呈现您的自定义框架。您可能还需要处理WM_NCHITTEST。当然,您需要使控件本身在物理上更大以考虑额外的框架厚度。

    这取决于您的应用程序设计以及您希望使用多少这样的控件,但如果是我,我会选择选项 #2。修改系统控件的标准绘图行为,其中许多都有数十年的积累和附加的兼容性修复,通常并不像您想象的那么容易。

    如果您确保没有在编辑控件上设置WS_BORDERWS_EX_CLIENTEDGE 样式,它自己将没有可见的边框。那么您所要做的就是拥有父窗口,在处理WM_PAINT 时,在其周围绘制框架。确保在父窗口上设置WS_CLIPCHILDREN 样式,这样您的自定义绘图就不会覆盖编辑控件。

    任何一条路最终都可能会奏效,所以这取决于你走哪条路。

    【讨论】:

    • 别忘了使用SetWindowRgn() 为编辑控件提供圆润的边缘。仅绘制圆边是不够的(顺便说一句,HRGN 会帮助您做到这一点),但您必须实际塑造编辑窗口。
    • 谢谢大家,我遵循了选项 #2,但遇到了问题:如果我删除 WS_EX_CLIENTEDGE 标志,文本会垂直错位:gyazo.com/f125abcc5973cf537732eccd07481bda
    • 另外,我不能让带有圆形区域和边框绘制代码的 SetWindowRgn() 一起工作。只有当我禁用边框绘制代码 gyazo.com/6b2d505b3b24882cb4d8c30fa9519345 时,我才会看到圆形编辑;如果启用绘画代码,我会看到:gyazo.com/5cbd8bd9537c93c12e00fdc2568d222d;代码:gist.github.com/mbg033/91e262dfd5798f62000d
    • 看起来你应该让编辑控件垂直变小,或者在顶部的自定义框架中添加一些白色填充。
    • @JonathanPotter:想想如果编辑控件被放置在具有非平凡(即不是单一颜色)背景的父窗口上会发生什么。角落必须反映它们背后的实际情况,而编辑控件不能(也不应该)知道那实际上是什么。所以最好让角落真正透明,让操作系统处理这些区域的绘图。
    【解决方案2】:

    这是一个适合我的实现。 它是“EDIT”类控件的子类,并替换 WM_NCPAINT 处理程序来为所有具有 WS_BORDER 或 WS_EX_CLIENTEDGE 样式的编辑框绘制一个圆角矩形。它在父 DC 上绘制边框。 角的直径现在是固定的(10),我想这应该取决于字体大小......

    感谢 Darren Sessions 提供的 GDI+ 示例如何绘制圆角矩形: https://www.codeproject.com/Articles/27228/A-class-for-creating-round-rectangles-in-GDI-with

    #include <windows.h>
    #include <objidl.h>
    #include <gdiplus.h>
    using namespace Gdiplus;
    #pragma comment (lib,"Gdiplus.lib")
    
    inline void GetRoundRectPath(GraphicsPath* pPath, Rect r, int dia)
    {
        // diameter can't exceed width or height
        if (dia > r.Width)    dia = r.Width;
        if (dia > r.Height)    dia = r.Height;
    
        // define a corner 
        Rect Corner(r.X, r.Y, dia, dia);
    
        // begin path
        pPath->Reset();
    
        // top left
        pPath->AddArc(Corner, 180, 90);
    
        // top right
        Corner.X += (r.Width - dia - 1);
        pPath->AddArc(Corner, 270, 90);
    
        // bottom right
        Corner.Y += (r.Height - dia - 1);
        pPath->AddArc(Corner, 0, 90);
    
        // bottom left
        Corner.X -= (r.Width - dia - 1);
        pPath->AddArc(Corner, 90, 90);
    
        // end path
        pPath->CloseFigure();
    }
    
    inline void GetChildRect(HWND hChild, LPRECT rc)
    {
        GetWindowRect(hChild,rc);
        SIZE si = { rc->right - rc->left, rc->bottom - rc->top };
        ScreenToClient(GetParent(hChild), (LPPOINT)rc);
        rc->right = rc->left + si.cx;
        rc->bottom = rc->top + si.cy;
    }
    
    inline void DrawRoundedBorder(HWND hWnd, COLORREF rgba = 0xFF0000FF, int radius = 5)
    {
        BYTE* c = (BYTE*)&rgba;
        Pen pen(Color(c[0], c[1], c[2], c[3]));
        if (pen.GetLastStatus() == GdiplusNotInitialized)
        {
            GdiplusStartupInput gdiplusStartupInput;
            ULONG_PTR           gdiplusToken;
            GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
            pen.SetColor(Color(c[0], c[1], c[2], c[3]));
        }
        pen.SetAlignment(PenAlignmentCenter);
    
        SolidBrush brush(Color(255, 255, 255, 255));
    
        RECT rc = { 0 };
        GetChildRect(hWnd, &rc);
        // the normal EX_CLIENTEDGE is 2 pixels thick.
        // up to a radius of 5, this just works out.
        // for a larger radius, the rectangle must be inflated
        if (radius > 5)
        {
            int s = radius / 2 - 2;
            InflateRect(&rc, s, s);
        }
        GraphicsPath path;
        GetRoundRectPath(&path, Rect(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top), radius * 2);
    
        HWND hParent = GetParent(hWnd);
        HDC hdc = GetDC(hParent);
        Graphics graphics(hdc);
    
        graphics.SetSmoothingMode(SmoothingModeAntiAlias);
        graphics.FillPath(&brush, &path);
        graphics.DrawPath(&pen, &path);
    
        ReleaseDC(hParent, hdc);
    }
    
    static WNDPROC pfOldEditWndProc = NULL;
    
    static LRESULT CALLBACK EditRounderBorderWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_NCCREATE:
        {
            DWORD style = GetWindowLong(hWnd, GWL_STYLE);
            if (style & WS_BORDER)
            {
                // WS_EX_CLIENTEDGE style will make the border 2 pixels thick...
                style = GetWindowLong(hWnd, GWL_EXSTYLE);
                if (!(style & WS_EX_CLIENTEDGE))
                {
                    style |= WS_EX_CLIENTEDGE;
                    SetWindowLong(hWnd, GWL_EXSTYLE, style);
                }
            }
            // to draw on the parent DC, CLIPCHILDREN must be off
            HWND hParent = GetParent(hWnd);
            style = GetWindowLong(hParent, GWL_STYLE);
            if (style & WS_CLIPCHILDREN)
            {
                style &= ~WS_CLIPCHILDREN;
                SetWindowLong(hParent, GWL_STYLE, style);
            }
        }
        break;
        case WM_NCPAINT:
            if (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_CLIENTEDGE)
            {
                DrawRoundedBorder(hWnd);
                return 0;
            }
        }
        return CallWindowProc(pfOldEditWndProc, hWnd, uMsg, wParam, lParam);
    }
    
    class CRoundedEditBorder
    {
    public:
        CRoundedEditBorder()
        {
            Subclass();
        }
        ~CRoundedEditBorder()
        {
            Unsubclass();
        }
    private:
        void Subclass()
        {
            HWND hEdit = CreateWindow(L"EDIT", L"", 0, 0, 0, 200, 20, NULL, NULL, GetModuleHandle(NULL), NULL);
            pfOldEditWndProc = (WNDPROC)GetClassLongPtr(hEdit, GCLP_WNDPROC);
            SetClassLongPtr(hEdit, GCLP_WNDPROC, (LONG_PTR)EditRounderBorderWndProc);
            DestroyWindow(hEdit);
        }
        void Unsubclass()
        {
            HWND hEdit = CreateWindow(L"EDIT", L"", 0, 0, 0, 200, 20, NULL, NULL, GetModuleHandle(NULL), NULL);
            SetClassLongPtr(hEdit, GCLP_WNDPROC, (LONG_PTR)pfOldEditWndProc);
            DestroyWindow(hEdit);
        }
    };
    CRoundedEditBorder g_RoundedEditBorder;
    
    LRESULT CALLBACK ParentWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_DESTROY: PostQuitMessage(0); return 0;
        }
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    
    #define WNDCLASSNAME L"RoundedEditBorderTestClass"
    
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
    {
        GdiplusStartupInput gdiplusStartupInput;
        ULONG_PTR           gdiplusToken;
        GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    
        WNDCLASSEXW wcex = { sizeof(WNDCLASSEX), CS_HREDRAW|CS_VREDRAW,ParentWndProc,0,0,hInstance,NULL,NULL,CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW)),NULL,WNDCLASSNAME,NULL };
        RegisterClassExW(&wcex);
    
        HWND hWnd = CreateWindowW(WNDCLASSNAME, L"Rounded Edit Border Test", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
        CreateWindowEx(0, L"EDIT", L"no border", WS_CHILD | WS_VISIBLE, 10, 10, 200, 24, hWnd, NULL, GetModuleHandle(NULL), NULL);
        CreateWindowEx(0, L"EDIT", L"no ex style", WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 50, 200, 24, hWnd, NULL, GetModuleHandle(NULL), NULL);
        CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", L"Ex_ClientEdge", WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 90, 200, 24, hWnd, NULL, GetModuleHandle(NULL), NULL);
        ShowWindow(hWnd, nCmdShow);
    
        MSG msg;
        while (GetMessage(&msg, nullptr, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    
        GdiplusShutdown(gdiplusToken);
        return (int)msg.wParam;
    }
    

    【讨论】:

      猜你喜欢
      • 2018-11-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多