Click here to Skip to main content
15,888,984 members
Articles / Desktop Programming / MFC

Menu Bitmaps from Minimal Source Code

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
1 Feb 2002CPOL2 min read 235.5K   3.3K   53  
Add bitmaps to your menus easily and with very little source code.
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2002 by Warren Gardner (warren555@hotmail.com).
//
// This code is free for personal and commercial use, providing this 
// notice remains intact in the source files and all eventual changes are
// clearly marked with comments. You must obtain the author's consent before 
// you can include this code in a software library. If this source code is 
// used in a commercial application please send me an e-mail so I can check 
// out your product.
//
// No warrantee of any kind, express or implied, is included with this
// software; use at your own risk, responsibility for damages (if any) to
// anyone resulting from the use of this software rests entirely with the
// user.
//
// You can send me bug reports, bug fixes, enhancements, etc. or post them 
// to the codeproject: http://www.codeproject.com.
//
///////////////////////////////////////////////////////////////////////////////


#include "stdafx.h"
#include "winuser2.h"
#include "BitmapMenu.h"



//
// Constructor and destructor.
//

template <class T_FrameWnd>
BitmapMenu<T_FrameWnd>::BitmapMenu()
: T_FrameWnd()
, m_textSpace(3)
{
	ASSERT(m_toolBars.empty());
}

template <class T_FrameWnd>
BitmapMenu<T_FrameWnd>::~BitmapMenu()
{
	m_toolBars.clear();
}



//
// When a menu is about to popup, insert callbacks so that the menu items 
// can be sized correctly and the bitmaps can be drawn.
//

template <class T_FrameWnd>
void BitmapMenu<T_FrameWnd>::OnInitMenuPopup(CMenu *pPopupMenu, UINT nIndex, 
											 BOOL bSysMenu)
{
	// Call the base class function.
	T_FrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);

	// If there are no tool bars, then do not add callbacks to the menu.
	if (m_toolBars.empty()) return;

	// Callbacks will be generated for every menu item so that the 
	// menus can be sized correctly and the bitmaps can be drawn.
	MENUITEMINFO_WIN50 menuItemInfo;
	menuItemInfo.cbSize = sizeof(menuItemInfo);
	menuItemInfo.fMask  = MIIM_BITMAP;
	menuItemInfo.hbmpItem = HBMMENU_CALLBACK;
	for (UINT i=0; i<pPopupMenu->GetMenuItemCount(); ++i)
		SetMenuItemInfo(pPopupMenu->m_hMenu, i, TRUE, &menuItemInfo);

	// Draw images in the same space occupied by checkmarks.
	MENUINFO_WIN50 menuInfo;
	menuInfo.cbSize = sizeof(menuInfo);
	menuInfo.fMask = MIM_STYLE;
	GetMenuInfoWin50(pPopupMenu->m_hMenu, &menuInfo);
    menuInfo.dwStyle |= MNS_CHECKORBMP;
	SetMenuInfoWin50(pPopupMenu->m_hMenu, &menuInfo);

	return;
}



//
// Set the size of a menu item. The size must be large enough to accommodate 
// the tool bar bitmap.
//

template <class T_FrameWnd>
void BitmapMenu<T_FrameWnd>::OnMeasureItem(int nIDCtl, 
									LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
	// Call the base class function.
	if (lpMeasureItemStruct->CtlType != ODT_MENU)
	{
		T_FrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
		return;
	}

	ASSERT(!m_toolBars.empty());

	// Find the size of a tool bar button.
	IMAGEINFO imageInfo;
	CToolBar *toolBar = *(m_toolBars.begin());
	CImageList *imageList = toolBar->GetToolBarCtrl().GetImageList();
	imageList->GetImageInfo(0, &imageInfo);
	CSize size = CRect(imageInfo.rcImage).Size();
	
	// Add at least a one pixel border around the bitmap so there is room 
	// to draw a raised button. Also, put some extra horizontal space 
	// between the bitmap and the menu text.
	lpMeasureItemStruct->itemHeight = 
		__max(size.cy+2, lpMeasureItemStruct->itemHeight+3);
	double ratio = (size.cx+2) / (double) (size.cy+2);
	lpMeasureItemStruct->itemWidth = 
		ratio * lpMeasureItemStruct->itemHeight + m_textSpace;

	return;
}



//
// Draw a tool bar bitmap on the menu. The bitmap will be drawn gray if the
// corresponding button is currently disabled.
//

template <class T_FrameWnd>
void BitmapMenu<T_FrameWnd>::OnDrawItem(int nIDCtl, 
										LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	// Call the base class function.
	T_FrameWnd::OnDrawItem(nIDCtl, lpDrawItemStruct);

	// The remainder of this function pertains to menus.
	if (lpDrawItemStruct->CtlType != ODT_MENU) return;

	// Get information about the menu item to be drawn.
	bool isDisabled = ((lpDrawItemStruct->itemState & ODS_GRAYED) != 0);
	bool isSelected = ((lpDrawItemStruct->itemState & ODS_SELECTED) != 0);
	CDC* dc = CDC::FromHandle(lpDrawItemStruct->hDC);

	// Get the image that corresponds to this menu item.
	int imageID;
	CImageList *imageList;
	GetImage(lpDrawItemStruct->itemID, imageID, imageList);
	if (imageID == -1) return;

	// Get the size and position of the button and bitmap.
	IMAGEINFO imageInfo;
	imageList->GetImageInfo(imageID, &imageInfo);
	CSize bitmapSize = CRect(imageInfo.rcImage).Size();
	CSize buttonSize(lpDrawItemStruct->rcItem.right - m_textSpace - 2,
					 lpDrawItemStruct->rcItem.bottom);
	CPoint bitmapPosition((buttonSize.cx - bitmapSize.cx) / 2 + 1,
						  (buttonSize.cy - bitmapSize.cy) / 2 + 1);
	ASSERT(bitmapPosition.x >= 0 && bitmapPosition.y >= 0);

	// The item is disabled, so draw a monochrome bitmap.
	if (isDisabled)	
	{
		DrawDisabled(imageList, dc, imageID, bitmapPosition, bitmapSize);
		return;
	}

	// If the item is selected, draw a raised button.
	if (isSelected) DrawButton(imageList, dc, buttonSize);

	// Draw the bitmap on the menu.
	imageList->Draw(dc, imageID, bitmapPosition, ILD_TRANSPARENT);

	return;
}



//
// Given a menu ID, get the corresponding image. This routine returns
// an image list and an image ID which is the index of the image within 
// the image list. If there is no tool bar button corresponding to the
// given menu ID, then the returned image ID is -1.
//

template <class T_FrameWnd>
void BitmapMenu<T_FrameWnd>::GetImage(UINT menuID, int &imageID, 
									  CImageList *&imageList)
{
	// Find the tool bar and button that correspond to the menu ID.
	int buttonID = imageID = -1;
	std::set<CToolBar*>::iterator toolBar;
	for (toolBar=m_toolBars.begin(); toolBar!=m_toolBars.end(); ++toolBar)
	{
		buttonID = (*toolBar)->CommandToIndex(menuID);
		if (buttonID != -1) break;
	}
	if (buttonID == -1) return;

	// Find the corresponding image ID.
	UINT commandID, style;
	(*toolBar)->GetButtonInfo(buttonID, commandID, style, imageID);

	// Find the corresponding image list.
	imageList = (*toolBar)->GetToolBarCtrl().GetImageList();
	ASSERT(imageList->GetSafeHandle() != NULL);

	return;
}



//
// Draw a raised button on the menu. This is done to indicate that the menu
// item is currently selected.
//

template <class T_FrameWnd>
void BitmapMenu<T_FrameWnd>::DrawButton(CImageList *imageList, CDC *dc, 
										CSize &buttonSize)
{
	ASSERT(!m_toolBars.empty());

	// Clear the area where the button will be drawn.
	CBrush brush(GetSysColor(COLOR_MENU));
	CBrush *oldBrush = dc->SelectObject(&brush);
	BOOL result = dc->PatBlt(0, 0, buttonSize.cx+2, buttonSize.cy+1, PATCOPY);
	dc->SelectObject(oldBrush);

	// Draw the lighted side of the button.
	CPen lightPen(PS_SOLID, 1, GetSysColor(COLOR_BTNHIGHLIGHT));
	CPen *oldPen = dc->SelectObject(&lightPen);
	dc->MoveTo(1, buttonSize.cy);
	dc->LineTo(1, 0);
	dc->LineTo(buttonSize.cx, 0);
	dc->SelectObject(oldPen);

	// Draw the darkened side of the button.
	CPen darkPen(PS_SOLID, 1, GetSysColor(COLOR_BTNSHADOW));
	oldPen = dc->SelectObject(&darkPen);
	dc->LineTo(buttonSize.cx, buttonSize.cy);
	dc->LineTo(0, buttonSize.cy);
	dc->SelectObject(oldPen);

	return;
}



//
// Draw an image in a disabled state by converting a color image to a 
// monochrome image. Pixels in the color image that are part of the 
// background or have the same color as the button highlight color are
// mapped to the monochrome background. All other pixels are mapped to
// the monochrome foreground.
//

template <class T_FrameWnd>
void BitmapMenu<T_FrameWnd>::DrawDisabled(CImageList *imageList, CDC *dc, 
										  int imageID, CPoint &position, 
										  CSize &size)
{
	ASSERT(!m_toolBars.empty());

	// Create a color bitmap.
	CWindowDC windowDC(0);
	CDC colorDC;
	colorDC.CreateCompatibleDC(0);
	CBitmap colorBmp;
	colorBmp.CreateCompatibleBitmap(&windowDC, size.cx, size.cy);
	CBitmap *oldColorBmp = colorDC.SelectObject(&colorBmp);

	// Create a monochrome bitmap.
	CDC monoDC;
	monoDC.CreateCompatibleDC(0);
	CBitmap monoBmp;
	monoBmp.CreateCompatibleBitmap(&monoDC, size.cx, size.cy);
	CBitmap *oldMonoBmp = monoDC.SelectObject(&monoBmp);

	// Copy the toolbar button to the color bitmap, make all transparent 
	// areas the same color as the button highlight color.
	imageList->DrawIndirect(&colorDC, imageID, CPoint(0,0), 
							size, CPoint(0,0), ILD_NORMAL, 
							SRCCOPY, GetSysColor(COLOR_BTNHIGHLIGHT));

	// Copy the color bitmap into the monochrome bitmap. Pixels that 
	// have the button highlight color are mapped to the background.
	colorDC.SetBkColor(GetSysColor(COLOR_BTNHIGHLIGHT));
	monoDC.BitBlt(0, 0, size.cx, size.cy, &colorDC, 0, 0, SRCCOPY);

	// Draw the monochrome bitmap onto the menu.
	dc->BitBlt(position.x, position.y, size.cx, size.cy, 
			   &monoDC, 0, 0, SRCCOPY);

	// Delete the color DC and bitmap.
	colorDC.SelectObject(oldColorBmp);
	colorDC.DeleteDC();
	colorBmp.DeleteObject();

	// Delete the monochrome DC and bitmap.
	monoDC.SelectObject(oldMonoBmp);
	monoDC.DeleteDC();
	monoBmp.DeleteObject();

	return;
}



///////////////////////////////////////////////////////////////////////////////
//
// Generate code for the two most common frame windows through explicit 
// instantiation. 
//
///////////////////////////////////////////////////////////////////////////////

template BitmapMenu<CFrameWnd>;
template BitmapMenu<CMDIFrameWnd>;

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions