RoundRect on Transparent background?

1.2k Views Asked by At

I'd like to use RoundRect to draw a filled round rectangle in WM_DRAWITEM for an owner-draw control. That all works fine but for some reason it sets the areas outside of the rounded corners to white (as if it cleared the square first then drew the round rectangle). I would like it to not touch that area, treating it as transparent. How is that done?

TIA!!

2

There are 2 best solutions below

2
user3161924 On

Since this is a button control, this seems to work (I tried to put in comments but couldn't format it).

case WM_CTLCOLORBTN:
{
  if ((HWND)lparam==GetDlgItem(hwnd, IDC_BUTTON)) {
    return (LRESULT) ::GetStockObject(NULL_BRUSH);
  }
  break;
}
1
jwezorek On

Windows by default are rectangles. When you paint them you have to paint a whole rectangle. That said, there are at least three ways to owner draw a button as a round rectangle:

The easy way: Paint the area outside of the round rectangle button the same color as the background color of its parent

The old school way : Use SetWindowRgn(...) to make the owner drawn button have a round rectangle shaped clipping region. Code for doing this is included below. The trouble with this technique is that you can't have the boundary of the round rectangle be anti-aliased.

The new way : Since Windows 8 child windows can be created with WS_EX_LAYERED extended style. This should let you just paint the window as a round rectangle; however, it is complicated by the fact that (I think) you need to include a manifest that sets the supported OS of your executable to be Windows 8 and up or else the call to CreateWindowEx(WS_EX_LAYERED, ... ) will just fail (at least it does for me when I try it). Unfortunately I can't show how to include a manifest off the top of my head.

Below however is very rudimentary code demonstrating the window region way. You could fix this up by using GDI+ to do the drawing and setting antialiased drawing or by blitting a bitmap instead of painting with GDI/GDI+ calls.

// RoundRectButton.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "RoundRectButton.h"

#define BTN_ID 101

HINSTANCE g_instance = 0;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

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

    MSG msg = { 0 };
    WNDCLASS wc = { 0 };
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BACKGROUND);
    wc.lpszClassName = L"owner_draw_btn";

    if (!RegisterClass(&wc))
        return -1;

    if (!CreateWindow(wc.lpszClassName, L"foobar", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 640, 480, 0, 0, hInstance, NULL))
        return -1;

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

LRESULT HandleDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam, int corner_wd, int corner_hgt, 
        COLORREF unclicked_color, COLORREF clicked_color)
{
    auto* dis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
    if (dis->CtlType != ODT_BUTTON)
        return 0;

    COLORREF fill_color = (dis->itemState & ODS_SELECTED) ? clicked_color : unclicked_color;

    auto rect = &dis->rcItem;
    //DrawFrameControl(dis->hDC, rect, DFC_BUTTON, style);
    HPEN pen = CreatePen(PS_SOLID, 6, RGB(0, 0, 0));
    HPEN old_pen = (HPEN) SelectObject(dis->hDC, pen);
    HBRUSH brush = CreateSolidBrush(fill_color);
    HBRUSH old_brush = (HBRUSH) SelectObject(dis->hDC, brush);

    RoundRect(dis->hDC, rect->left, rect->top, rect->right, rect->bottom, corner_wd, corner_hgt);

    SelectObject(dis->hDC, old_pen);
    SelectObject(dis->hDC, old_brush);

    DeleteObject(pen);
    DeleteObject(brush);

    TCHAR text[512];
    auto n = GetWindowText(dis->hwndItem, text, 512);
    SetBkMode(dis->hDC, TRANSPARENT);
    DrawText(dis->hDC, text, n, rect, DT_SINGLELINE | DT_VCENTER | DT_CENTER);

    return 0;
}

HWND CreateRoundRectButton(HWND parent, int x, int y, int wd, int hgt, int corner_wd, int corner_hgt, int id)
{
    HWND button = CreateWindow(
        L"button", L"foobar",
        BS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
        x, y, wd, hgt, parent,
        (HMENU) id,
        g_instance,
        0
    );
    SetWindowRgn(button, CreateRoundRectRgn(0, 0, wd, hgt, corner_wd, corner_hgt), TRUE);
    return button;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND button = NULL;
    DWORD err;
    switch (message)
    {
    case WM_CREATE:
        button = CreateRoundRectButton(hWnd, 15, 15, 150, 35, 15, 15, BTN_ID);
        return 0;

    case WM_DRAWITEM:
        return HandleDrawItem(hWnd, wParam, lParam, 15, 15, RGB(230,230,230), RGB(255,255,255));

    case WM_CLOSE:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}