WINAPI使用自定义边框编辑控件

时间:2015-02-23 22:01:37

标签: winapi editcontrol

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

enter image description here

我应该将编辑控件子类化并在WM_NCPAINT中进行自定义绘制吗?

2 个答案:

答案 0 :(得分:2)

我猜你有两个选择:

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

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

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

如果您确保编辑控件上未设置WS_BORDERWS_EX_CLIENTEDGE样式,则它将没有自己的可见边框。然后你要做的就是拥有父窗口,在处理WM_PAINT时,围绕它绘制框架。确保在父窗口中设置WS_CLIPCHILDREN样式,以便自定义绘图不会覆盖编辑控件。

这两种路径最终都可能起作用,所以这取决于你走哪条路。

答案 1 :(得分:1)

这是对我有用的实现。 它子类化“ 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;
}