Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows Toolbar - Controlling button size and padding

I'm trying to understand the behaviour of a Windows toolbar - in particular how the following values interact:

  • the size of the bitmap image used
  • the effective size of a toolbar button
  • the padding between the image and the button edge
  • the height of the toolbar

Text displayed by a button is not relevant in my case.

What I actually want to do is provide an option for the user so he can choose from several toolbar button sizes (that will display bitmaps of say, 16x16, 32x32, or 48x48 pixels) and redisplay the toolbar accordingly after the option value changes. This is implemented by destroying the toolbar's image lists and rebuilding them with the appropriate bitmaps. The problem I currently have is that when switching from size 16 to 48 and back to size 16, the toolbar looks slightly different than before.

This is what the toolbar looks like when the application starts (correct):

Before switching the toolbar size

Once I switch to size 48 and back again, it looks like this (wrong):

After switching to a larger size and back

All buttons are higher than before, and each dropdown button has additional space around its bitmap and its dropdown arrow.

(For testing purposes, the toolbar has been made high enough to accomodate all button sizes without requiring an increase in height. This is to rule out the possibility that the change in button size stems from a possible toolbar resize, necessitated by temporarily switching to size 48.)

It looks as if additional padding were being rendered between a button bitmap and the button edge - as if rebuilding the toolbar with larger bitmaps/buttons caused Windows to internally increase the padding (which would make sense), but not decrease it when I subsequently rebuild the toolbar with the smaller bitmaps/buttons. However, sending TB_GETPADDING always returns 0x00060007, which indicates that the standard (correct) padding for 16x16 bitmaps is in place.

In an attempt to solve the problem by setting padding myself, I set the TBSTYLE_AUTOSIZE style on all non-separator buttons (this is required in order to apply padding). With this style, without even calling TB_SETPADDING, after switching to size 48 and back again, the toolbar looks like this:

After switching to a larger size and back, with TBSTYLE_AUTOSIZE

In this case, the button height is also wrong.

The question is: What is causing the buttons to be displayed differently after rebuilding the image lists?

Some aside notes:

  • When building the toolbar, I call TB_SETBITMAPSIZE, but neither TB_SETBUTTONSIZE nor TB_SETPADDING, because the bitmap size is all I have, and I assumed the button size would be derived correctly from that.
  • I'm aware I could simply build the entire toolbar window from scratch (not just the image lists), but would like to avoid that, so I can keep working with the same toolbar window handle.
  • I'm aware of the CCS_NORESIZE toolbar style (it's currently set) and the TB_AUTOSIZE message, but experiments with them have not led to any insights.
like image 225
Dabbler Avatar asked Sep 15 '14 19:09

Dabbler


2 Answers

I can't say what is the problem(there is no code in the question) but it is most probable that the solution of destroying the list of images causes this. You dont need to destroy the lists but to remove the buttons and then add new ones. The bellow code works fine:

Create ToolBar:

if((toolBarHwnd = CreateWindowEx(
                0,
                TOOLBARCLASSNAME,,
                NULL,
                WS_VISIBLE | WS_CHILD | TBSTYLE_WRAPABLE,
                0,
                0, //820,
                0,
                0,
                winHwnd, //main window
                (HMENU)IDC_TOOLBAR,
                hThisInstance,
                NULL
            )) == NULL){/*Error*/}

Create ImageList's for your images:

HIMAGELIST g_hImageListSmall = NULL, g_hImageListMedium = NULL, g_hImageListLarge = NULL;
int numButtons = 3
g_hImageListSmall = ImageList_Create(16, 16,   // Dimensions of individual bitmaps.
                                ILC_COLOR16 | ILC_MASK,   // Ensures transparent background.
                                numButtons, 0);
g_hImageListMedium = ImageList_Create(32, 32,
                                ILC_COLOR16 | ILC_MASK,
                                numButtons, 0);
g_hImageListLarge = ImageList_Create(48, 48,
                                ILC_COLOR16 | ILC_MASK,
                                numButtons, 0);

Add images to the lists:

HBITMAP hBitmapImageSmall = NULL, hBitmapImageMedium = NULL, hBitmapImageLarge = NULL;
hBitmapImageSmall = LoadImage(NULL, L"....YourBitmap.bmp", IMAGE_BITMAP, 16, 16, 0x10);
ImageList_Add(g_hImageListSmall , hBitmapImageSmall, NULL);
ImageList_Add(g_hImageListSmall , hBitmapImageSmall, NULL);
ImageList_Add(g_hImageListSmall , hBitmapImageSmall, NULL); //I am using the same image

hBitmapImageMedium = LoadImage(NULL, L"....YourBitmap.bmp", IMAGE_BITMAP, 32, 32, 0x10);
ImageList_Add(g_hImageListSmall , hBitmapImageMedium , NULL);
ImageList_Add(g_hImageListSmall , hBitmapImageMedium , NULL);
ImageList_Add(g_hImageListSmall , hBitmapImageMedium , NULL);

The same with the large one(48x48)

Add g_hImageListSmall to the ToolBar for start:

//Set the image list.
SendMessage(toolBarHwnd, TB_SETIMAGELIST, (WPARAM)0, (LPARAM)g_hImageListSmall);

// Initialize button info.
// IDM_NEW, IDM_OPEN, and IDM_SAVE are application-defined command constants.
TBBUTTON tbButtons[numButtons] = 
{
    { 0, IDM_NEW,  TBSTATE_ENABLED, BTNS_AUTOSIZE, {0}, 0, (INT_PTR)NULL },
    { 1, IDM_OPEN, TBSTATE_ENABLED, BTNS_AUTOSIZE, {0}, 0, (INT_PTR)NULL},
    { 2, IDM_SAVE, TBSTATE_ENABLED, BTNS_AUTOSIZE, {0}, 0, (INT_PTR)NULL}
};

// Add buttons.
SendMessage(toolBarHwnd, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);
SendMessage(toolBarHwnd, TB_ADDBUTTONS, (WPARAM)numButtons, (LPARAM)&tbButtons);

// Resize the toolbar
SendMessage(toolBarHwnd, TB_AUTOSIZE, 0, 0);

That is the first step.

Write two functions:

void RemoveButtons(void){
    int nCount, i;

    // Remove all of the existing buttons, starting with the last one.
    nCount = SendMessage(toolBarHwnd, TB_BUTTONCOUNT, 0, 0);

    for(i = nCount - 1; i >= 0; i--){ SendMessage(toolBarHwnd, TB_DELETEBUTTON, i, 0); }

    return;
}

enum{SMALL, MEDIUM, LARGE};

void AddButtons(int sizeButtons){
    if(sizeButtons == SMALL){
        SendMessage(toolBarHwnd, TB_SETIMAGELIST, (WPARAM)0, (LPARAM)g_hImageListSmall);
    }
    else if(sizeButtons == MEDIUM){
        SendMessage(toolBarHwnd, TB_SETIMAGELIST, (WPARAM)0, (LPARAM)g_hImageListMedium);
    }
    else{
        SendMessage(toolBarHwnd, TB_SETIMAGELIST, (WPARAM)0, (LPARAM)g_hImageListLarge);
    }

    // Add buttons.
    SendMessage(toolBarHwnd, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);
    SendMessage(toolBarHwnd, TB_ADDBUTTONS, (WPARAM)numButtons, (LPARAM)&tbButtons);

    // Resize the toolbar
    SendMessage(toolBarHwnd, TB_AUTOSIZE, 0, 0); 

    return;
}

When ever you want to change the size of the buttons in ToolBar:

RemoveButtons();
AddButtons(LARGE); //or SMALL, MEDIUM

References:

How to Create Toolbars
How to Customize Toolbars


The common controls have been a major bug factory in Windows. Microsoft has had a great deal of trouble keeping them compatible across 6 major Windows releases and 10 versions of comctl32.dll. Particularly the visual style renderers have been a problem spot.

Core issue is that the api for them was set in stone 18 years ago with no reasonable way to make it work differently from the way it worked in their first release. Their code acquired a great many deal of appcompat hacks to achieve this. Such an hack will for example doctor a value that was returned by the previous version so that the client program has no idea, and doesn't need to know, that it is working with a very different version from the one it was tested against.

This has side-effects, the kind you'll discover when you use the controls in an unusual way that's very different from the way they are normally used by meat-and-potatoes Windows programs. Exactly like your scenario. Very high odds that you are battling internal state of the toolbar that you cannot see and doesn't get properly restored when you switch sizes. Quite undebuggable, that internal state isn't visible at all. Other than from the undesirable side-effects.

The solution is the one you already know. Recreate the toolbar from scratch. It can't go wrong that way.

like image 4
Hans Passant Avatar answered Nov 02 '22 15:11

Hans Passant