Click here to Skip to main content
15,884,388 members
Articles / Programming Languages / C
Tip/Trick

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

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
21 Feb 2021CPOL2 min read 3.4K   3  
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.

C++
/// <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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader Celonis SA
Germany Germany
I am currently the CEO of Symbioworld GmbH and as such responsible for personnel management, information security, data protection and certifications. Furthermore, as a senior programmer, I am responsible for the automatic layout engine, the simulation (Activity Based Costing), the automatic creation of Word/RTF reports and the data transformation in complex migration projects.

The main focus of my work as a programmer is the development of Microsoft Azure Services using C# and Visual Studio.

Privately, I am interested in C++ and Linux in addition to C#. I like the approach of open source software and like to support OSS with own contributions.

Comments and Discussions

 
-- There are no messages in this forum --