One day my Product Manager asked me to update the page header dialog that we display so our users can customize the header that gets printed on the top of each page. This was something many of our users complained about - they had to enter obscure codes to get the date format, and there was no easy way to check what they entered, except to print a page. I had been thinking about this for a while, and I had a pretty good idea of what I wanted to do. There was just one hitch: my Product Manager wanted to display all of the commonly used date/time formats in a list, and the user could choose from that list. This had the advantage that the user would not have to enter the individual format codes. The problem: it was a long list, and to be comprehensive, it had to include 11/21/01, 11-21-01, 11.21.01, and other day/month/year orders.
The first thing I did was to gather the complete list of date/time formats that I had ever seen or heard of anyone using. More than 100! The next thing I did was to go over the list with my Product Manager. We got it down to less than 60, but decided that the list should also include selections for individual date/time elements, like "day of month", "year with century", etc. This way, the user could choose one of the standard formats, or he could construct his own, using the date/time elements. Total number of list items: 72.
OK, at this point I knew I had a problem. There was no way to fit 72 items into any kind of reasonable dialog, unless I used a listbox of some kind. And I kept thinking that it would be nice to group together similar standard formats, to make it easier for the user. At some point I began to think of popup menus - easy to use, and they had separator lines for groupings. So I entered all the items into a table, and created a popup menu:
Unfortunately, I had this on the screen when my Product Manager came by. He looked at it, looked at me, then walked away. But I had already decided how to fix it. At this point, the table of menu IDs and format strings already contained -1 entries for the separator lines. Being actively lazy, I realized I could trivially extend this to accommodate sub-popup menus. At the same time, I thought of the web sites that encouraged me to keep shopping with handy "See more like this" buttons. Putting the two together, I used -2 entries in the table to signal the start of sub-popup menus. The idea was to show a few entries in each group, and then use a "More like this" popup menu to show the rest. Here is the result:
When the user hovers on Date/Time Elements
, he sees:
The next submenu shows date formats that begin with the day of the month:
Then the submenu that shows formats that begin with the month:
And finally the submenu that shows formats that begin with the year:
It is likely that you will want to make changes to this implementation to meet your own requirements. Most of the code that supports the menu button and the menu handler is in XPrintHeader.cpp
. Besides this code, you will need to insert
ON_COMMAND_RANGE(ID_INSERT_FIRST, ID_INSERT_LAST, OnInsert)
in the dialog's message map.
ON_COMMAND_RANGE causes all the menu selections to be routed to the
OnInsert() function, where the two edit fields are updated.
HeaderFormats is defined as an array of
UINT nID; TCHAR *pszFormat; TCHAR *pszDescription; } Format;
With this array, it is very easy to rearrange items and insert separators and submenus. As you saw, I first collected all the items in one menu, then added separators, and then added submenus at appropriate places.
When the user makes a menu selection, the contents of the
pszFormat member is appended to the writable edit field, and the read-only edit field shows an example. The read-only edit field is updated immediately if any changes are made to the format string. Non-format text is displayed as is.
demo shows how to use XPrintHeader.cpp
The technique presented in this article is applicable to a wide variety of UI requirements, where it is necessary to organize and present many different options. Through the use of "More like this" popup menus - with an example displayed on the menu - the user can quickly drill down to the selection he is looking for, without having to wade through a long list.
- Read menus from file - Since the popup menus are created dynamically, it is possible to read the menu table from a file or database. This would also allow end users to customize the order or contents of the menus.
- Adjust menus according to usage - This enhancement would shift the most-used selections to the top of the menu group, so that they would be displayed on the main menu, rather than a submenu. This would be very nice if also coupled with saving the new menu order.
Version 1.0 - 2003 July 30
This software is released into the public domain. You are free to use it in any way you like. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.