Introduction

I guess I know what you're thinking-"Oh no!! Not another toolbar article!". Rest assured that this article discusses something that's most probably not found in any of those handy (dog-eared) MFC/SDK books you've got. It's about a very cool toolbar feature called chevrons. I hope you'll enjoy this, and incorporate the feature into your own code. Adding Chevrons to your code gets your app yet another step towards that elusive professional standard most commercial programs exude.

Chevrons

Version 5.80 of the common control library added support for a cool feature to the rebar control called chevrons. When the length of a band is lesser than the size of it's child control, the rebar displays a chevron to indicate that there's more to show. This feature is particularly useful when there are more bands in the rebar and the user requires more working space in the application. In addition, the user can intuitively see that there are more items that are hidden. This article shows you how to incorporate chevrons and how to handle the same.

To see chevrons at work, fire up IE and resize the window so the width of IE rebar is shrunk to the point it clips some of its items. Now you should see the the chevron(>>), to indicate that some items are not completely visible.

Chevrons can also been seen on the Task bar when you have the quick launch deskband option enabled (Win 98). When the chevron is clicked on, a floating toolbar that has the buttons/options that are not shown in the band appears.

Programming Chevrons

The folks who gave you the common controls have already done much of the work for you. As you might have guessed, there is a flag to activate the chevron style, as well as a handler that's called when the chevron is clicked. Here's how to get it all up and running.

When adding a band to the rebar control, the flag RBBS_USECHEVRON has to be OR'ed in with the style:

Note that each band that might display a chevron can have its style modified with the SetBandInfo() call.

The flag RBBIM_IDEALSIZE has to be set so that the ideal size for the chevron can be specified. This flag indicates that the cxIdeal member holds the ideal size of the band. When the band size is reduced below this value, a chevron is shown. You can experiment with this by changing:

rbbi.cx = rbbi.cxIdeal = sizeBar.cx;

to

rbbi.cx = sizeBar.cx;
rbbi.cxIdeal = sizeBar.cx/2;

You will find that the chevrons are shown only when the size of the band is half the length of the toolbar.

RBBIM_CHILDSIZE specifies the child window size information of the band. For the chevron to appear, at least both the above flags must be specified.

The RBBIM_SIZE flag has been set so that the bands are initially displayed with this size. If you don't use this flag, then the bands won't stay right next to each other-provoking complaints from users!

RBBIM_ID is a very useful and important flag that must be set. Of course, the member wID should also be set to a unique value, so that you can identify the band when required. This is because when users drag the band around, the index of the band keeps changing depending on the current position. But the band id stays with the band no matter where it goes.

NOTE: These two steps are just for convenience, it may be possible to set it all in one go.

Well, at this point your app should display the chevron when required. However, we still aren't done yet; we need to take care of what happens when the user interacts by clicking it. We have to write the handler code. Really, what you do in the event is most likely up to you, but here, we shall go with the most natural behavior!

Assuming CChevBar is derived from CRebar, add a message map for the chevron push event:

void CChevBar::OnChevronPushed( NMHDR * pNotifyStruct, LRESULT* result )
{
NMREBARCHEVRON* pChev = (NMREBARCHEVRON*) pNotifyStruct;
// Has the band id of the chevron that generated this message
int iBand = pChev->uBand;
// Do what ever is needed when the chevron is pushed,
// usually a popup showing the hidden button will be
// shown, Rebar doesn't do it, it is our JOB !
// Well what are we there for ? :-)
}

The important value that is needed is the uBand member of the structure NMREBARCHEVRON. This identifies the band whose chevron was clicked. Once we have the band id, we can get all the information about the band. Usually, we would need the child window handle.

Chevrons are usually placed when the toolbar is the child window of the band, but of course, this need not always be the case.

What do we do when a chevron is pushed? 99% of the time, we'll be dealing with a toolbar as the child window for the band. So let's stick with this at the moment. Okay, now let's say we wanted to display a popup menu for the items that are hidden when the Chevron is clicked. But how would we find the hidden buttons in the toolbar?

Here's what to do to get those hidden buttons:

Obtain the band rectangle (subtract chevron width if you want to); let it be R1

Get the toolbar button rectangle, let it be R2

Check if the intersecting rectangle of R1 and R2 is same as the R2.

If they intersect correctly then the button is shown

Of course, this algorithm has to be changed when the child window of the band is not a toolbar.

The simple example application provided along with this article adds the hidden items to an owner drawn popup menu and it displays the popup menu just below the chevron. In the popup menu's drawitem method, the bitmap of the button is extracted from the toolbar's image list and then drawn. The text is extracted from the tip text part of the string resource, which has the same id of the menu item.

Conclusion

Phew! Hopefully by now, you've got a pretty good feel of what chevrons are and how they're implemented. I hope this article has whetted your appetite to find out more and dig in the goodies available in Windows! Happy programming!

Known Problems

As is always the case, there're some issues that haven't been resolved at the moment. If you find out ways of circumventing these problems or have any ideas, I'd appreciate it if you let me know-or better still, post your own article for others to see!

Toolbar buttons with the drop down style are not handled properly (you can check this with IE, IE does it very nicely). Usually, when the dropdown style toolbar button is clicked another popup menu will be displayed with more options. Since we are using owner drawn menus and there is no way to get a new menu, our application stumbles here L My way of solving this would be to write a custom menu that has buttons. This would send a similar notification to the parent like the toolbar, when its button is clicked.

Another problem lies in writing reusable code. Rebar is a control, toolbar is another control. The rebar may not have a toolbar as its child in the bands. If so, then how do we write reusable code/class that works for all sorts of different child controls in the rebar?? Go figure.

I am sure most of you must've worked with BandObjects. When this is the case, how are we notified of the chevron click, and how do we handle it?

The initial size setting for the band works fine when the toolbar doesn't have any buttons with the style flag TBSTYLE_DROPDOWN. But when a toolbar has one, the GetMaxSize() function seems to return a lesser value. Is there any other way to set the initial size?

Programming Toolbar Chevrons - An Update

This update serves to rectify a few problems arising in the original "Programming Toolbar Chevrons" article. In essence, it's an update on the way the chevron is handled. Primarily, this is as close as you can get to simulate Internet Explorer 5.0's chevron handling.

The original article describes the technique of using a popup-menu to display the hidden buttons in a tool bar. As a result, the dropdown buttons are not handled correctly, as there is no way to know the action of a button down event.

The idea now is to show a popup window that has a toolbar containing the hidden buttons. This ensures that the new toolbar behaves just as the original one.

Let's see how to use project files and save the explanation for later.

Create an SDI or MDI project with the Rebars enabled

Copy the four files ChevBar.cpp, ChevBar.h, ChevDrop.cpp, ChevDrop.h and include it in the project.

Change the default CReBar class in the Mainframe(or anywhere relevant) to CChevBar

In the code that adds the child bars to the Rebars, modify the AddBar() call as shown below:

if ( !m_wndReBar.Create(this) ||
// 0 is the Band id, it has to be unique
!m_wndReBar.AddBar(&m_wndToolBar, 0 ) ||
// 1 is band id it has to be unique
!m_wndReBar.AddBar(&m_wndToolBar1, 1 ) ||
!m_wndReBar.AddBar(&m_wndDialogBar) )
{
...
}

Compile and execute the application. Your application should have the chevrons handled correctly now, just like the way Internet explorer 5.0 does.

The best thing to see chevron at work is the buttons with TBSTYLE_DROPDOWN set. For a sample, applications like word, Outlook express can be checked. When the main window is resized smaller than the toolbar width, the chevron appears, when the chevron is clicked, the toolbar buttons that are with "dropdown" style, also has "dropdown" style in the popup window. A mouse click action on this "popup" toolbar simulate the mouse click in the real toolbar button. This is very convenient when a high "working screen" space is required.

The sample application with this article displays a popup in response to the chevron click.

Lets see how it all goes...

Its code time

The idea is simple:

Create a popup window

Create a toolbar with the popup as the parent and add the hidden buttons to it

Show the popup window just below chevrons

Handle messages in the pop-up window sent by the toolbar. Redirect them if necessary

However, as usual, the implementation of the idea gets rather challenging:

The first thing to see is the CChevBar::OnChevronPushed() member. This is called by the framework when a chevron button is clicked. In response to it we have to check if the child window is really a toolbar (at this point, we don't know how to handle bands with other controls in it). If it is a toolbar a new CChevDrop is created-it is this CChevDrop object that drives the rest of the program.

CChevDrop::ShowPopup() is the next function that is called which:

Creates the popup window

Creates the toolbar inside this popup window

Copies the image list from the real toolbar to the one in popup

Inserts the hidden buttons from the real toolbar to the one in popup

And that's it! Your chevron popup is now displayed. But hang on to your hats - it won't start working immediately. This is because the toolbar sends all the messages to the parent, which happens to be the popup window. Hmmm… what if we tell the toolbar to send the notifications to the frame window itself ?? Yes, that works fine, but the popup won't go away once you click the toolbar button. Therefore, you've got to handle the notifications in the popup and send only the required ones to the parent frame. All these are tabulated as follows, for your convenience:

Condition

Action required

A button in the toolbar was clicked

The toolbar sends a command message to the parent.

This means an action is going to be taken and our popup window with the toolbar has to be closed. So pass the command message to the parent frame and close/destroy our popup window.

The dropdown arrow was clicked for the button with TBSTYLE_DROPDOWN

The toolbar sends a notification message to the parent.We have to redirect the notification to the parent frame to let it handle. Typically the parent frame handler shows another popup menu full of options. We should not close the popup

A few problems (uh oh, here we go again):

For the chevrons to show up correctly, the ideal size of the band has to be set. But it was difficult to find the size of the toolbar properly when the toolbar has buttons with the TBSTYLE_DROPDOWN style enabled.

CToolBarCtrl::GetMaxSize() returns the correct size of the toolbar when the toolbar has NO TBSTYLE_DROPDOWN buttons. But it always seems to return something smaller than the actual size when the buttons have TBSTYLE_DROPDOWN style enabled. It looks like the width of TBSTYLE_DROPDOWN style buttons are not taken into account.

Another attempt of mine was to obtain the item rectangle for the first and last buttons. From this, the size of the toolbar can be calculated. But this too doesn't seem to work. The returned size was less than the actual size.

Yet another try was to use CToolBarCtrl::SetRows(CtoolBarCtrl::GetRows(), FALSE, &rectTool), but it's a sad thing that it didn't work too.

If anybody gets around the way to find the size of the toolbar, please comment on it or please refer me to it. I'd very much appreciate it.

Usually the menu scrolls ( "Animates" ) to display its contents, but this functionality has not been implemented at the moment. This is left to the reader as an exercise (wicked grin).

Usually when a popup menu is shown, the main window(or parent) is not deactivated... But in this case it is deactivated. A problem might occur when the parent frame does not expect this to happen...

A few final words:

The code has been commented with the drawbacks and workarounds. If you think there's a better way to reach the same goal, please do post it. It would helpful for all.

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.

//The following code is used for adding string for a button on the m_tb.
//Because m_tb is CToolBarCtrl type but not CToolBar type, so I use "SetButtonInfo" function to set the string
//according to MSDN document.

I am having 5 tool bars, when i launch my application only first toolbar is fully visible others are at extreme right end and only drop down arrow's are visible ,and big empty space in between first and other toolbar. How can show all tool bars one after another?

I use 2 additional toolbars in my app. How to assign exactly location of additional toobars docking? By default it dock in a second row next to top of main tb. But I want to be placed it the next of main tb, in column. My code is here:

Hi,
Can a gripper be inserted into the toolbar to resize a toolbar item left side of the gripper like google toolbar gripper that resizes the search combobox. Please tell the solution if anyone know. Any programming language is welcomed and C# is great plus as required by me. Please make email the solution at rizwan_nuces@yahoo.com
Thanks
Rizwan

First, let me say this is a greate article and the code it's verry well written.

I've noticed that the IE chevron poped up menu it's not quite keyboard navigation friendly, it's not quite what you would expect from a regular menu which we all know it isn't.
For example if there's a sub menu you can't use left & right keys to go back and forwad.
There are limitations to this fake menu.
I've found that your idea of poping up a toolbar like ui insted of a menu like ui works much better because the user would not be confused.

Hi,
The code you have shared is awesome. I am having a slight problem. The Chevron button is parially displaying tsome buttons. But if we observe it in internet explorer then it does not display button partially when we resize the explorer. Either the buttons are displayed fully or not at all in the toolbar. So how can we acheive this kind of functionality.

Hi...I have a problem with subclassing the internet explorer main window. I would like to do this in order to catch RBN_CHEVRONPUSHED message. I have made a class derived from CWindowImpl in which I put NOTIFY_CODE_HANDLER(RBN_CHEVRONPUSHED, OnChevronPushed). On the SetSite method:
HWND hWnd;
m_spWebBrowser->get_HWND((long*)&hWnd);

Hello again....I solved the problem....I was subclassing the wrong window...Actually it is not the main internet explorer window who receives RBN_CHEVRONPUSHED but the parent window of the CReBar control which is a worker window. So...first find the CReBar window and the subclass it's parent. Hope this will others in need .

In IE or other APP, when the chevron was clicked twice consecutively without menu tracking, the popup menu/wnd should be closed. That is:
The first click activate the popup.
The second click deactivate the popup and all status come back to normal.

I try to do check in On..Pushed function but failed. How can it be solved.

When i click new mail. I have 5 buttons.
It have some events.
If 1 in 5 buttons to hide when i resize window again.
It put to chevron Office.
Then it lost events. I can't to draw chevron.
Can't you help me.

Hello,
I have an IE toolband with iconic dropdown buttons. The problem is that icons are not visible in the chevron menu , although they appear on dropdown buttons. I have subclassed IE rebar, so I use standard chevron menu ( I don't display mine ). Can anyone tell me what must I do, to show icons on chevron menu?

I'm programming a toolbar for IE. I'm naturally using a toolbar control as a child window.

My problem is simple, whatever I do, the chevron is NEVER shown.

I've played with all possible combinations of values returned by GetBandInfo.

Description of the CONDITION for the chevron to appear in MSDN is rather poor and uncertain (in my opinion). The only thing I've found there is that the containing rebar control must be created with a special flag, but I assume IE does it for me (other toolbars have chevrons). As far as I understand, from the toolbar perspective the only condition is that current size is smaller than the toolbar's ideal size( ideal size is returned upon pdbi->dwMask & DBIM_ACTUAL )Please correct me if I'm wrong.