RFR: 8348760: RadioButton is not shown if JRadioButtonMenuItem is rendered with ImageIcon in WindowsLookAndFeel [v24]

Prasanta Sadhukhan psadhukhan at openjdk.org
Tue Apr 22 05:46:47 UTC 2025


On Sat, 29 Mar 2025 20:30:15 GMT, Phil Race <prr at openjdk.org> wrote:

> > ```
> >     mii.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID | MIIM_BITMAP | MIIM_CHECKMARKS;
> > ```
> 
> Per https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-menuiteminfoa
> 
> The MFT_BITMAP, MFT_SEPARATOR, and MFT_STRING values cannot be combined with one another. (nb MIIM is the replacement name for MFT but the same applies)
> 
> I suspect you mean to be setting HBITMAP hbmpChecked; and/or HBITMAP hbmpUnchecked;
> 
> not HBITMAP hbmpItem; which is used to display a bitmap instead of a text string label.
> 
> Also you aren't clearing the fMask / resetting it when using the entries without a bitmap I have no idea how much all of this can confuse the GDI API.

Here's the new program without setting  MFT_BITMAP  and MFT_STRING together and using hbmpChecked

#include <windows.h>
#include <string>

#define IDM_RADIO1 1001
#define IDM_RADIO2 1002
#define IDM_RADIO3 1003

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    static TCHAR szAppName[] = TEXT("RadioMenuDemo");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;

    RegisterClass(&wndclass);

    hwnd = CreateWindow(szAppName, TEXT("Radio Menu Demo"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        400, 300,
        NULL, NULL, hInstance, NULL);

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

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

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    static HMENU hMainMenu, hRadioSubMenu;

    switch (message) {
    case WM_CREATE: {
        // Create main menu
        hMainMenu = CreateMenu();
        hRadioSubMenu = CreatePopupMenu();

        // Configure menu items using MENUITEMINFO
        MENUITEMINFO mii = { 0 };
        mii.cbSize = sizeof(MENUITEMINFO);
        mii.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID | MIIM_CHECKMARKS; // | MIIM_BITMAP;
        

        // First radio item
        mii.fType = MFT_RADIOCHECK;
        std::wstring wstr1 = L"Radio Option 1";
        LPWSTR lp1 = (LPWSTR)wstr1.c_str();
        mii.dwTypeData = lp1;
        mii.cch = (UINT)strlen((const char *)lp1);
        mii.wID = IDM_RADIO1;
        mii.fState = MFS_CHECKED;

        HDC hdc = GetDC(hwnd);
        HBITMAP hBitmap = CreateCompatibleBitmap(hdc, 16, 16);
        HDC memDC = CreateCompatibleDC(hdc);
        //HGDIOBJ oldObj = SelectObject(memDC, hBitmap);
        HBRUSH hBrush = CreateSolidBrush(RGB(255, 0, 0));
        RECT rect = { 0, 0, 16, 16 };
        FillRect(memDC, &rect, hBrush);
        mii.hbmpChecked = hBitmap;
        //mii.hbmpItem = hBitmap;
        InsertMenuItem(hRadioSubMenu, 0, TRUE, &mii);

        // Second radio item
        std::wstring wstr2 = L"Radio Option 2";
        LPWSTR lp2 = (LPWSTR)wstr2.c_str();
        mii.dwTypeData = lp2;
        mii.cch = (UINT)strlen((const char *)lp2);
        mii.wID = IDM_RADIO2;
        mii.hbmpChecked = hBitmap;
        InsertMenuItem(hRadioSubMenu, 1, TRUE, &mii);

        // Third radio item
        std::wstring wstr3 = L"Radio Option 3";
        LPWSTR lp3 = (LPWSTR)wstr3.c_str();
        memset((void *) & mii, 0, sizeof(mii));
        mii.cbSize = sizeof(MENUITEMINFO);
        mii.fType = MFT_RADIOCHECK;
        mii.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID | MIIM_CHECKMARKS;
        mii.dwTypeData = lp3;
        mii.cch = (UINT)strlen((const char *)lp3);
        mii.wID = IDM_RADIO3;

        mii.hbmpChecked = NULL;
        //mii.hbmpItem = NULL;
        InsertMenuItem(hRadioSubMenu, 2, TRUE, &mii);

        // Add submenu to main menu
        AppendMenu(hMainMenu, MF_POPUP, (UINT_PTR)hRadioSubMenu, L"Options");
        SetMenu(hwnd, hMainMenu);

        // Set initial checked item using CheckMenuRadioItem
        CheckMenuRadioItem(hRadioSubMenu,
            IDM_RADIO1, IDM_RADIO3,  // Group range
            IDM_RADIO1,              // Initial selection
            MF_BYCOMMAND);           // Identifier type
        return 0;
    }

    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case IDM_RADIO1:
        case IDM_RADIO2:
        case IDM_RADIO3:
            // Update radio selection
            CheckMenuRadioItem(hRadioSubMenu,
                IDM_RADIO1, IDM_RADIO3,
                LOWORD(wParam), MF_BYCOMMAND);
            break;
        }
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}


It seems in windows 11 with this program, the imageicon cannot be clubbed with radiobullet/checkmark if BITMAP/STRING cannot be together so it will look like 

if RadioButton 1 is selected with icon (ie BITMAP)
![image](https://github.com/user-attachments/assets/a7da085e-80d5-4d1d-9e2e-0975b82894d2)

if RadioButton 3 is selected without icon (ie BITMAP set to NULL)
![image](https://github.com/user-attachments/assets/ea467c50-ac11-4f6b-a82d-305fdb6a9602)

We can make changes in JDK to only draw imageicon if menuitem is selected which will let user distinguish between selected/unselected menuitem

-------------

PR Comment: https://git.openjdk.org/jdk/pull/23324#issuecomment-2820134416


More information about the client-libs-dev mailing list