Unknownguy
Unknownguy

Reputation: 191

C++ Win32 Displaying a bitmap with transparent background for button

I have a button which is subclassed and I would like it to display a bitmap with a transparent background. Upon searching the internet, I found that you have to do it with the function AlphaBlend( I tried this but that didn't work either). I also saw this bitmap transparency in Visual Studio 2013 C++ thread and I tried the steps but I couldn't do it. I would be fine with either GDI or GDI+ just as long as i can get button with a transparent background image on it. Example:

enter image description here

Here is my code—— I know it's messy but please bare with it as I was trying a lot of things to try and make it work (there was a lot of copying and pasting)

Update : Here is the code again but trimmed without all the fat and yes I did try WS_EX_LAYERED on both the windows.

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

#include "framework.h"
#include "CustomButton2.h"
#include <commctrl.h>
#include <gdiplus.h>
#include <system_error>
#include "SkinClass/skin.h"
#define MAX_LOADSTRING 100
#define CRAPPY 567
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' " "version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

// Global Variables:
HINSTANCE hInst;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name
const char g_szClassName[] = "MyClassName";

// Forward declarations of functions included in this code module:
ATOM                MyRegisterClass(HINSTANCE hInstance);
void                RegisterClass1(HINSTANCE);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);





/// Turn given window into a layered window and load a bitmap from given resource ID 
/// into it.
/// The window will be resized to fit the bitmap.
/// Bitmap must be 32bpp, top-down row order, premultiplied alpha.
///
/// \note For child windows, this requires Win 8 or newer OS
///       (and "supportedOS" element for Win 8 in application manifest)  
///
/// \exception Throws std::system_error in case of any error.

void SetLayeredWindowFromBitmapResource(
    HWND hwnd, UINT bitmapResourceId, HINSTANCE hInstance = nullptr)
{
    // Enable "layered" mode for the child window. This enables full alpha channel 
    // transparency.

    // GetWindowLong() won't reset the last error in case of success.
    // As we can't judge from the return value of GetWindowLong() alone if 
    // the function was successful (0 may be returned even in case of
    // success), we must reset the last error to reliably detect errors.
    ::SetLastError(0);
    DWORD exStyle = ::GetWindowLong(hwnd, GWL_EXSTYLE);
    if (!exStyle)
    {
        // NOTE: Call GetLastError() IMMEDIATELY when a function's return value 
        // indicates failure and it is documented that said function supports 
        // GetLastError().
        // ANY other code (be it your own or library code) before the next line 
        // must be avoided as it may invalidate the last error value.
        if (DWORD err = ::GetLastError())
            throw std::system_error(static_cast<int>(err),
                std::system_category(),
                "SetLayeredWindowFromBitmapResource: Could not get extended window style");
    }

    // SetWindowLong() won't reset the last error in case of success.
    // As we can't judge from the return value of GetWindowLong() alone if 
    // the function was successful (0 may be returned even in case of
    // success), we must reset the last error to reliably detect errors.
    ::SetLastError(0);
    if (!::SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED))
    {
        if (DWORD err = ::GetLastError())
            throw std::system_error(static_cast<int>(err),
                std::system_category(),
                "SetLayeredWindowFromBitmapResource: Could not set extended window style");
    }

    // Use RAII ( https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization )
    // to cleanup resources even in case of exceptions.
    // This greatly simplifies the code because now we don't have to manually cleanup the 
    // resources at every location in the code where we throw an exception.
    struct Resources {
        HBITMAP hImage = nullptr;
        HGDIOBJ hOldImage = nullptr;
        HDC hMemDC = nullptr;

        // This destructor will be automatically called before the function 
        // SetLayeredWindowFromBitmapResource() returns aswell as any locations 
        // in the code where the "throw" keyword is used to throw an exception.
        ~Resources()
        {
            if (hMemDC)
            {
                if (hOldImage)
                    ::SelectObject(hMemDC, hOldImage);
                ::DeleteDC(hMemDC);
            }
            if (hImage)
                ::DeleteObject(hImage);
        }
    } res;

    // Make it possible to use nullptr as an argument for the hInstance parameter of 
    // this function. This means we will load the resources from the current executable 
    // (instead of another DLL).
    if (!hInstance)
        hInstance = ::GetModuleHandle(nullptr);

    // Load bitmap with alpha channel from resource. 
    // Flag LR_CREATEDIBSECTION is required to create a device-independent bitmap that 
    // preserves the alpha channel.
    res.hImage = reinterpret_cast<HBITMAP>(::LoadImage(
        hInstance, MAKEINTRESOURCE(bitmapResourceId), IMAGE_BITMAP,
        0, 0, LR_CREATEDIBSECTION));
    if (!res.hImage)
    {
        DWORD err = ::GetLastError();
        throw std::system_error(static_cast<int>(err),
            std::system_category(),
            "SetLayeredWindowFromBitmapResource: Could not load bitmap resource");
    }

    // Get bitmap information (width, height, etc.)
    BITMAP imgInfo{ 0 };
    if (!::GetObject(res.hImage, sizeof(imgInfo), &imgInfo))
    {
        DWORD err = ::GetLastError();
        throw std::system_error(static_cast<int>(err),
            std::system_category(),
            "SetLayeredWindowFromBitmapResource: Could not get bitmap information");
    }

    if (imgInfo.bmBitsPixel != 32 || imgInfo.bmPlanes != 1)
    {
        // Use a constant error value here because this is our own error condition.
        // Of course GetLastError() wouldn't return anything useful in this case.
        DWORD err = ERROR_INVALID_DATA;
        throw std::system_error(err, std::system_category(),
            "SetLayeredWindowFromBitmapResource: bitmap must be 32 bpp, single plane");
    }

    // Create a memory DC that will be associated with the image.
    // UpdateLayeredWindow() can't use image directly, it must be in a memory DC.
    res.hMemDC = ::CreateCompatibleDC(nullptr);
    if (!res.hMemDC)
    {
        DWORD err = ::GetLastError();
        throw std::system_error(static_cast<int>(err),
            std::system_category(),
            "SetLayeredWindowFromBitmapResource: Could not create memory DC");
    }

    res.hOldImage = ::SelectObject(res.hMemDC, res.hImage);
    if (!res.hOldImage)
    {
        DWORD err = ::GetLastError();
        throw std::system_error(static_cast<int>(err),
            std::system_category(),
            "SetLayeredWindowFromBitmapResource: Could not select bitmap into memory DC");
    }

    // Assign the image to the child window, making it transparent.
    SIZE size{ imgInfo.bmWidth, imgInfo.bmHeight };
    POINT ptSrc{ 0, 0 };
    BLENDFUNCTION blend{ AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
    if (!::UpdateLayeredWindow(hwnd, nullptr, nullptr, &size, res.hMemDC, &ptSrc,
        0, &blend, ULW_ALPHA))
    {
        DWORD err = ::GetLastError();
        throw std::system_error(static_cast<int>(err),
            std::system_category(),
            "SetLayeredWindowFromBitmapResource: Could not update layered window");
    }

    // Destructor of res object will cleanup resources here!
}



int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.
    RegisterClass1(hInstance);
    HWND hWnd = CreateWindowExA(WS_EX_LAYERED, g_szClassName, "Scenes", WS_OVERLAPPEDWINDOW | WS_EX_LAYERED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
    hInstance, NULL);

    HWND hButton = CreateWindow(TEXT("BUTTON"), TEXT("START EDITING!"), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW | WS_EX_LAYERED, 1, 1,228, 228,
        hWnd, (HMENU)CRAPPY, NULL, NULL);

    //SetLayeredWindowFromBitmapResource(hButton, ID_THECIRCLE, hInstance);
    SetLayeredWindowAttributes(hWnd, 0, 249, LWA_ALPHA);

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    SetLayeredWindowFromBitmapResource(hButton, ID_THECIRCLE);

    MSG msg;

    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
            TranslateMessage(&msg);
            DispatchMessage(&msg);

    }

    return (int) msg.wParam;
}


//
//   FUNCTION: RegisterClass1()
//
//   PURPOSE: Registers the class
//
//   COMMENTS:
//
//
//
//

void RegisterClass1(HINSTANCE hInstance) {
    WNDCLASSEXA wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    //wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wc.hInstance = hInstance;
    wc.lpfnWndProc = WndProc;
    wc.lpszClassName = g_szClassName;
    wc.lpszMenuName = "MENU";
    wc.style = CS_HREDRAW | CS_VREDRAW;

    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);

    }

}



//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE: Processes messages for the main window.
//
//  WM_COMMAND  - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            {
            case IDM_ABOUT:
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: Add any drawing code that uses hdc here...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

Upvotes: 0

Views: 4642

Answers (2)

Unknownguy
Unknownguy

Reputation: 191

In my case the code that was provided didn't work because it was throwing an exception and I didn't add the manifest file properly. I would like to note some things:

Bitmap Images made with gimp do not work; use what was suggested in the thread that I linked in my question and that would be PixelFormer. In Pixel Former go "file", then "Import" and choose your file(in my case it was transparent png of a circle). Next go to "export" and choose bitmap bmp (windows bitmap it will say). Once you do that, already A8:R8:G8:B8 (32bpp) will be selected and you have to next check the last two boxes "Premultiplied Alpha" and "Top-down row order". What has to be said is that image does indeed have an alpha channel and it's transparent. Don't be fooled in opening up in paint and you see a black background and you think the transparency isn't there—— it's there the transparency works... If you open it up it in photoshop (i think) or a sophisiticated image editing program you will see a transparent background in your image.

manifest.xml

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> 
    <application>
        <!-- Windows 10 --> 
        <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
        <!-- Windows 8.1 -->
        <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
        <!-- Windows 8 -->
        <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
        <!-- Windows 7 -->
        <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
        <!-- Windows Vista -->
        <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/> 
    </application>
  </compatibility>
  <dependency>
    <dependentAssembly>
        <assemblyIdentity
            type="win32"
            name="Microsoft.Windows.Common-Controls"
            version="6.0.0.0"
            processorArchitecture="*"
            publicKeyToken="6595b64144ccf1df"
            language="*"
        />
    </dependentAssembly>
  </dependency>
</assembly>

Copy the above and save it as manifest.xml

The other thing that I would like to note is adding a manifest entry file: in my case I had to add it to C:\Users\Rubel\source\repos\CustomButton2\CustomButton2 so whoever is looking at this in the future, replace that with your directory of your project;for example, C:\Users\blah\source\repos\blah\blah (the blahs with the name of your stuff). Paste your manifest file there (in the directory).enter image description here

You then have to add it to visual studio. You do this by right clicking in your project in the solution explorer (in my case it was CustomButton2 ) and go to "Manifest Tool" in "Input and Output". In here type in "Additional Manifest Files" the path of your manifest file (in my case it was manifest.xml but if you were following along, then this should be the case as well for your path) enter image description here and click ok.

That is it, if you used the code ( Strive Sun - MSFT ) that was given it should work. Here is my final code if anyone was interested:

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

#include "framework.h"
#include "CustomButton2.h"
#include <commctrl.h>
#include <gdiplus.h>
#include <system_error>
#define MAX_LOADSTRING 100
#define CRAPPY 567
#define IDC_OWNERDRAWBUTTON 101
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' " "version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

// Global Variables:
HINSTANCE hInst;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name
const char g_szClassName[] = "MyClassName";

// Forward declarations of functions included in this code module:
ATOM                MyRegisterClass(HINSTANCE hInstance);
void                RegisterClass1(HINSTANCE);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK OwnerDrawButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);


void SetLayeredWindowFromBitmapResource(
    HWND hwnd, UINT bitmapResourceId, HINSTANCE hInstance = nullptr)
{
    DWORD exStyle = ::GetWindowLong(hwnd, GWL_EXSTYLE);

    SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);

    struct Resources {
        HBITMAP hImage = nullptr;
        HGDIOBJ hOldImage = nullptr;
        HDC hMemDC = nullptr;
        ~Resources()
        {
            if (hMemDC)
            {
                if (hOldImage)
                    ::SelectObject(hMemDC, hOldImage);
                ::DeleteDC(hMemDC);
            }
            if (hImage)
                ::DeleteObject(hImage);
        }
    } res;

    if (!hInstance)
        hInstance = ::GetModuleHandle(nullptr);

    res.hImage = reinterpret_cast<HBITMAP>(::LoadImage(
        hInstance, MAKEINTRESOURCE(bitmapResourceId), IMAGE_BITMAP,
        0, 0, LR_CREATEDIBSECTION));

    BITMAP imgInfo{ 0 };
    GetObject(res.hImage, sizeof(imgInfo), &imgInfo);

    res.hMemDC = ::CreateCompatibleDC(nullptr);
    res.hOldImage = ::SelectObject(res.hMemDC, res.hImage);

    // Assign the image to the child window, making it transparent.
    SIZE size{ imgInfo.bmWidth, imgInfo.bmHeight };
    POINT ptSrc{ 0, 0 };
    BLENDFUNCTION blend{ AC_SRC_OVER, 0, 200, AC_SRC_ALPHA };
    UpdateLayeredWindow(hwnd, nullptr, nullptr, &size, res.hMemDC, &ptSrc,
        0, &blend, ULW_ALPHA);

    // Destructor of res object will cleanup resources here!
}




int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.
    RegisterClass1(hInstance);
    HWND hWnd = CreateWindowExA(WS_EX_LAYERED, g_szClassName, "Scenes", WS_OVERLAPPEDWINDOW | WS_EX_LAYERED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
    hInstance, NULL);

    HWND hButton = CreateWindow(TEXT("BUTTON"), TEXT("START EDITING!"), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW | WS_EX_LAYERED, 1, 1,228, 228,
        hWnd, (HMENU)CRAPPY, NULL, NULL);

    SetWindowSubclass(hButton, &OwnerDrawButtonProc, IDC_OWNERDRAWBUTTON, 0);
    //SetLayeredWindowFromBitmapResource(hButton, ID_THECIRCLE, hInstance);
    SetLayeredWindowAttributes(hWnd, 0, 249, LWA_ALPHA);

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    //SetLayeredWindowFromBitmapResource(hButton, ID_THECIRCLE);

    MSG msg;

    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
            TranslateMessage(&msg);
            DispatchMessage(&msg);

    }

    return (int) msg.wParam;
}


//
//   FUNCTION: RegisterClass1()
//
//   PURPOSE: Registers the class
//
//   COMMENTS:
//
//
//
//

void RegisterClass1(HINSTANCE hInstance) {
    WNDCLASSEXA wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    //wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wc.hInstance = hInstance;
    wc.lpfnWndProc = WndProc;
    wc.lpszClassName = g_szClassName;
    wc.lpszMenuName = "MENU";
    wc.style = CS_HREDRAW | CS_VREDRAW;

    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);

    }

}



//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE: Processes messages for the main window.
//
//  WM_COMMAND  - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            {
            case IDM_ABOUT:
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: Add any drawing code that uses hdc here...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

LRESULT CALLBACK OwnerDrawButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam,
    LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    switch (uMsg)
    {
    case WM_PAINT:
    {
        RECT rc;
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        SetLayeredWindowFromBitmapResource(hWnd, ID_THECIRCLE);
        EndPaint(hWnd, &ps);
        return 0;
    }
    case WM_NCDESTROY:
        RemoveWindowSubclass(hWnd, &OwnerDrawButtonProc, 1);
        break;
    }
    return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}

Application running : enter image description here

I know this was verbose but I wanted it to be easy for whoever is encountering this so that they know how to do it.

Upvotes: 0

Strive Sun
Strive Sun

Reputation: 6299

Starting from Window 8, WS_EX_LAYERED can be used for child controls.

Method: A manifest file is required to specify at least Window 8 compatibility (sub-layering is only supported from Window 8).

Refer: Targeting your application for Windows

Code Sample: (error check has been removed)

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

#include "framework.h"
#include "Test_CustomButton.h"
#include <commctrl.h>
#include <system_error>

#pragma comment (lib,"Comctl32.lib")

#define MAX_LOADSTRING 100
#define IDC_OWNERDRAWBUTTON 101
#define CRAPPY 567

// Global Variables:
HINSTANCE hInst;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name

// Forward declarations of functions included in this code module:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK OwnerDrawButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);

void SetLayeredWindowFromBitmapResource(
    HWND hwnd, UINT bitmapResourceId, HINSTANCE hInstance = nullptr)
{
    DWORD exStyle = ::GetWindowLong(hwnd, GWL_EXSTYLE);

    SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);

    struct Resources {
        HBITMAP hImage = nullptr;
        HGDIOBJ hOldImage = nullptr;
        HDC hMemDC = nullptr;
        ~Resources()
        {
            if (hMemDC)
            {
                if (hOldImage)
                    ::SelectObject(hMemDC, hOldImage);
                ::DeleteDC(hMemDC);
            }
            if (hImage)
                ::DeleteObject(hImage);
        }
    } res;

    if (!hInstance)
        hInstance = ::GetModuleHandle(nullptr);

    res.hImage = reinterpret_cast<HBITMAP>(::LoadImage(
        hInstance, MAKEINTRESOURCE(bitmapResourceId), IMAGE_BITMAP,
        0, 0, LR_CREATEDIBSECTION));

    BITMAP imgInfo{ 0 };
    GetObject(res.hImage, sizeof(imgInfo), &imgInfo);    

    res.hMemDC = ::CreateCompatibleDC(nullptr);   
    res.hOldImage = ::SelectObject(res.hMemDC, res.hImage);

    // Assign the image to the child window, making it transparent.
    SIZE size{ imgInfo.bmWidth, imgInfo.bmHeight };
    POINT ptSrc{ 0, 0 };
    BLENDFUNCTION blend{ AC_SRC_OVER, 0, 200, AC_SRC_ALPHA };
    UpdateLayeredWindow(hwnd, nullptr, nullptr, &size, res.hMemDC, &ptSrc,
        0, &blend, ULW_ALPHA);

    // Destructor of res object will cleanup resources here!
}

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_TESTCUSTOMBUTTON, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TESTCUSTOMBUTTON));

    MSG msg;

    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TESTCUSTOMBUTTON));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_TESTCUSTOMBUTTON);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);  

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{

    switch (message)
    {
    case WM_CREATE:
    {
        HWND hButton = CreateWindowEx(0, L"button", NULL, WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, 150, 50, 80, 80, hWnd, (HMENU)IDC_OWNERDRAWBUTTON, hInst, NULL);
        SetWindowSubclass(hButton, &OwnerDrawButtonProc, IDC_OWNERDRAWBUTTON, 0);
    }
    break;
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // Used be test
            HPEN hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
            SelectObject(hdc, hPen);          
            MoveToEx(hdc, 150, 150, NULL);  
            LineTo(hdc, 200, 60);
            LineTo(hdc, 250, 150);
            LineTo(hdc, 150, 150);
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

LRESULT CALLBACK OwnerDrawButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam,
    LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    switch (uMsg)
    {        
    case WM_PAINT:
    {
        RECT rc;
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        SetLayeredWindowFromBitmapResource(hWnd, IDB_BITMAP1);
        EndPaint(hWnd, &ps);
        return 0;
    }
    case WM_NCDESTROY:
        RemoveWindowSubclass(hWnd, &OwnerDrawButtonProc, 1);
        break;
    }
    return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}

Debug:

1

Note:

When I tested, I found that the following conditions need to be met:

  • The image to load must be a 32 bpp, top-down bitmap with premultiplied alpha channel.

    Here is my 32 bpp image for your test.

  • As I said at the beginning, you need to add a manifest file, you can create a .manifest file yourself. Then add it in the compiler.

  • You only need to add the WS_EX_LAYERED style to the child control, because your requirement is to make the buttons transparent.

Upvotes: 1

Related Questions