c++user-interfacewinapirounded-cornerscustom-button

Custom rounded button using win32


I want to create a custom button using Win32. After searching, I found a group of methods, including using BS_OWNERDRAW, and I got the desired result and this is when the button is rectangular, but when I wanted to create a rounded button, I faced a problem, which is the appearance of a white background behind the button and despite a lot of research, I did not find the right way to get rid of the white background. I hope you can help me.

This is the code I used:

#define UNICODE
#include <windows.h>
#include <gdiplus.h>

using namespace Gdiplus;

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{

    // Define window properties variables
    const wchar_t g_szClassName[] = L"myWindowClass";

    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    // Registering the Window Class
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"Failed to register window class", L"Error", MB_OK);
        return 1;
    }

    // Creating the window
    HWND hWnd = CreateWindowEx(0, L"myWindowClass", L"New Window",
                            WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
                            500, 350, NULL, NULL, GetModuleHandle(NULL), NULL);

    if (hWnd == NULL)
    {
        MessageBox(NULL, L"Failed to create window", L"Error", MB_OK);
        return 1;
    }

    // Show the window
    ShowWindow(hWnd, SW_SHOW);

    // Message loop
    while (GetMessage(&Msg, NULL, 0, 0))
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

    return Msg.wParam;
}

LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HWND buttonHandle;
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    switch (uMsg)
    {
    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_CREATE:
    {
        // cretae the button
        buttonHandle = CreateWindowEx(0, L"BUTTON", L"OK",
                                    WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
                                    50, 50, 200, 100, hwnd, (HMENU)1, NULL, NULL);

        break;
    }
    case WM_PAINT:
    {
        // fill the window with a solid color
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        HBRUSH hBrush = CreateSolidBrush(RGB(30, 30, 30));
        FillRect(hdc, &ps.rcPaint, hBrush);
        DeleteObject(hBrush);
        EndPaint(hwnd, &ps);
        return 0;
    }
    case WM_DRAWITEM:
    {
        LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;

        // Get button rectangle
        RECT buttonRect = lpdis->rcItem;

        // Create Gdiplus::Graphics object from device context
        Gdiplus::Graphics graphics(lpdis->hDC);
        graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); // Set anti-alias mode for smoother edges
        // Define button color with alpha channel (transparency)
        Gdiplus::Color buttonColor = Gdiplus::Color(255, 170, 70, 70);

        // Create solid brush for button color
        Gdiplus::SolidBrush brush(buttonColor);
        int x = buttonRect.left;
        int y = buttonRect.top;
        int width = buttonRect.right - buttonRect.left;
        int height = buttonRect.bottom - buttonRect.top;
        // Draw a rounded rectangle
        int cornerRadius = 40;
        Gdiplus::GraphicsPath path;
        path.AddArc(x, y, cornerRadius, cornerRadius, 180, 90);
        path.AddArc(x + width - cornerRadius, y, cornerRadius, cornerRadius, 270, 90);
        path.AddArc(x + width - cornerRadius, y + height - cornerRadius, cornerRadius, cornerRadius, 0, 90);
        path.AddArc(x, y + height - cornerRadius, cornerRadius, cornerRadius, 90, 90);
        path.CloseFigure();
        graphics.FillPath(&brush, &path);
        break;
    }

    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    return 0;
}

The result was as follows

rounded button win32


UPDATE:

Among the answers provided below is an answer from Remy Lebeau. The first method did not work.

The second method, where you use SetLayeredWindowAttributes, gave a result closer to what is required, but with some impurities, as in the picture.

ٌRemy Lebeau answer

UPDATE 2:

The first method provided by Remy Lebeau is successful after adding WS_CLIPSIBLINGS , but I lost the smooth edges feature, and this is a picture of the result :

remy lebeau result update

We can get the same result using the second method if we do not use SetSmoothingMode, but here we also lose the advantage of smooth edges.


Solution

  • First off, you are calling GdiplusStartup() in the wrong place. By having it at the top of MainWndProc(), you are calling it for every message that your window receives. You need to call GdiplusStartup() only once, so move in into WinMain() instead. And also, you need to call GdiplusShutdown() before exiting from WinMain().

    Now, that being said, your button has a default rectangular shape, but you are simply not drawing it as a rectangle. That is why you are seeing the white areas.

    You can use CreateRoundRectRgn() and SetWindowRgn() to give the button a true non-rectangular shape to match your drawing.

    For example:

    #define UNICODE
    #include <windows.h>
    #include <gdiplus.h>
    
    using namespace Gdiplus;
    
    LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
        GdiplusStartupInput gdiplusStartupInput;
        ULONG_PTR gdiplusToken;
    
        // Starting GDI+
        if (GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) != Status::Ok)
        {
            MessageBox(NULL, L"Failed to start GDI+", L"Error", MB_OK);
            return 1;
        }
    
        // Registering the Window Class
        WNDCLASSEX wc = {};
        wc.cbSize = sizeof(WNDCLASSEX);
        wc.style = 0;
        wc.lpfnWndProc = MainWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hInstance;
        wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = CreateSolidBrush(RGB(30, 30, 30));
        wc.lpszMenuName = NULL;
        wc.lpszClassName = L"myWindowClass";
        wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    
        if (!RegisterClassEx(&wc))
        {
            MessageBox(NULL, L"Failed to register window class", L"Error", MB_OK);
            GdiplusShutdown(gdiplusToken);
            return 1;
        }
    
        // Creating the window
        HWND hWnd = CreateWindowEx(0, wc.lpszClassName, L"New Window",
                                WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
                                500, 350, NULL, NULL, GetModuleHandle(NULL), NULL);
    
        if (hWnd == NULL)
        {
            MessageBox(NULL, L"Failed to create window", L"Error", MB_OK);
            GdiplusShutdown(gdiplusToken);
            return 1;
        }
    
        // Show the window
        ShowWindow(hWnd, SW_SHOW);
    
        // Message loop
        MSG Msg;
        while (GetMessage(&Msg, NULL, 0, 0))
        {
            TranslateMessage(&Msg);
            DispatchMessage(&Msg);
        }
    
        GdiplusShutdown(gdiplusToken);
        
        return Msg.wParam;
    }
    
    LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;
    
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    
        case WM_CREATE:
        {
            // create the button
            HWND buttonHandle = CreateWindowEx(0, L"BUTTON", L"OK",
                                        WS_CLIPSIBLINGS | WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
                                        50, 50, 200, 100, hwnd, (HMENU)1, NULL, NULL);
    
            // create a region to shape the button
            HRGN hRgn = CreateRoundRectRgn(0, 0, 200, 100, 40, 40);
            SetWindowRgn(buttonHandle, hRgn, TRUE);
            break;
        }
    
        case WM_DRAWITEM:
        {
            LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
    
            // Get button rectangle
            RECT buttonRect = lpdis->rcItem;
            int x = buttonRect.left;
            int y = buttonRect.top;
            int width = buttonRect.right - buttonRect.left;
            int height = buttonRect.bottom - buttonRect.top;
    
            // Create Graphics object from device context
            Graphics graphics(lpdis->hDC);
            graphics.SetSmoothingMode(SmoothingModeAntiAlias); // Set anti-alias mode for smoother edges
    
            // Create solid brush for button color
            SolidBrush brush(Color(170, 70, 70));
            // Draw a rounded rectangle
            int cornerRadius = 40;
            GraphicsPath path;
            path.AddArc(x, y, cornerRadius, cornerRadius, 180, 90);
            path.AddArc(x + width - cornerRadius, y, cornerRadius, cornerRadius, 270, 90);
            path.AddArc(x + width - cornerRadius, y + height - cornerRadius, cornerRadius, cornerRadius, 0, 90);
            path.AddArc(x, y + height - cornerRadius, cornerRadius, cornerRadius, 90, 90);
            path.CloseFigure();
            graphics.FillPath(&brush, &path);
            break;
        }
    
        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
    
        return 0;
    }
    

    Alternatively, on Windows 8+ you can assign the WS_EX_LAYERED window style to the button (prior to Win8, WS_EX_LAYERED works only on top-level windows), and then use SetLayeredWindowAttributes() or UpdateLayeredWindow() to make the button have true transparency in the areas that you don't want to draw.

    For example:

    #define UNICODE
    #include <windows.h>
    #include <gdiplus.h>
    
    using namespace Gdiplus;
    
    LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
        GdiplusStartupInput gdiplusStartupInput;
        ULONG_PTR gdiplusToken;
    
        // Starting GDI+
        if (GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) != Status::Ok)
        {
            MessageBox(NULL, L"Failed to start GDI+", L"Error", MB_OK);
            return 1;
        }
    
        // Registering the Window Class
        WNDCLASSEX wc = {};
        wc.cbSize = sizeof(WNDCLASSEX);
        wc.style = 0;
        wc.lpfnWndProc = MainWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hInstance;
        wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = CreateSolidBrush(RGB(30, 30, 30));
        wc.lpszMenuName = NULL;
        wc.lpszClassName = L"myWindowClass";
        wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    
        if (!RegisterClassEx(&wc))
        {
            MessageBox(NULL, L"Failed to register window class", L"Error", MB_OK);
            GdiplusShutdown(gdiplusToken);
            return 1;
        }
    
        // Creating the window
        HWND hWnd = CreateWindowEx(0, wc.lpszClassName, L"New Window",
                                WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
                                500, 350, NULL, NULL, GetModuleHandle(NULL), NULL);
    
        if (hWnd == NULL)
        {
            MessageBox(NULL, L"Failed to create window", L"Error", MB_OK);
            GdiplusShutdown(gdiplusToken);
            return 1;
        }
    
        // Show the window
        ShowWindow(hWnd, SW_SHOW);
    
        // Message loop
        MSG Msg;
        while (GetMessage(&Msg, NULL, 0, 0))
        {
            TranslateMessage(&Msg);
            DispatchMessage(&Msg);
        }
    
        GdiplusShutdown(gdiplusToken);
        
        return Msg.wParam;
    }
    
    LRESULT CALLBACK MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;
    
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    
        case WM_CREATE:
        {
            // create the button
            HWND buttonHandle = CreateWindowEx(WS_EX_LAYERED, L"BUTTON", L"OK",
                                        WS_VISIBLE | WS_CHILD | BS_OWNERDRAW,
                                        50, 50, 200, 100, hwnd, (HMENU)1, NULL, NULL);
    
            // set button transparent color (fuchsia)
            SetLayeredWindowAttributes(buttonHandle, RGB(255, 0, 255), 255, LWA_COLORKEY);
            break;
        }
    
        case WM_DRAWITEM:
        {
            LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
    
            // Get button rectangle
            RECT buttonRect = lpdis->rcItem;
            int x = buttonRect.left;
            int y = buttonRect.top;
            int width = buttonRect.right - buttonRect.left;
            int height = buttonRect.bottom - buttonRect.top;
    
            // Create Graphics object from device context
            Graphics graphics(lpdis->hDC);
            graphics.SetSmoothingMode(SmoothingModeAntiAlias); // Set anti-alias mode for smoother edges
    
            // Create solid brush for button background color (fuchsia)
            SolidBrush bkgBrush(Color(255, 0, 255));
            // Draw the background
            graphics.FillRectangle(&bkgBrush, x, y, width, height);
    
            // Create solid brush for button color
            SolidBrush buttonBrush(Color(170, 70, 70));
            // Draw a rounded rectangle
            int cornerRadius = 40;
            GraphicsPath path;
            path.AddArc(x, y, cornerRadius, cornerRadius, 180, 90);
            path.AddArc(x + width - cornerRadius, y, cornerRadius, cornerRadius, 270, 90);
            path.AddArc(x + width - cornerRadius, y + height - cornerRadius, cornerRadius, cornerRadius, 0, 90);
            path.AddArc(x, y + height - cornerRadius, cornerRadius, cornerRadius, 90, 90);
            path.CloseFigure();
            graphics.FillPath(&buttonBrush, &path);
            break;
        }
    
        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
    
        return 0;
    }