WINAPI Изменить элемент управления с пользовательской рамкой

Как правильно реализовать пользовательские скругленные границы для управления EDIT в чистом WinAPI (без MFC)? Мне нужно редактировать с границей, как это:

Должен ли я создать подкласс управления редактированием и сделать произвольную рисование в WM_NCPAINT или что-то в этом роде?

1 ответ

Я думаю, у вас есть два варианта:

  • Как вы сказали, вы можете подкласс и переопределить WM_NCPAINTи т. д., чтобы обеспечить вашу собственную не клиентскую зону
  • В качестве альтернативы вы можете просто отключить стили границ в элементе управления редактирования и сделать родительское окно ответственным за рисование рамки.

С опцией № 1, вам нужно будет переопределить WM_NCCALCSIZE увеличить не-клиентскую область элемента управления редактирования (т.е. уменьшить клиентскую область), а затем WM_NCPAINT визуализировать ваш пользовательский кадр. Вам также может понадобиться WM_NCHITTEST, И, конечно, вам нужно было бы увеличить физически сам элемент управления, чтобы учесть дополнительную толщину рамы.

Это зависит от дизайна вашего приложения и от того, сколько элементов управления вы хотите использовать, но если бы это был я, я бы выбрал вариант №2. Изменение стандартного поведения при рисовании системных элементов управления, многие из которых имеют накопленные десятилетия накопленных кладжей и исправлений совместимости, часто не так просто, как вы могли ожидать.

Если вы убедитесь, что WS_BORDER а также WS_EX_CLIENTEDGE стили не установлены в элементе управления редактирования, у него не будет собственной видимой границы. Тогда все, что вам нужно сделать, это иметь родительское окно, при обработке WM_PAINTнарисуйте рамку вокруг него. Убедитесь, что вы установили WS_CLIPCHILDREN стиль родительского окна, чтобы ваш пользовательский рисунок не переписывал элемент управления редактирования.

Любой путь, вероятно, сработает в конце, хотя, так что вам решать, каким путем вы идете.

Это реализация, которая мне подходит. Он является подклассом элемента управления класса "EDIT" и заменяет обработчик WM_NCPAINT для рисования прямоугольника со скругленными углами для всех полей редактирования со стилем WS_BORDER или WS_EX_CLIENTEDGE. Он рисует границу родительского контроллера домена. Диаметр угла теперь фиксированный (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;
}
Другие вопросы по тегам