Copyright by msj. I
Original: http://www.microsoft.com/msj/0199/C/C0199.ASPX
QI read your columns in the August and October 1997 issues of MSJ describing how to implement coolbars in MFC and your article about cool menus in the January 1998 issue. How can I implement a menu in my coolbar, as is done in Visual C ®, OUTLOOK® AND THE Microsoft® Office Products? I don't know where to begin.
Many readers
I read your column, MSJ published in 1997, August and October, describing how to implement a coolbar with MFC, how can I achieve a coolbar with my Coolbar, like Visual C and Outlook, how do I start? ? A You do not know where to begin because it can not be done using any presupplied means. Probably you were hoping all you have to do is use the TBS_MENU flag in the CreateFooble function, and return an HMENU when you get an NM_PASSMETHEMENUPLEAZ notification ......................... ..
You don't know where to start because any old means is not at all. You probably want you to use the TBS_MENU logo in the createfooble function, return a HMenu handle when you receive NM_PassMethenupleaz notifications. It is sighful to have never been as good as Windows world life. Command column, such as what you know, there is a lot of programming in this line. This is why they have to occupy this full column.
The answer is this: you have to more or less reinvent menu bars from scratch But before I switch to geek mode, some words of caution I'll bet ten dollars the Microsofties will eventually make command bars available through some DLL or other,.. the same way they made common controls and the 3D look a standard part of Windows®. in fact, I'm certain of it. Why? Because command bars are already available in Windows CE. So if there's any way you can, wait. But if there is the answer to the late gUI lock-now! -Then Keep Reading. The answer is like this: You have more or less re-transform the menu bar. But before I want to turn to the strange way, pay attention to some words. I have to gamble $ 10, Microsoftiies to make the command control finally need to call a lot of DLL or other, in the same way, they will make the general controls and 3D interfaces a standard part of Windows. In fact, I am convinced of this. why? Because the command bar has been used on Windows CE. So the best way is to wait. But if you can't avoid the most popular GUI interface required by Mr. ACME CORP PRESIDENT, then look.
I'll show you how to make Even mr. acme happy.
I will let you know how to make ACME more happy.
Figure 1 Coolbar Bands
Just so everyone knows what I'm talking about, what are command bars, anyway? Command bars are toolbars that contain menus and can sit inside coolbars (also known as rebars) in apps like Microsoft Internet Explorer 4.0. Figure 1 shows a coolbar with a command bar, toolbar, and combo box as child windows (or bands). The main benefit of command bars is they do not have to span the entire width of the frame. Figure 2 shows the same app with all the bands on one line. With the high cost of pixel space-so many buttons competing for visibility-command bars are a win, which is why Windows CE already has them. (Not many pixels on those teeny palmtops.) Coolbars / rebars, command bars, and Cool Menus Are All Part of the New GUI Look.
Also everyone knows what I am talking about, what is the command column? The command column is a toolbar that includes a menu, which can be placed inside Coolbar (also called rebars), like IE4.0. Figure 1 shows a coolbar, with a command bar, toolbar, and combine bar like a child window (or rape). The biggest advantage of commandbar is that the command column does not need to occupy the width of the entire frame. Figure 2 shows that all columns are crowded on a line. For pixel spaces of inch gold, many buttons compete their faces, the command bar is a winner, why WINDOW CE is already using them (there is not much pixel space in the palm computer). Coolbars / rebars, and command columns, and Cool Menu is all of the new GUI samples. (Translator: Probably Rebar = Resizable Bar) Figure 2 One-line Bands
To implement command bars, I wrote an MFC class called CMenuBar. I should've called it CCommandBar, but I wrote the code before indoctrination to official barspeak (menu bar is more accurate, anyway). CMenuBar is trivial to use. Instantiate one in your coolbar class, create and load it, then add it to your coolbar like any other band. If you're using my CCoolBar class from the October issue, the place to do all this is in CCoolBar :: OnCreateBands.
In order to implement the command bar, I wrote a MFC class called CMenubar. I should call CCommandbar, but the code I wrote is before the red head file (no matter what, Menu Bar is more accurate). CMenuBar is very easy. First instantiate one in your Coolbar class, generate and load it, then add it to your Coolbar like other columns. If you are using my ccoolbar class, the one published in October, just do this in CCoolbar :: OnCreateBands.
Class CMYCOOLBAR: PUBLIC CCOOLBAR {
protected:
Cmenubar M_Wndmenubar;
Virtual bool oncreatebands ();
}
Bool CMYCOOLBAR :: OnCreateBands ()
{
CMenuBar & MB = m_wndmenubar;
Verify (Mb.create (this, / * args * /));
MB.LoadMenu (idR_mainframe);
CSIZE SZMIN = // Whatver you want
Return INSERTBAND (& MB, SZMIN, 0x7FF);
}
Visual C 6.0 has a new CReBar class for doing coolbars; if you want to use CReBar, you'll have to adjust accordingly Either way, there's one more thing you have to do: write a PreTranslateMessage function that gives your CMenuBar a chance to. There is a new Crebar class in translate messages.vc6.0 to do Coolbar; if you want to use the Crebar class, you still have to do a lot of adjustments: no matter what method, one or more things you have to do: write one The PretranslateMessage function gives your CMenubar class to convert information.
Bool CMAINFRAME :: PretranslateMessage (MSG * PMSG)
{
Return M_WndCoolBar.m_WndMenuBar.TranslateFrameMessage (PMSG)? True:
CframeWnd :: PretranslateMessage (PMSG);
}
. That's it Of course I designed CMenuBar to be easy That's my job But behind the simple interface lies some really gnarly MFC hacking In fact, there's too much code to explain it all here;... I can only give you the essentials and send You to the source code for details.
That's it. Of course, I design CMenubar is easy. That is my job. But behind the simple interface, there are many difficult MFC code, and there are too many code here to explain it, I can only provide you with some elements, and you should go to source code. Look for details.
But why all the code? What's the big deal with command bars? Ever since birth, Windows has conceived of menus as an attribute of top-level windows, not as windows in themselves. You specify the menu when you create the window or through a call to SetMenu. Windows does the rest. It draws the menu bar, handles mouse action, tracks the popups, handles keyboard mnemonics, and sends your app all kinds of warm fuzzy messages like WM_INITMENU so you can do stuff. But in Windows, only frame windows (not child windows) can have a menu, and a menu bar spans the entire width of the frame. For command bars, what you want is a little autonomous resizable window that's a menu. So what's a poor developer to do?
But where is the truth of all the code? What is the greatest processing of Command Bars? From the date of birth, Windows consider making the menu into a characteristic of a top window, and they are not like a window. You will specify the menu when generating a window or by calling SetMenu. Windows do the remaining part. It draws a menu bar, dealing with mouse behavior, tracking pop-up, handling keyboard information, sending a variety of messy messages to your app, such as WM_INITMENU, for you to do something. However, in Windows, there is a frame window instead of a child window to have a menu, and a menu bar across the entire frame. For the command bar, if you want is a map-changing menu window. So what should a poor developer do? Sadly, the only thing you can do is reinvent everything from scratch, inside a window. Well, not everything. You can call TrackPopupMenuEx to run popup menus, so all you really have to implement is the menu bar-the top-level row of menu items. There are many ways to do it-Visual C and the Office products each use their own special window classes-but the way Internet Explorer 4.0 does it is to start with a ToolbarWindow32 that has text (as opposed to iconic) buttons.
Unfortunately, in one window, the only thing you can do is to change some things from the beginning. Oh, not all things. You can call TRACKPOPUPMENUEX to run the pop-up menu, so you really have to implement a menu bar - the top of the menu item. There are many ways to -VC and Office products have their own special window - IE4.0 starting with a generation of ToolbarWindow32 (it seems to be against icon)
Figure 3 Text Buttons
Figure 3 shows the elements of a command bar implemented this way: the toolbar (menu bar), buttons (top-level menu items), and popup menus, all inside a coolbar You supply the glue to make everything work That is,.. the code that handles mouse and keyboard input to track the buttons and display the proper popup for top-level menu item each. It sounds easy but turns out to be the coding equivalent of mucking out the Augean stables. So let's roll up our sleeves and Start cleaning!
Figure 3 shows the element of this implementation of a command bar: Toolbar (menu bar), buttons, pop-up menus, these are in a Coolbar. You have to make them bond together. It means that the code to handle the input of the mouse and keyboard to track the button, and appropriately display the pop-up menu for each top menu item. It sounds easy, but if it turns into code but it is difficult, just like cleaning a very dirty place. Let us pull up the sleeves and start making big sweep! CMenuBar is derived from CFlatToolBar from my October 1997 column. The first interesting CMenuBar function is LoadMenu. That's the function you call to load the menu, remember? It comes in three overloaded flavors, but only one does any work (see Figure 4).
CMenubar derived self-CFLATTOOLBAR (October 1997). The first interesting cMenubar function is loadmenu. That is a function you call to load the menu, remember? It has three overloads, with only one job.
After deleting any old buttons, LoadMenu adds a button for each top-level menu item. The buttons have TBSTYLE_ AUTOSIZE set, which tells the toolbar to size the button based on its text. Perspicacious readers will have noticed I omitted the detail that shows how you actually set the button text. Toolbars have a curious way of doing this. you do not just set the text as an LPCTSTR. Instead, you create a string with all the text labels, zero-terminated with a double-zero at the End:
After deleting all buttons, loadMenu adds a button for each top menu. The button is set to the TBStyle_autosize style, telling the size of the Toolbar button to be based on the text. The readers of the Unite will notice that I ignore how to display the details of the text on the button. Toolbars do this in a weird manner. You don't set your text to the LPCTSTR type. Conversely, you generate a string, have all text tags, ending with two zero.
LPCTSTR mYStrings =
_T ("& File / 0 & Edit / 0 & View / 0");
Then you call AddStrings with this string, and for each button you specify the index of the text you want in TBBUTTON :: iString. Pretty weird, huh? Even weirder is the fact you can not delete strings, you can only add them! But I want to let programmers call LoadMenu repeatedly to load different menus, so CMenuBar has to go through some minor conniptions to combat the toolbar. CMenuBar keeps an array of all the strings it's ever added, so it will not add the same string again . Otherwise the toolbar might eventually (after a few centuries) run out of memory. Also omitted to spare you is some chicanery with NULLing the frame menu. When you call LoadMenu, CMenuBar automatically sets your frame's menu to NULL. That's because it doesn ' t make sense to have a frame menu if you're using command bars. But MFC gets very upset if you do pFrame-> SetMenu (NULL) while the frame is being created, so instead CMenuBar postpones the deed by posting a message to itself Then you use this string to call AddString as the parameter, and for each button, you are in tbutton: : The istring item specifies its index. Good guy, is it? What is wrong is that you can't delete strings, you can only increase! However, I want to let the programmer repeat call loadMenu to load different strings, so cmenubar has to make some hysterical battle with Toolbar. CMenubar saves a queue: it has increased all strings because I don't want to add the same string. Otherwise, Toolbar may eventually exceed memory (after approximately the age). In order to save you the entanglement of the empty frame menu, when you call LoadMenu, CMenubar automatically clears your framework menu. That's because if you use the menu bar frame menu, there is not much sense. But the MFC will become very bad if you do this when the frame is generated: p pframe-> setmenu (NULL), the cMenubar of the generation will post the send message to its own situation.
At this point what you have is a toolbar with a bunch of text buttons that do not do anything. You press a button and nothing happens. What you want to happen is have a popup menu appear. For example, if you click the File Button, You Expect to See a popmman @, save, print, and so on. no problem. Just add some code to do it. Now you have only one toolbar with a text button, what is also Do not do it. You press the next button to see a pop-up menu. For example, you click the File button, you want to see the pop-up menu with the following command: Open, Save, Print, and more. Small case, let you!
Void Cmenubar :: ONLBUTTONDOWN (Uint Nflags, Cpoint Pt)
{
INT IBUTTON = Hittest (PT);
IF (iButton> = 0 && ibutton TrackPopup (iButton); Else CFLATTOOLBAR :: ONLBUTTONDOWN (NFLAGS, PT); } CMenuBar :: HitTest returns the button hit, or -1 if none. It overrides TB_HITTEST, which has a bug in at least some versions of comctl32.dll. (If the button is clipped at the end of the toolbar, TB_HITTEST will return a Hit for That Button Even IF THE MOUSE TOTALLY OUTSIDE TOOLBAR WINDOW!) The Other Function, TrackPopup, Displays The Popup. CMenuBar :: Hittest returns a button hit, no life is returned - 1. It overloads TB_HitTest, which has problems in many versions of ComctL32.dll. Other functions TrackPopup show a pop-up menu. Void Cmenubar :: TrackPopup (int ibutton) { PressButton (iButton, True); HMENU HPOPUP = :: getSubmenu (m_hmenu, ibutton); TRACKPOPUPMENUEX (HPOPUP, / * LOT'S O 'ARGS * /); PressButton (iButton, False); } That is, push the button, run the popup, and unpush the button. All very simple. But when you run this code you run into brick wall number one. Your app starts fine, all the pretty buttons appear. You click File and TrackPopup . displays the popup with Open, Save, and so on But now try moving the mouse over a different menu bar item, like Edit What happens Anyone who's ever used Windows knows what should happen:.? the system should dismiss the File popup (Open , Save, Print) and Display the Edit Popup (CUT, COPY, PASTE). Does this in Fact Happen? NOOOO. Why not? Because CmenuBar is Still Waiting for TrackPopMenuex to return. Press the button to perform pop-up, and no longer press Button. Everything is very simple. But when you run this code, you hit the brick wall. Your program starts very well, all lovely buttons appeared. You click on File, TrackPopup Display the pop-up menu, with Open, Save, and so on. But now try moving the mouse to a different menu item, like Edit. what happened? Anyone who has used Windows knows what will happen: the system will eliminate the File pop-up (Open, Save, Print) and display Edit pop-up (CUT, COPY, PASTE). Is this actually happened? No never didn't. Why isn't it? Because CMenubar is still waiting for TRACKPOPUPMENUEX to return. When you call TrackPopupMenuEx, control disappears into a black hole-TrackPopupMenuEx's own little mini message loop-and does not come out again until the user selects an item or dismisses the menu by clicking outside it, pressing Escape, or kicking the disk hard enough . to dislodge its platters The user can move the mouse all he likes;. TrackPopupMenuEx will not return control So how are you supposed to track the popups when you do not have control If you could somehow detect when the mouse has moved over? A New Button, You Could Send WM_CANCELMODE TO CANCEL THE POPUP, THEN DISPLAY A DIFFERENT One. But How Can You Detect Where The Mouse Is When Your Code Doesn't Have Control? When you call TRACKPOPUPMENUEX, control disappear into a black hole -TrackPopupmenuex own small mini information loop - and no longer comes until the user chooses one or eliminating this menu by clicking on it, press Escape, or kick your hard drive It flew out of the shell. Users can move the mouse to all the places he like; TrackPopupMenuex has not returned control. So however, how do you have to track the pop-up menu, when you don't have control? Anyway, if you can notice when your mouse moves on a new button, you can send WM_CANCELMODE to cancel the pop-up menu and then display a different one. But how do you feel the mouse, when your code is not controlled? The Answer IS: Install A Windows Hook. Oh Dear, Not That. Windows Hooks Are To Most Program What a Cross Is To a Vampire. Any Time The Answer IS "Install A Windows Hook," you know you're in, as My I geologist friend calls it, deep schist. But it's not really that bad. A Windows hook is no more than a callback function you can install to have Windows call you when something interesting happens. There are several kinds of hooks, but the one that does the trick here is a WH_MSGFILTER hook. If you install a WH_MSGFILTER hook, Windows calls it whenever there's any kind of input event-mouse, keyboard, cortical array-destined for a dialog box, message box, scroll bar, or menu. So the Next REWRITE OF TRACKPOPUP GoES Something Like Figure 5. The answer is: Install a Windows Hook. Oh, not that. Windows Hooks is a watershed in most programmers to a vampire. Announcement is "Installing a Windows Hook," You know you, like my geological friend, calling it, Dao Olympic rock. But it is not so bad. A Windows hook is just a callback function you can install, and Windows will call you when some interesting things happen. There are several hooks that generate an expected effect here that is the WH_MSGFilter hook. If you have a wh_msgfiler hook, Windows calls it, no matter when, there is any input event - mouse, keyboard, Cortical Queue - Go to the dialog, information box, roller, or menu. Therefore, the next trackpopup's rewriting is like a matter of Figure 5. The idea is this. Before calling TrackPopupMenuEx, install a menu input hook. The hook watches mouse events. If the hook detects that the user has moved the mouse over a different toolbar button, it notes the button (m_iNewPopup) and cancels the popup. TrackPopupMenuEx returns, TrackPopup removes the hook and, seeing m_iNewPopup has been set, repeats the whole process, this time to display a different popup. If TrackPopupMenuEx returns for any other reason, m_iNewPopup will be -1 and TrackPopup quits. Pretty neat. Now, Here's The hook function. The idea is like this. Install a menu input hook before calling TRACKPOPUPMENUEX. Hook monitors mouse events. If the hook found that the user moved the mouse on different buttons, it wrote the button and cancel the pop-up menu. TRACKPOPUPMENUEX Returns, TrackPopup Remove Hook Next, m_INEWPopup is set, repeated the entire process, which shows a different pop-up. If TrackPopUpMenuex returns due to different reasons, m_INEWPOPUP will turn-1 and TRACKPOPUP exits. It's great, this is the hook function. // (Static Member FN) LResult Callback CMenuBar :: MenuInputFilter (int Code, WPARAM WP, LPARAM LP) { Return (Code == MSGF_MENU && G_PMenubar &&&& g_pmenubar-> onMenuInput ((msg &) lp)? True : CallNexthooKex (G_HMSGHOK, CODE, WP, LP)); } MSGF_MENU means this is a menu (as opposed to dialog or other) event, in which case the hook passes the buck to a virtual CMenuBar function, OnMenuInput. In order to figure out which CMenuBar object to call, I pass it to the hook function Through A Global, G_Pmenubar (Multithreaders BEWARE!). There's no Other Way to Pass It, Since Setwindowshook Has No Void * Cookie Argument. OnMenuInput Processes The Event Like So. MSGF_Menu means this is a menu (not a dialog or other) event, in which case hook pushes responsibility to a CMenuBar virtual member function, onMenuinput. In order to infer one of the CMenubar objects being called, I passed a global variable g_pmenubar by giving the hook function. There is no other way to pass it, because SETWINDOWSHOOK does not have a void * cookie parameter. OnMenuInput is like this to handle this thing. Bool Cmenubar :: OnMenuInput (MSG & M) { IF (m.Message == WM_MOUSEMOVE) { Cpoint pt = m.lparam; INT IBUTTON = Hittest (PT); IF (ibutton> = 0 && ibutton! = m_itracking) { // User Moved Mouse over Different Button m_INEWPOPUP = ibutton; // Remember IT Getowner () -> PostM_CanceLmode; } } Return False; // Don't Eat } } Return CSUBCLASSWND :: WindowProc (MSG, WP, LP); } When the user moves the mouse, OnMenuInput checks to see if the mouse is now over a different toolbar button. If so, it notes the new button in m_iNewPopup and posts WM_CANCELMODE to quit the current popup. TrackPopupMenuEx exits, and TrackPopup displays the new popup . When the user moves the mouse, OnMenuInput checks if the mouse is now on a different button. As such, it records the new button with m_inewpopup and sends WM_CANCELMODE to exit the current pop-up menu. TRACKPOPUPMENEX exits, TrackPopup displays new pop-up. There are more mouse details I will not sweat here (see the source code in Figure 6). The important thing is to understand how you can install a hook that spies on mouse messages to implement the basic popup-tracking behavior. This is the Essential Core of Cmenubar. There are more mouse details I have not discussed here (see the source code of Figure 6). Important things are to know how to install a hook tracking mouse, in order to perform a basic pop-up behavior. This is the essence of cmenubar. Mouse input down. Next, the keyboard. There are several issues here. First is getting the right and left arrow keys to track popups the same as the mouse. As you'd suspect, the code is almost identical. The same OnMenuInput handler works For Both. The mouse input is put down first. Next, keyboard. There are several versions here. The first is to process the right arrow and the left arrow key to track the pop-up, just like the mouse. You may doubt, the code is almost the same. The same ONMENUINPUT is working in both. Bool Cmenubar :: OnMenuInput (MSG & M) { IF (m.Message == WM_MOUSEMOVE) { // AS Before . . . } else if (m.Message == WM_KEYDOWN) { IF (/ * vk_left or vk_right * /) { INT iNewPopup = GetNextorPrevbutton (m_ipopuptracking, vkey == vk_left); IF (inewpopup! = m_itracking) { m_inewpopup = inewpopup; Getowner () -> PostM_CanceLmode; } } Return False; // Don't Eat } The difference is that instead of calling HitTest to determine what the new button / popup is, OnMenuInput calls another function, GetNextOrPrevButton. This function simply increments and decrements the button number, wrapping at either end. Different is that in order to call HitTest to decide whether it is a new button / pop-up, onMenuInput calls another function, GetNextorPrevbutton. This function is simple to increase or decrease the number of buttons, and package it at the end. As usual, reality is not quite as simple as I've painted. If the highlighted menu item has a submenu, pressing right-arrow should not display the next top-level popup, but should display the submenu. In other words, don ' t do anything, let TrackPopupMenuEx do its thing. Likewise, if the user presses left-arrow to back out of a submenu, you do not want to display the previous top-level popup. So you need some code to detect and ignore these special cases. How does OnMenuInput know what popup / item TrackPopupMenuEx is currently highlighting? It does not. But TrackPopupMenuEx sends a WM_MENUSELECT message whenever the user selects a new menu item. The problem is Windows sends the message to whichever window you specified as the owner when you called TrackPopupMenuEx. For all the MFC message map stuff to work CMenuBar specifies its owner-the containing frame-as the popup owner. So that's where Windows sends WM_ mENUSELECT. to intercept it, CMenuBar uses one of my standard programming tricks, C Subclasswnd. This Little Class Lets Any Object Intercept Messages Sent to A WINDOW. CMENUBAR Defines ITS OWN CSUBCLASSWND DERIVATE TO INTERCEPT WM_MENUSELECT MESSAGES SENT to the Frame. In general, the truth is not as simple as I write. If the highlighted menu item has a child, press the right arrow Should not display the next top menu, and the submenu should be displayed. Change the word, don't do anything, let TRACKPOPUPMENUEX do it. Similarly if the user presses the left arrow to exit the submenu, untouched the previous top menu to display the previous top menu. So you need a lot of code to handle these special situations. How does ONMENUInput know Popup and Item TRACKPOPUPMENUEX are current highlights? It doesn't know. But TRACKPOPUPMENUEX sends a WM_MENUSELECT message whether the user selects a new menu item. The problem is that Windows sends information to no matter which one you specify, when you call TrackPopUpMenuex. For all MFC message mappings, CMenubar specifies its owner-accommodated frame - as the owner of the pop-up menu. So Windows sent WM_MENUSELEC to go there. To intercept it in the middle, cmenubar uses a standard programming skill, csubclasswnd. This class allows any object to intercept messages sent to the window. CMenuBar defines its own CSUBClassWnd to intercept the WM_MENUSELECT message sent to the frame. LRESULT CMenuBarframehook :: WindowProc UINT MSG, WPARAM WP, LPARAM LP) { IF (MSG == WM_MENUSELEC) { m_pmenubar-> onmenuselect ( (HMENU) LP, (UINT) LOWORD (WP)); } Return CSUBCLASSWND :: WindowProc (MSG, WP, LP); } Following The Usual Paaraldigm, Cmenubarframehook Passs The Buck To a Cmenubar Virtual Function, Onmenuselect, Which Does The Real Work. Void Cmenubar :: OnMenuSelect (HMENU HMENU, UINT IITEM) { IF (M_ITRACKINGSTATE> 0) { m_bprocessrightarrow = (:: getSubmenu (HMENU, IIITEM) == NULL); m_bprocessLetarrow = HMENU == m_hmenutracking; } } OnMenuSelect sets up some flags to tell OnMenuInput when to process arrow keys CMenuBar will process VK_ RIGHT only if the current menu item does not have a submenu;. It processes VK_LEFT only if the current menu is the same one it originally passed to TrackPopupMenuEx ( that is, not a submenu). Of course, you have to modify CMenuBar :: TrackPopup to set m_hMenuTracking before calling TrackPopupMenuEx, and OnMenuInput to process the left / right keys only if m_bProcessLeft / RightArrow are set. Consider it done. OnMenuselect sets a lot of signs to inform OnMenuInput When to process the arrow keys. CMenubar only handles VK_Right if the current menu item has no submenu; it also processes VK_LEFT only if the current menu is only transmitted to TRACKPOPUPMENUEX (that is, there is no submenu). Of course, you have to modify CMenuBar :: TrackPopup to set M_HMenutracking before calling TrackPopUpMenuex, and onMenuInput only handles the Left / Right key if m_bprocessLEFT / RIGHTARROW is set. I think it is done. That solves the tracking problem for keyboard input, but there are other keyboard issues Once you start implementing keyboard support for command bars, you realize that there are actually three possible states your command bar can be in, as far as tracking goes:. The resting State (cmenubar :: track_none) Where nothing is happy That solves the tracking problem is the keyboard input, but there are other keyboard versions. Once you start implementing the keyboard to support the command bar, you realize that there are three possible states your command bar can be in it. For tracking transformation status: still too (cmenubar :: track_none) did not happen, trace_popup When you Tracking to pop-up, just like I discussed, there is a TRACK_BUTTON state, you press F10 or Alt intervention. In the TRACK_BUTTON state, one of the buttons (File, Edit, and so on) is highlighted and pressing right / left highlights the next / previous button, without displaying any popups. Implementing the TRACK_BUTTON state is a simple matter of processing keystrokes-no fancy hooks or anything. But how do you get the keystrokes? This is where CMenuBar :: TranslateFrameMessage comes in. I told you earlier one of the things you have to do to use CMenuBar is implement a PreTranslateMessage function that calls CMenuBar :: TranslateFrameMessage. Remember? This is what it looks like in pseudocode. In the TRACK_BUTTON state, one of the buttons (file, edit, et al.) Is highlighted and pressing the Right / Left highlighting the next / previous button in line without displaying any pop-up. Implementing the Track_Button status is a simple thing for a hit button - no hooks or anything. But how do you get hit? This is where cmenubar :: TranslateFrameMessage works. I originally told you that you are not doing is: Call CMenuBar :: TranslateFrameMessage Execute a PretranslateMessage function. remember? That's why it looks a bit like pseudo code. ToggleTrackButtonMode toggles the command bar's state between TRACK_NONE and TRACK_BUTTON. SetHotItem highlights the ith button-this is a CFlatToolBar function, a wrapper for TB_SETHOTITEM. Once again I'm glossing over details. For example, F10 comes in as WM_SYSKEYDOWN, not WM_KEYDOWN, and you have to handle key up as well as key down events to nail the details Also, if the user presses VK_DOWN (down-arrow) while in the TRACK_BUTTON state, CMenuBar goes into the TRACK_POPUP state;. that is, it "enters" the Popup for the hot button. There's Nothing Major Here, Just Different Uses of Functions You've Already Met. TOGGLETRACKBUTTONMODE Try the status of the command column between TRACK_NONE and TRACK_BUTTON. SetHotItem highlights - this is a CFLATTOOLBAR function for a layer of packaging for TB_SETHOTITEM. I have ignored the details again. For example, F10 is powered as a wm_syskeydown, not WM_KeyDown comes in, for the key to the event, your handle is not like processing, for this detail. So if the user presses VK_DOWN when it is in a TRACK_BUTTON state, cMenubar enters the TRACK_POPUP status; meaning, it will enter the pop-up for the hotkey. Nothing is here, just use the different uses of the functions you have encountered. What about other keys, like Alt-F to invoke the File menu or Alt-E for Edit? Or just typing F while in TRACK_BUTTON mode? Do you have to scan all the buttons looking for strings with an ampersand in them followed by a letter that matches? fortunately not. This is one place where toolbars come to the rescue. The latest (4.72) version of comctl32.dll has a toolbar message called TB_MAPACCELERATOR that takes a key and the address of a UINT, and returns TRUE if the key matches a button mnemonic, with the command ID returned in the UINT. CFlatToolBar has a wrapper for this called MapAccelerator. So all you have to do to handle Alt-F and company is add another if clause to TranslateFrameMessage. how other key , Such as Alt-f To activate the File menu or ALT-E activation editing menu? Or just type f in TRACK_BUTTON mode? You have to scan all the buttons, look for strings with '&', following the items that match the menu? Fortunately, not. This is where Toolbar isasting. The latest comctl32.dll has a message called TB_MapAccelerator to get a key and an address of an UINT, and returns true if the key and a button's mating, and bring this Command ID in the uint bit. CflatToolbar has a layer of packaging for this call mapaccelerator. So all of you have to do it is to process Alt-F and a TRANLATEFRAMEMESSAGE. For terminology pedants, TB_MAPACCELERATOR is a misnomer. It should be called TB_MAPMNEMONIC. Accelerators are shortcut keys defined by an accelerator table. The underlined & characters-such as the O in Open-are properly called mnemonics. CMenuBar does not have to do anything TO Handle Accelerators; MFC Handles Them Through ITS Normal Mechanisms. Speaking of MFC, how do command bars interact with the framework? Since CMenuBar passes the toolbar's owning window, which CFlatToolBar sets to be the frame window, to TrackPopupMenuEx, everything just works. TrackPopupMenuEx sends WM_INITMENUPOPUP and WM_COMMAND messages to the frame, just the way IT Would For a Normal frame-level menu. All Your on_command and on_command_update_ui handlers Go ON Working. TB_MapAccelerator is an error. It should be referred to as TB_MAPMNEMONIC. The accelerator button is a shortcut to define it by an acceleration key table. Uplined & characters - such as O in Open - should be called a mission. CMenubar is not all things that do not do processing accelerator s; MFC processes them through its normal mechanism. I should tell you if I have another obstacle. The first time I run my command bar, all my buttons look disease. That is to say they are ash. I was stunned, but a small adventure exposed a criminal. By default, MFC makes Menu items that do not have the handlers cannot be used. To simplify the implementation, I select the button index as its ID, so the IDs have items 0, 1, 2, etc., it does not mean to consider the button IDs as the command IDS because they represent the selection of the top menu, not command - But try telling MFC this! There is one other snag I should tell you about. The first time I ran my command bar, all my buttons looked rather sickly. Which is to say, grey. At first I was dumbfounded, but a little spelunking revealed the culprit. By default , MFC Disables MENU ITEMS That Don't Have Handlers. To Simplify Implementation, I Chose The Index of The Button As ITS ID, So The IDS Have Values Like 0, 1, 2, And So On. It Doesn't make SENSE TO think of the button IDs as being command IDs, since they represent top-level menu choices, not commands-but try telling MFC that! Since the IDs 0, 1, 2, and so on have no ON_COMMAND handlers, MFC insisted on disabling them . to work around this, I implemented an ON_UPDATE_COMMAND_UI_RANGE handler for all IDs in the range 0 to 255, one that specifically enables the buttons. 256 is an arbitray but reasonable number. If your top-level menu has more than 256 items, you need To schedule Some Therapy sessions with dr. gui. Therefore, IDS 0, 1, 2, etc., there is no ON_COMMAND processor, and MFC is resolutely unavailable. In order to make this all, I implemented an on_updata_command_ui_range handler for all IDs within the range between 0 and 255, which is explicitly the button is available. My test program, MBTest (see Figures 1 and 2), which is included in the code download at the top of this page, is a simple text editor based on CEditView It combines all the cool UI classes from my previous columns:. CFlatToolBar, CCoolBar, CCoolMenuManager, and CMenuBar Figure 6 shows only the source for CMenuBar like I said, it's a lot of code If it seems like too much, consider this:... CMenuBar does not even handle MDI buttons (min / restore / close ) for maximized mdi windows! and it doesn't do msaa (Microsoft Accessibility API) Either. I Leave these Minor Details As EXERCISES for the Reader. Good Luck!