Full-Featured 24-bit Color Toolbar
Environment: VC6.0 SP4, Win95/NT3.51 or later
Creating a CToolbar with 16 or 24 bit buttons is easy if you aren't worried about disabled buttons and your buttons don't have any pixels that should be set to the toolbar's background color. Things quickly get more complicated if you want to correctly handle those other cases. This sample program attaches 24 bit images to the standard MFC main frame toolbar and handles both disabled and hot buttons as well as transparent button backgrounds. It can easily be extended to handle 16 bit images or different toolbar configurations.
If your application can be run with the display set to less than 16 bits per pixel, you should add code to check the display depth and not load the high color toolbars in those cases, since they won't look good.
Specifying separate bitmaps for the enabled, disabled, and hot buttons isn't too hard--CToolbar::GetToolbarCtrl() gets the toolbar control, then CToolbarCtrl::SetImageList(), CToolbarCtrl::SetDisabledImageList(), and CToolbarCtrl::SetHotImageList() are used to assign the bitmaps.
Making the image lists 24 bits deep requires creating the CImageLists with no images, then loading the images as CBitmaps, and finally copying the CBitmaps into the CImageLists.
Loading the bitmaps as 24 bits per pixel even when the user's screen is set to a different bit depth requires extra work, too. CBitmap::LoadBitmap() converts the bitmap to the screen's bit depth, so the Win32 function ::LoadImage() is used instead to create a 24 bit DIBSECTION, which is then attached to a CBitmap so it can be passed to CImageList::Add().
When a toolbar is created in the Visual Studio toolbar editor, any pixel that is light gray (RGB (192, 192, 192)) is replaced with the user's chosen button color at runtime, making those pixels effectively transparent. For some reason this doesn't happen to 24 bit CImageLists. Specifying a mask color when the CImageList is created just ends up replacing that color with black, not with the system button color. This code does that color subsitution "manually." Before a bitmap is added to an image list, the code iterates over the pixels in the bitmap, replacing each RGB (192, 192, 192) pixel with the system button color (::GetSysColor (COLOR_BTNFACE)). Since the bitmaps are DIBSECTIONs, the code can access the pixels directly for maximum efficiency.
Since the Visual Studio graphics editor can't handle 24 bit images, you must edit the images in another program that can, such as GIMP or Adobe Photoshop. Save them as 24 bit .bmp files in the project's "res" folder. You can then import them into the project using the "import" command in the Developer Studio "Resource" tab.
// these constants represent the dimensions and number of buttons in // the default MFC-generated toolbar. If you need something different, // feel free to change them. For extra credit, you can load the // toolbar's existing image list at runtime and copy the parameters from // there. static const int kImageWidth (16); static const int kImageHeight (15); static const int kNumImages (8); static const UINT kToolBarBitDepth (ILC_COLOR24); // this color will be treated as transparent in the loaded bitmaps -- // in other words, any pixel of this color will be set at runtime to // the user's button color. The Visual Studio toolbar editor defaults // to 192, 192, 192 (light gray). static const RGBTRIPLE kBackgroundColor = {192, 192, 192}; int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) { TRACE0("Failed to create toolbar\n"); return -1; // fail to create } // attach the hicolor bitmaps to the toolbar AttachToolbarImages (IDB_HICOLOR_TOOLBAR, IDB_HICOLOR_TOOLBAR_DISABLED, IDB_HICOLOR_TOOLBAR_HOT); // the rest of your CMainFrame::OnCreate() code goes here } // find every pixel of the default background color in the specified // bitmap and set each one to the user's button color. static void ReplaceBackgroundColor (CBitmap& ioBM) { // figure out how many pixels there are in the bitmap BITMAP bmInfo; VERIFY (ioBM.GetBitmap (&bmInfo)); // add support for additional bit depths if you choose VERIFY (bmInfo.bmBitsPixel == 24); VERIFY (bmInfo.bmWidthBytes == (bmInfo.bmWidth * 3)); const UINT numPixels (bmInfo.bmHeight * bmInfo.bmWidth); // get a pointer to the pixels DIBSECTION ds; VERIFY (ioBM.GetObject (sizeof (DIBSECTION), &ds) == sizeof (DIBSECTION)); RGBTRIPLE* pixels = reinterpret_cast<RGBTRIPLE*>(ds.dsBm.bmBits); VERIFY (pixels != NULL); // get the user's preferred button color from the system const COLORREF buttonColor (::GetSysColor (COLOR_BTNFACE)); const RGBTRIPLE userBackgroundColor = { GetBValue (buttonColor), GetGValue (buttonColor), GetRValue (buttonColor)}; // search through the pixels, substituting the user's button // color for any pixel that has the magic background color for (UINT i = 0; i < numPixels; ++i) { if (pixels [i].rgbtBlue == kBackgroundColor.rgbtBlue && pixels [i].rgbtGreen == kBackgroundColor.rgbtGreen && pixels [i].rgbtRed == kBackgroundColor.rgbtRed) { pixels [i] = userBackgroundColor; } } } // create an image list for the specified BMP resource static void MakeToolbarImageList (UINT inBitmapID, CImageList& outImageList) { CBitmap bm; // if we use CBitmap::LoadBitmap() to load the bitmap, the colors // will be reduced to the bit depth of the main screen and we won't // be able to access the pixels directly. To avoid those problems, // we'll load the bitmap as a DIBSection instead and attach the // DIBSection to the CBitmap. VERIFY (bm.Attach (::LoadImage (::AfxFindResourceHandle( MAKEINTRESOURCE (inBitmapID), RT_BITMAP), MAKEINTRESOURCE (inBitmapID), IMAGE_BITMAP, 0, 0, (LR_DEFAULTSIZE | LR_CREATEDIBSECTION)))); // replace the specified color in the bitmap with the user's // button color ::ReplaceBackgroundColor (bm); // create a 24 bit image list with the same dimensions and number // of buttons as the toolbar VERIFY (outImageList.Create ( kImageWidth, kImageHeight, kToolBarBitDepth, kNumImages, 0)); // attach the bitmap to the image list VERIFY (outImageList.Add (&bm, RGB (0, 0, 0)) != -1); } // load the high color toolbar images and attach them to m_wndToolBar void CMainFrame::AttachToolbarImages (UINT inNormalImageID, UINT inDisabledImageID, UINT inHotImageID) { // make high-color image lists for each of the bitmaps ::MakeToolbarImageList (inNormalImageID, m_ToolbarImages); ::MakeToolbarImageList (inDisabledImageID, m_ToolbarImagesDisabled); ::MakeToolbarImageList (inHotImageID, m_ToolbarImagesHot); // get the toolbar control associated with the CToolbar object CToolBarCtrl& barCtrl = m_wndToolBar.GetToolBarCtrl(); // attach the image lists to the toolbar control barCtrl.SetImageList (&m_ToolbarImages); barCtrl.SetDisabledImageList (&m_ToolbarImagesDisabled); barCtrl.SetHotImageList (&m_ToolbarImagesHot); }

Comments
Getting the size of a Toolbar resource
Posted by Legacy on 02/19/2004 12:00amOriginally posted by: Matador
ReplyDoesn't work with other dimensions
Posted by Legacy on 05/11/2003 12:00amOriginally posted by: Vrej
It works with toolbar buttons that are 15 by 16, but not other sizes. Needs a quick fix.
ReplyWorks Great - except Win95
Posted by Legacy on 02/11/2003 12:00amOriginally posted by: Tom
I can't get this working in Win95 with IE 5.5. The toolbar comes up kinda black (hard to describe).
Anyone else?
(Great work by the way, I'm not complaining)
Reply
properly bacground color solution
Posted by Legacy on 07/30/2002 12:00amOriginally posted by: konan2
hi,everyone.
Win os versions(95, 98, 2k, xp) have different backgroud color each other. so you change properly bacground color whit system color.
You must not use RGB (192, 192, 192, Use ::GetSysColor (COLOR_3DFACE)funtion.
if so, each win os display properly bacground color
sorry: my email address have anti-spam strings.
erase "anti-", "-abc".
thank you.
ReplyHow to load bitmaps elsewhere than in 'res' folder?
Posted by Legacy on 07/22/2002 12:00amOriginally posted by: Jean-Nicolas
Hi,
First, I want to say that this is an excellent work!
In order to load toolbar images dynamically, I need to load bitmaps that are not in the "res" folder.
I'm wondering if you could help.. Thanks! :)
ReplyExcellent work!
Posted by Legacy on 04/28/2002 12:00amOriginally posted by: Martin Latiak
Pretty easy to use and does exactly what it should. Very nice piece of code... Thanks.
Martin
ReplyHow to set the background color properly...
Posted by Legacy on 08/03/2001 12:00amOriginally posted by: Sylvain Pearson
In the sample, the ReplaceBackgroundColor function does not work properly...
Here is how to fix the problem:
1- Comment out the call the ReplaceBackgroundColor function.
2- Create the image list with a mask
outImageList.Create(kImageWidth, kImageHeight, kToolBarBitDepth | ILC_MASK, kNumImages, 1);
3- Specify the transparency color when adding the bitmap:
outImageList.Add(&bm, RGB (192, 192, 192);
Thanks
Replyyou should check this one
Posted by Legacy on 07/08/2001 12:00amOriginally posted by: jpeter
I got a really good true color toolbar demo project from http://www.ncf.ca/~eo593/samples.htm
good luck
ReplyIt looks like it works, but...
Posted by Legacy on 03/30/2001 12:00amOriginally posted by: Per Hansen
If you change color of the background, for example if a properties dialog changes the background color, you will have to modify the background color in all the images in the image list for the toolbar.
ReplyDon't you think it would be smarter, if this was done in a background mask'er, who replaced a predefined background color with another (like ::GetSysColor(COLOR_BACKGROUND)).
Or better yet, define a one bitplane mask bitmap, like Windows does, to mask against our bitmap.