c++imagewinapibuttoncomctl32

Can't set button image list


I use MinGW32 with Code::Blocks IDE on Windows 10. I'm trying to assign an image list to a button via a "BUTTON_IMAGELIST", but the "SendMessage" function always fails (returns '0'). However, "GetLastError()" doesn't give me an error either. Assigning a single image to the button works, but with an image list the button stays blank. What am I doing wrong here?

main.cpp

#include <iostream>
#include <windows.h>
#include <commctrl.h>
#include "resource.h"

HIMAGELIST ImageList;

LRESULT CALLBACK WindowProcedure ( HWND WindowHandle, UINT message, WPARAM wParam, LPARAM lParam )
{
    switch ( message )
    {
    case WM_CREATE:
        {
            HWND ButtonOne = CreateWindowEx( 0, "BUTTON", "", BS_PUSHBUTTON | BS_BITMAP | WS_CHILD | WS_VISIBLE, 40, 10, 50, 50, WindowHandle, (HMENU)( IDC_BUTTON_ONE ), GetModuleHandle( nullptr ), nullptr );

            HANDLE Image = LoadImage( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDB_BITMAP_SQUARE ), IMAGE_BITMAP, 50, 50, LR_DEFAULTCOLOR );
            ImageList = ImageList_Create( 50, 50, ILC_COLORDDB, 1, 0 );
            ImageList_Add( ImageList, (HBITMAP)Image, nullptr );

            RECT rect = { 0, 0, 50, 50 };
            BUTTON_IMAGELIST *ButtonImageList = new BUTTON_IMAGELIST;
            ButtonImageList->himl = ImageList;
            ButtonImageList->margin = rect;
            ButtonImageList->uAlign = BUTTON_IMAGELIST_ALIGN_CENTER;
            std::cout << SendMessage( ButtonOne, BCM_SETIMAGELIST, (WPARAM)0, (LPARAM)( ButtonImageList ) ); //output = 0
        }
        break;
    case WM_DESTROY:
        ImageList_Destroy( ImageList );
        PostQuitMessage ( 0 );
        break;
    default:
        return DefWindowProc ( WindowHandle, message, wParam, lParam );
    }
    return 0;
}

const std::string WindowClassName = "CommonControlsTest";

int WINAPI WinMain ( HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow )
{
    INITCOMMONCONTROLSEX *ComCon;
    ComCon->dwSize = sizeof( INITCOMMONCONTROLSEX );
    ComCon->dwICC  = ICC_LISTVIEW_CLASSES;
    InitCommonControlsEx( ComCon );

    HWND WindowHandle;
    MSG Messages;
    WNDCLASSEX WindowClass;

    WindowClass.hInstance = hThisInstance;
    WindowClass.lpszClassName = WindowClassName.c_str();
    WindowClass.lpfnWndProc = WindowProcedure;
    WindowClass.style = CS_DBLCLKS;
    WindowClass.cbSize = sizeof ( WNDCLASSEX );
    WindowClass.hIcon = LoadIcon ( NULL, IDI_APPLICATION );
    WindowClass.hIconSm = LoadIcon ( NULL, IDI_APPLICATION );
    WindowClass.hCursor = LoadCursor ( NULL, IDC_ARROW );
    WindowClass.lpszMenuName = NULL;
    WindowClass.cbClsExtra = 0;
    WindowClass.cbWndExtra = 0;
    WindowClass.hbrBackground = reinterpret_cast<HBRUSH>( COLOR_BACKGROUND );

    if ( !RegisterClassEx ( &WindowClass ) )
        return 0;

    WindowHandle = CreateWindowEx ( 0, WindowClassName.c_str(), "Common Controls Test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 300, HWND_DESKTOP, NULL, hThisInstance, NULL );

    ShowWindow ( WindowHandle, nCmdShow );
    UpdateWindow( WindowHandle );

    while ( ( GetMessage ( &Messages, NULL, 0, 0 ) ) )
    {
        TranslateMessage( &Messages );
        DispatchMessage( &Messages );
    }
    return Messages.wParam;
}

resource.rc

#include "Resource.h"
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "CommonControlsTest.exe.manifest"
IDB_BITMAP_SQUARE       BITMAP      "Square.bmp"

resource.h

#define IDC_BUTTON_ONE          101
#define IDB_BITMAP_SQUARE       201

CommonControlsTest.exe.manifest

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
    version="1.0.0.0"
    processorArchitecture="*"
    name="CompanyName.ProductName.YourApplication"
    type="win32"
/>
<description>Your application description here.</description>
<dependency>
    <dependentAssembly>
        <assemblyIdentity
            type="win32"
            name="Microsoft.Windows.Common-Controls"
            version="6.0.0.0"
            processorArchitecture="*"
            publicKeyToken="6595b64144ccf1df"
            language="*"
        />
    </dependentAssembly>
</dependency>
</assembly>

Solution

  • Your resource.h file needs to define the CREATEPROCESS_MANIFEST_RESOURCE_ID and RT_MANIFEST constants as 1 and 24, respectively.

    resource.h

    #define CREATEPROCESS_MANIFEST_RESOURCE_ID  1
    #define RT_MANIFEST                         24
    #define IDC_BUTTON_ONE                      101
    #define IDB_BITMAP_SQUARE                   201
    

    Also, you do not need to allocate the BUTTON_IMAGELIST dynamically (you are leaking it):

    RECT rect = { 0, 0, 50, 50 };
    BUTTON_IMAGELIST Button ImageList {};
    ButtonImageList.himl = ImageList;
    ButtonImageList.margin = rect;
    ButtonImageList.uAlign = BUTTON_IMAGELIST_ALIGN_CENTER;
    std::cout << SendMessage( ButtonOne, BCM_SETIMAGELIST, 0, (LPARAM) &ButtonImageList );
    

    Same with INITCOMMONCONTROLSEX (which you are not even allocating):

    INITCOMMONCONTROLSEX ComCon {};
    ComCon.dwSize = sizeof( ComCon );
    ComCon.dwICC  = ICC_LISTVIEW_CLASSES;
    InitCommonControlsEx( &ComCon );
    

    When an API asks for a pointer, that doesn't always mean dynamic memory should be used.