Difficulty setting menu item bitmaps from CImageList

Viewed 356

I have a menu which prompts in a certain situation. and i have a bitmap image which contains the icons i want to add to the menu.
first, i loaded the image list as following:

CImageList imageList;
imageList.Create(18, 16, ILC_COLOR24|ILC_MASK, 0, 0)
CBitmap bitmap;
bitmap.LoadBitmap(IDR_CL2_TAB_MENU_OPTIONS);
imageList.Add(&bitmap, RGB(192, 192, 192));

second, i tried to extract the bitmaps to add to the menu using the following

IMAGEINFO imgInfo;
ImgList.GetImageInfo( nBmpNo, &imgInfo );
pMenu->SetMenuItemBitmaps(iItem, MF_BYPOSITION, CBitmap::FromHandle( imgInfo.hbmImage ), NULL);

but unfortunately, it doesn't work so i tried the code written here https://www.codeproject.com/Articles/4673/Extracting-Single-Images-from-a-CImageList-object

but it only gives me black icons in the menu.
here is my complete function to show the menu:

void CMainFrame::ShowTabOptions(CPoint point)
{
  CMenu Menu, *pMenu = NULL;

  if (!Menu.LoadMenu (IDR_POPUP_TAB_OPTIONS))
    return;

  pMenu = Menu.GetSubMenu(0);
  if (NULL == pMenu)
    return;

  CImageList imageList;
  if(!imageList.Create(18, 16, ILC_COLOR24|ILC_MASK, 0, 0))
    return;

  CBitmap bitmap;
  bitmap.LoadBitmap(IDR_CL2_TAB_MENU_OPTIONS);
  imageList.Add(&bitmap, RGB(192, 192, 192));

  CArray<CBitmap*, CBitmap*> bitmapArray;
  CBitmap b1, b2, b3, b4, b5, b6, b7, b8, b9;
  bitmapArray.Add(&b1);
  bitmapArray.Add(&b2);
  bitmapArray.Add(&b3);
  bitmapArray.Add(&b4);
  bitmapArray.Add(&b5);
  bitmapArray.Add(&b6);
  bitmapArray.Add(&b7);
  bitmapArray.Add(&b8);
  bitmapArray.Add(&b9);

  for (int iItem = 0; iItem < 9; iItem++)
  {
    imageList.Copy( 0, iItem, ILCF_SWAP );

    IMAGEINFO imageInfo;
    imageList.GetImageInfo(0,&imageInfo);
    CDC dc; 
    dc.CreateCompatibleDC (GetWindowDC()); 
    CRect rect (imageInfo.rcImage);

    bitmapArray.GetAt(iItem)->CreateCompatibleBitmap (this->GetWindowDC(), rect.Width (), rect.Height ());

    CBitmap* pOldBmp = dc.SelectObject (bitmapArray.GetAt(iItem));
    imageList.DrawIndirect (&dc, 0, CPoint (0, 0), CSize (rect.Width (), rect.Height ()), CPoint (0, 0)/*, ILD_NORMAL, SRCCOPY, RGB(255, 255, 255)*/);
    dc.SelectObject (pOldBmp);

    pMenu->SetMenuItemBitmaps(iItem, MF_BYPOSITION, bitmapArray.GetAt(iItem), NULL);
  }


  pMenu->TrackPopupMenu (TPM_LEFTALIGN | TPM_VCENTERALIGN | TPM_LEFTBUTTON, point.x, point.y, this, NULL);
  Menu.DestroyMenu ();

  for (int iIndex = 0; iIndex < bitmapArray.GetCount(); iIndex ++)
  {
    bitmapArray.GetAt(iIndex)->DeleteObject();
  }
}

Could anyone tell me what am i missing?
Thanks in advance

1 Answers

So, this is how I actually do it:

Step 1

I have a variable define din my class:

MenuBitmapsMap m_mapMenuBitmap;

This is in the header:

using MenuBitmapsMap = map<UINT, CBitmap>;

Step 2

I have this method in my class:

void CCreateReportDlg::AddMenuBitmaps()
{
    CMenu*pMenu = GetMenu();

    if (pMenu == nullptr)
        return;

    m_mapMenuBitmap.clear(); 

    // File menu
    UpdateMenuBitmap(pMenu, ID_FILE_SAVE, IDB_BMP_MENU_SAVE);
    UpdateMenuBitmap(pMenu, ID_FILE_SAVEASFILE, IDB_BMP_MENU_SAVE_AS);
    UpdateMenuBitmap(pMenu, ID_FILE_PRINT, IDB_BMP_MENU_PRINT);
    UpdateMenuBitmap(pMenu, ID_FILE_IMPORT_FROM_OCLM_ASSIGNMENT_HISTORY, IDB_BMP_MENU_IMPORT);
    UpdateMenuBitmap(pMenu, ID_FILE_EXPORT, IDB_BMP_MENU_EXPORT);
    UpdateMenuBitmap(pMenu, ID_FILE_EXPORTSETTINGS, IDB_BMP_MENU_SETTINGS);
    UpdateMenuBitmap(pMenu, ID_FILE_PRINT_PREVIEW, IDB_BMP_MENU_PRINT_PREVIEW);
    UpdateMenuBitmap(pMenu, ID_FILE_AVAILABILITY_REPORT, IDB_BMP_MENU_REPORT);

    // Auto menu
    UpdateMenuBitmap(pMenu, ID_AUTOASSIGN_AUTO_ASSIGN, IDB_BMP_MENU_AUTO_ASSIGN);
    UpdateMenuBitmap(pMenu, ID_AUTOASSIGN_SELECTED_COLUMN, IDB_BMP_MENU_COLUMNS);
    UpdateMenuBitmap(pMenu, ID_AUTOASSIGN_SETTINGS, IDB_BMP_MENU_SETTINGS);
    UpdateMenuBitmap(pMenu, ID_AUTOASSIGN_CLEAR_ASSIGNMENTS, IDB_BMP_MENU_CLEAR);
    UpdateMenuBitmap(pMenu, ID_AUTOASSIGN_SELECTED_COLUMN, IDB_BMP_MENU_COLUMNS);
    UpdateMenuBitmap(pMenu, ID_AUTOASSIGN_EXCLUSIONS, IDB_BMP_MENU_EXCLUSIONS);
    UpdateMenuBitmap(pMenu, ID_AUTOASSIGN_SELECTED_COLUMN, IDB_BMP_MENU_COLUMNS);
    UpdateMenuBitmap(pMenu, ID_AUTOASSIGN_SHOW_CONFLICTS, IDB_BMP_MENU_HIGHLIGHT);

    // Help menu
    UpdateMenuBitmap(pMenu, ID_HELP_HELP, IDB_BMP_MENU_HELP);

}

The above method is called in OnInitDialog.

Step 3

This is the definition for UpdatemenuBitmap:

void CCreateReportDlg::UpdateMenuBitmap(CMenu *pMenu, UINT uCommandID, UINT uBMPResource,
    bool bByPosition /*false*/, bool bDisabled /*false*/)
{
    if (pMenu == nullptr)
        return;

    // We are working on the "actual" bitmap object held in the map
    CBitmap& bitmap = m_mapMenuBitmap[uCommandID];
    if (bitmap.GetSafeHandle() != nullptr)
        bitmap.DeleteObject();
    if (bitmap.LoadBitmap(uBMPResource) == NULL)
        return;

    theApp.SetBitmapBackgroundAsMenuColour(bitmap);
    if (bDisabled)
        theApp.SetBitmapAsGrayScale(bitmap);

    if (!pMenu->SetMenuItemBitmaps(uCommandID,
        bByPosition ? MF_BYPOSITION : MF_BYCOMMAND,
        &bitmap,
        &bitmap))
    {
        // #UpdateMenuBitmap Failed to set the menu item bitmaps
    }
}

I have not supplied the other two methods because they don't directly relate to your question I don't think. I can add then to the answer if required.

When you look at the documentation for SetMenuItemBitmaps it states:

When the menu is destroyed, these bitmaps are not destroyed; it is up to the application to destroy them.

In your code you are creating the CImageList inside the method. Add it as a member variable instead so that the object remains valid for when the menus are displayed. Then, destroy your image list in OnDestroy (might not be required for resource images).

Update

Here are the extra methods which I have in the app class:

void CMeetingScheduleAssistantApp::SetBitmapBackgroundAsMenuColour(HBITMAP hbmp)
{
    UpdateBitmapBackground(hbmp, true);
}


void CMeetingScheduleAssistantApp::SetBitmapAsGrayScale(HBITMAP hbmp)
{
    UpdateBitmapBackground(hbmp, false);
}

void CMeetingScheduleAssistantApp::UpdateBitmapBackground(HBITMAP hbmp, bool enabled, COLORREF crBackground /* GetSysColor(COLOR_MENU) */)
{
    if (!hbmp)
        return;
    HDC memdc = CreateCompatibleDC(nullptr);

    BITMAP bm;
    ::GetObject(hbmp, sizeof(bm), &bm);
    int w = bm.bmWidth;
    int h = bm.bmHeight;
    BITMAPINFO bi = { sizeof(BITMAPINFOHEADER), w, h, 1, 32, BI_RGB };

    std::vector<uint32_t> pixels(w * h);
    GetDIBits(memdc, hbmp, 0, h, &pixels[0], &bi, DIB_RGB_COLORS);

    //assume that the color at (0,0) is the background color
    uint32_t old_color = pixels[0];

    //this is the new background color
    uint32_t bk = crBackground;

    //swap RGB with BGR
    uint32_t new_color = RGB(GetBValue(bk), GetGValue(bk), GetRValue(bk));

    //define lambda functions to swap between BGR and RGB
    auto bgr_r = [](uint32_t color) { return GetBValue(color); };
    auto bgr_g = [](uint32_t color) { return GetGValue(color); };
    auto bgr_b = [](uint32_t color) { return GetRValue(color); };

    BYTE new_red = bgr_r(new_color);
    BYTE new_grn = bgr_g(new_color);
    BYTE new_blu = bgr_b(new_color);

    //change background and modify disabled bitmap
    for (auto &p : pixels)
    {
        if (p == old_color)
        {
            p = new_color;
        }
        else if (!enabled)
        {
            //blend color with background, similar to 50% alpha
            BYTE red = (bgr_r(p) + new_red) / 2;
            BYTE grn = (bgr_g(p) + new_grn) / 2;
            BYTE blu = (bgr_b(p) + new_blu) / 2;
            p = RGB(blu, grn, red); //<= BGR/RGB swap
        }
    }

    //fix corner edges
    for (int row = h - 2; row >= 1; row--)
    {
        for (int col = 1; col < w - 1; col++)
        {
            int i = row * w + col;
            if (pixels[i] != new_color)
            {
                //check the color of neighboring pixels:
                //if that pixel has background color,
                //then that pixel is the background 

                bool l = pixels[i - 1] == new_color; //left pixel is background
                bool r = pixels[i + 1] == new_color; //right  ...
                bool t = pixels[i - w] == new_color; //top    ...
                bool b = pixels[i + w] == new_color; //bottom ...

                                                     //we are on a corner pixel if:
                                                     //both left-pixel and top-pixel are background or
                                                     //both left-pixel and bottom-pixel are background or
                                                     //both right-pixel and bottom-pixel are background or
                                                     //both right-pixel and bottom-pixel are background
                if (l && t || l && b || r && t || r && b)
                {
                    //blend corner pixel with background
                    BYTE red = (bgr_r(pixels[i]) + new_red) / 2;
                    BYTE grn = (bgr_g(pixels[i]) + new_grn) / 2;
                    BYTE blu = (bgr_b(pixels[i]) + new_blu) / 2;
                    pixels[i] = RGB(blu, grn, red);//<= BGR/RGB swap
                }
            }
        }
    }

    SetDIBits(memdc, hbmp, 0, h, &pixels[0], &bi, DIB_RGB_COLORS);
    DeleteDC(memdc);
}
Related