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);
}