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

Downloads

Download demo project - 37 Kb


Comments

  • Eureca!

    Posted by Oleg on 11/15/2012 07:30am

    Two days have been spent in the internet trying to find out the reason why do I see some kind of "holes" in my toolbar button icons. Finally, here is the first article that explains it quite completely. Thanks a lot! I never knew that bitmaps should be 24-bit coloured.

    Reply
  • Getting the size of a Toolbar resource

    Posted by Legacy on 02/19/2004 12:00am

    Originally posted by: Matador

    Getting the size of a Toolbar resource can be hepful to extend Peter's code with a CToolBar derived class:
    
    

    // This structure is in bartool.cpp (MFC source code)

    struct CToolBarData
    {
    WORD wVersion;
    WORD wWidth;
    WORD wHeight;
    WORD wItemCount;
    WORD *items() { return (WORD*)(this+1); }
    };

    // GetResourceInfo function gets the toolbar resource infos

    BOOL CYourToolBar::GetResourceInfo(LPCTSTR lpszResourceName)
    {
    // Loading the toolbar resource

    HINSTANCE hInst = ::AfxFindResourceHandle(lpszResourceName, RT_TOOLBAR);
    HRSRC hRsrc = ::FindResource(hInst, lpszResourceName, RT_TOOLBAR);
    HGLOBAL hGlobal = ::LoadResource(hInst, hRsrc);

    // Retrieving the toolbar resource data

    CToolBarData* pData = (CToolBarData*)LockResource(hGlobal);
    ASSERT(pData != NULL);
    ASSERT(pData->wVersion == 1);

    // Saving the size of toolbar buttons

    CYourToolBar::m_bnWidth = pData->wWidth; // button width
    CYourToolBar::m_bnHeight = pData->wHeight; // button height
    CYourToolBar::m_bnCount = 0; // button count

    // Encounting toolbar buttons

    for (int i = 0; i < pData->wItemCount; i++)
    if (pData->items()[i] != NULL) // NULL for SEPARATORS
    m_bnCount++;

    // freeing resources

    ::UnlockResource(hGlobal);
    ::FreeResource(hGlobal);

    return TRUE;
    }

    Reply
  • Doesn't work with other dimensions

    Posted by Legacy on 05/11/2003 12:00am

    Originally posted by: Vrej

    It works with toolbar buttons that are 15 by 16, but not other sizes. Needs a quick fix.

    Reply
  • Works Great - except Win95

    Posted by Legacy on 02/11/2003 12:00am

    Originally 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:00am

    Originally 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.

    Reply
  • How to load bitmaps elsewhere than in 'res' folder?

    Posted by Legacy on 07/22/2002 12:00am

    Originally 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! :)

    Reply
  • Excellent work!

    Posted by Legacy on 04/28/2002 12:00am

    Originally posted by: Martin Latiak

    Pretty easy to use and does exactly what it should. Very nice piece of code... Thanks.

    Martin

    Reply
  • How to set the background color properly...

    Posted by Legacy on 08/03/2001 12:00am

    Originally 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

    Reply
  • you should check this one

    Posted by Legacy on 07/08/2001 12:00am

    Originally posted by: jpeter

    I got a really good true color toolbar demo project from http://www.ncf.ca/~eo593/samples.htm

    good luck

    Reply
  • It looks like it works, but...

    Posted by Legacy on 03/30/2001 12:00am

    Originally 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.
    Don'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.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The exponential growth of data, along with virtualization, is bringing a disruptive level of complexity to your IT infrastructure. Having multiple point solutions for data protection is not the answer, as it adds to the chaos and impedes on your ability to deliver consistent SLAs. Read this white paper to learn how a more holistic view of the infrastructure can help you to unify the data protection schemas by properly evaluating your business needs in order to gain a thorough understanding of the applications …

  • The impact of a data loss event can be significant. Real-time data is essential to remaining competitive. Many companies can no longer afford to rely on a truck arriving each day to take backup tapes offsite. For most companies, a cloud backup and recovery solution will eliminate, or significantly reduce, IT resources related to the mundane task of backup and allow your resources to be redeployed to more strategic projects. The cloud - can now be comfortable for you – with 100% recovery from anywhere all …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds