Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to Set a New or Change / Exchange / Replace the Image of a Toolbar Button

0.00/5 (No votes)
21 Feb 2021 1  
My best practice approach to change the image of a toolbar button
In this tip, I present a solution for putting a new image on an existing toolbar button.

Introduction

I've searched the web, but I haven't found a satisfying recipe for putting a new image on an existing toolbar button - even the Microsoft documentation was of limited help.

All of you who are facing a similar problem, I would like to present my solution here.

Background

Attempt 1: There is the TB_CHANGEBITMAP message available, but before using it - the image list of the toolbar must be adjusted with ImageList_Add(). This may cause a realloc of the image list in case its capacity is exhausted and some sources say you may have to rebind the image list to the toolbar. I did not manage to get this to work, the TB_CHANGEBITMAP message always returned FALSE.

Attempt 2: To work around the TB_CHANGEBITMAP message, I tried the TB_GETBUTTONINFO and TB_SETBUTTONINFO messages. These messages return TRUE, but after the calls, the old image was gone and the toolbar button only showed the background color.

Attempt 3: Only after that, I had the idea to simply exchange the image in the image list of the toolbar, whereby the image index does not change, the image list size does not change and no changes have to be made to the toolbar button.

A first positive side effect of this approach is that the image list does not increase in size even when the image is swapped several times.

Furthermore, ImageList_Add() always creates its own copies of the color bitmap and mask bitmap for the image list. On the one hand, this keeps the responsibility for the original HBITMAP handles in the application and does not pass them to the windows manager, making it easier to keep the application code clean. On the other hand, it is no longer possible to find an earlier bitmap after the ImageList_Add() call with the help of the original HBITMAP handles (e.g., to swap the image back).

The second positive side effect of this approach is that it is not necessary to find previous bitmaps at all.

Using the Code

The following method swaps the bitmaps of a toolbar button in the toolbar's image list and deletes the bitmaps previously registered for the button.

/// <summary>Replaces the image of the indicated button. This replaces the image bitmap(s)
/// within the tool bar's image list.</summary>
/// <param name="uiCommandID">The ID of the tool bar button to replace the image for.</param>
/// <param name="hbmpDDBImage">The color bitmap of the new image.</param>
/// <param name="hbmpDDBMask">The mask bitmap of the new image or <c>NULL</c> if no mask is
/// required.</param>
/// <returns>The image index of the replaced image within the image list (<c>0</c> ... <c>n</c>)
/// on success, or <c>-3</c> if image index within the image list can't be investigated, or
/// <c>-2</c> if tool bar button has currently no image to replace, or <c>-1</c> if image
/// replacement fails.</returns>
int ReplaceButtonImage(UINT uiCommandID, HBITMAP /* weak */ hbmpDDBImage,
                                         HBITMAP /* weak */ hbmpDDBMask)
{
    TBBUTTONINFO tbi;
    tbi.cbSize = sizeof(TBBUTTONINFO);
    tbi.dwMask = TBIF_IMAGE | TBIF_COMMAND;

    if (::SendMessage((HWND)_hResObj, TB_GETBUTTONINFO, 
          (WPARAM)uiCommandID, (LPARAM)&tbi) < 0)
        return -3;

    int iImageCount = ::ImageList_GetImageCount(_hEnabledImageList);
    if (tbi.iImage < 0 || tbi.iImage >= iImageCount)
        return -2;

    if (hbmpDDBImage == NULL)
        return -2;

    IMAGEINFO ii;
    HBITMAP   hbmpDDBImageOld = NULL;
    HBITMAP   hbmpDDBMaskOld  = NULL;

    if (::ImageList_GetImageInfo(_hEnabledImageList, tbi.iImage, &ii) == TRUE)
    {
        hbmpDDBImageOld = ii.hbmImage;
        hbmpDDBMaskOld  = ii.hbmMask;
    }

    if (::ImageList_Replace(_hEnabledImageList, tbi.iImage, 
        hbmpDDBImage, hbmpDDBMask) == TRUE)
    {
        if (hbmpDDBImageOld != NULL)
            ::DeleteObject((HGDIOBJ)hbmpDDBImageOld);
        if (hbmpDDBMaskOld != NULL)
            ::DeleteObject((HGDIOBJ)hbmpDDBMaskOld);

        return tbi.iImage;
    }

    return -1;
}

Points of Interest

In the end, the simplest solution turned out to be the best solution.

History

  • 22nd February, 2021: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here