Copyright by MSJ. I
原文:http://www.microsoft.com/msj/0199/c/c0199.aspx
Q I 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
我读了你的专栏,1997,8月和10月出版的MSJ,描述了怎样用MFC实现一个coolbar,我怎么能实现一个coolbar用我的coolbar,像Visual C++和Outlook一样的,我怎么开始呢?
A You don't know where to begin because it can't 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. Unfortunately, life is never that good in windowland. Command bars, as they're known in the trade, require lots of programming. That's why they take up the entire column this month.
你不知道从哪儿开始是因为用任何老办法根本不行。你大概希望你只要在CreateFooble函数中使用TBS_MENU标志,在你收到NM_PASSMETHENUPLEAZ通知时返回一个HMENU句柄就万事大吉了。可叹的是在windows世界生活从未那样好过。命令栏,如你所知,在这个行当里需要很多的编程。这就是为什么他们要占据这个完整专栏。
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's absolutely no way to stave off Mr. Acme Corp President from demanding the latest GUI look—now!—then keep reading.
答案是这样的:你得或多或少重新改造菜单栏。但在我要转变到奇怪方式之前,要注意一些词语。我要赌10美元,Microsoftiies为使命令控件最终工作需要呼叫很多DLL或别的什么,以同样的方法,他们也会使一般的控件和3D 界面成为Windows的一个标准部分。实际上我对此深信不疑。为什么?因为命令栏已经在Windows CE上使用了。因此最好的方法是,等待。但如果你没法子避开Mr. Acme Corp President 主席所需要的最流行的GUI界面,那就接着看吧。
I'll show you how to make even Mr. Acme happy.
我将让你知道怎样使Acme先生更加高兴。
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 don't 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.
同样每个人都知道我正在谈什么,到底什么是命令栏呢?命令栏是一个包括了菜单的工具栏,能放在coolbar(也叫rebars)的里面,就像是IE4.0。图一显示了一个coolbar,带一个命令栏,工具栏,和组合栏像是子窗口(或栏条)。命令栏的最大好处就是命令栏不需要占据整个框架的宽度。图二显示了所有的栏都挤在一条线上。对于寸土寸金的像素空间,许多按钮竞相露脸,命令栏却是赢家,这就为什么Window CE已经在使用它们(在掌上电脑中没有太多的像素空间)。Coolbars/rebars,和命令栏,还有cool menu 是新的GUI样貌的全部。(译者:大概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.
为了实现命令栏,我写了一个MFC类,称之为CMenuBar。我应该称之为CCommandBar,但是我写的代码是在红头文件出现之前(不管怎样,menu bar更为准确)。CMenuBar很好用。先在你的coolbar类中实例化一个,生成和载入它,然后像其他的栏条一样把它增加到你的coolbar中。如果你正在使用我的CCoolBar类,在October发表的那个,就在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 = // whatever 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 translate messages.
VC6.0中有一个新的CReBar类为了做coolbar;如果你想用CReBar类,你还是得做很多的调整:无论哪种方法,有一件或更多的事你必须做:写一个PreTranslateMessage 函数给你的CMenuBar类一个机会来转换信息。
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.
就是这样。当然我设计CMenuBar很容易。那就是我的工作。但在简单界面的背后实实在在地堆放着很多难懂的MFC代码的堆砌,在这儿有太多的代码全是为了解释它,我仅能提供给你几点要素,而你应该去源代码中寻找细节。
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?
但是所有的代码的道理在哪里呢?对command bars 最大的处理是什么呢?自诞生之日起,Windows就考虑把菜单做成一个具有顶层窗口的特征,它们自己不像是是窗口。你来指定菜单,当生成窗口的时候或是通过呼叫SetMenu。Windows做余下的部分。它画菜单栏,处理鼠标行为,跟踪弹出,处理键盘信息,向你的应用程序发送各种各样的乱七八糟的消息,比如WM_INITMENU,为了你能做点事情。但是在Windows中,唯有框架窗口,而不是子窗口才能够拥有菜单,和一个跨越整个框架的菜单栏。对于命令栏来说,如果你想要的是一个自治的大小可变的菜单窗口。那么一个可怜的开发人员该怎么做呢?
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.
不幸的是,在一个窗口里面,你能做的唯一一件事就是从头改变一些事情。哦,不是所有的事。你能够呼叫TrackPopupMenuEx来运行弹出菜单,因此你真得实现一个菜单栏-菜单项的顶层。有很多方法-VC和Office产品都有他们自己的特别的窗口类-IE4.0用一个代文字按钮的ToolbarWindow32开始(好像是反对图标化)
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 each top-level menu item. 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!
图3显示了一个命令栏这种实现方式的要素:工具栏(菜单栏),按钮,弹出菜单,这些都是在一个coolbar中。你得使他们粘合在一起工作。意思就是,代码要处理鼠标和键盘的输入为了跟踪按钮,还有适当地为每个顶层菜单项显示弹出菜单。听起来很容易,但要是转变成代码却是步履维艰,就像是清洁一个极为肮脏的地方。让我们挽起袖子,开始大扫除吧!
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派生自我的CFlatToolBar(October 1997)。第一个有趣的CMenuBar函数是LoadMenu。那是一个你为了载入菜单所调用的函数,记得吗?它有三个重载,只有一个做所有的工作。
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 don't 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:
在删除所有按钮之后,LoadMenu为每个顶层菜单增加一个按钮。按钮设置成TBSTYLE_AUTOSIZE风格,告诉toolbar按钮的大小以文本为基准。机灵的读者会注意到我忽略了如何在按钮上显示文本的细节。Toolbars用一种古怪的方式做这个。你不是把文本设置成LPCTSTR类型。反之,你生成一个串,拥有所有文本标签,以双零为结尾。
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't 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 won't 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.
接着你用这个串为参数呼叫AddString,并且为每一个按钮,你在TBBUTTON::iString项指定其索引。好家伙,够怪的?更怪的是你不能删除串,你只能增加!然而我想要让程序员重复的呼叫LoadMenu来载入不同的串,因此CMenuBar不得不和toolbar进行一些歇斯底里的战斗。CMenuBar保存了一个队列:它曾经增加的所有串,因为我不想增加同样的串。否则toolbar可能最终(大约几世纪后)超出内存。为了使你省去清空框架菜单的纠缠,当你呼叫LoadMenu时,CMenuBar自动的清空你的框架菜单。那是因为如果你用菜单栏框架菜单没有多少意义。但是MFC会变得很糟糕,如果当框架生成时你这样做:p pFrame->SetMenu(Null), 代之CMenuBar会使发送消息给它自己的情况推迟。
At this point what you have is a toolbar with a bunch of text buttons that don't 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 popup menu with the File commands: Open, Save, Print, and so on. No problem. Just add some code to do it.
现在你只有一个带着文本按钮的工具栏,什么事情也不做。你按下一个按钮想要出现一个弹出菜单。譬如,你点击File按钮,你希望看到弹出菜单带着如下命令:Open,Save, Print, 等等。小Case,瞧好吧您呢!
void CMenuBar::OnLButtonDown(UINT nFlags, CPoint pt)
{
int iButton = HitTest(pt);
if (iButton >= 0 && iButton<GetButtonCount())
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 is totally outside the toolbar window!) The other function, TrackPopup, displays the popup.
CMenuBar::HitTest 返回一个按钮命中,没命中返回-1。它重载了TB_HITTEST,它在很多版本的Comctl32.dll中的处理都有毛病。其他的函数TrackPopup,显示一个弹出菜单。
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 TrackPopupMenuEx to return.
按下按钮,执行弹出,并且不再按按钮。一切非常简单。但当你运行这个代码之时你就撞上砖墙一堵。你的程序开始很好,所有可爱的按钮出现。你点击File,TrackPopup显示弹出菜单,上面带有Open,Save,等等。但是现在试着移动鼠标到一个不同的菜单项上,像是Edit。发生什么?任何人曾经用过Windows的都知道什么将发生:系统将消除File的弹出(Open,Save,Print)并且显示Edit弹出(Cut,Copy,Paste)。这一点实际上发生了吗?没没没没没。为什么没?因为CMenuBar仍然正在等待TrackPopupMenuEx的返回。
When you call TrackPopupMenuEx, control disappears into a black hole—TrackPopupMenuEx's own little mini message loop—and doesn't 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 won't return control. So how are you supposed to track the popups when you don't 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?
当你呼叫TrackPopupMenuEx,控制权消失进入一个黑洞-TrackPopupMenuEx自己的小形迷你信息循环-而且再也不出来直到用户选择一项或者消除这个菜单通过在它外面点击,按Escape,或者使劲踢你的硬盘使它飞出壳儿外。用户能够移动鼠标到他喜欢的所有地方;TrackPopupMenuEx没有返还控制权。因此假设是你你要怎样追踪弹出菜单呢,当你没有控制权的时候?不管怎样如果你能察觉当鼠标在一个新的按钮上移动,你能够发送WM_CANCELMODE来取消弹出菜单,然后显示一个不同的一个。但是你怎样觉察鼠标在哪儿,当你的代码没有控制的时候?
The answer is: install a Windows hook. Oh dear, not that. Windows hooks are to most programmers what a cross is to a vampire. Any time the answer is "install a Windows hook," you know you're in, as my 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.
回答是:安装一个Windows hook。噢,不是那样的。Windows hooks是大多数程序员到吸血鬼的一个什么分水岭。任何时候回答是“安装一个Windows hook,”你知道连你也在内,像是我的地质学朋友称它,深奥难懂的岩石。但它不是真那么糟糕。一个Windows钩子只不过是一个你能够安装的回叫函数,Windows会呼叫你,当某些有趣的事情发生。有几个钩子,在这里能产生预期的效果的一个是WH_MSGFILTER钩子。如果你安装了一个WH_MSGFILER钩子,Windows呼叫它,无论什么时候,有任何一种输入事件-鼠标,键盘,cortical 队列-去往对话框,信息框,卷动条,或是菜单。因此下一个TrackPopup的重写就像是图五所做的事情那样。
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.
想法是这样的。在呼叫TrackPopupMenuEx之前,安装一个菜单输入钩子。钩子监视鼠标事件。如果钩子发现用户在不同的按钮上移动了鼠标,它记下按钮并取消弹出菜单。TrackPopupMenuEx返回,TrackPopup移除钩子接着,m_iNewPopup被设置,重复整个过程,这次显示了一个不同的弹出。如果TrackPopupMenuEx因为不同的原因返回,m_iNewPopup将变成-1并且TrackPopup退出。好极了,这就是钩子函数。
// (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_hMsgHook, 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意思是这是一个菜单(而不是对话框或其他什么)事件,在这种情况下hook把责任推给一个CMenuBar的虚拟成员函数,OnMenuInput。为了推断出是哪一个CMenuBar对象被呼叫,我通过给钩子函数传递一个全局变量g_pMenuBar(多线程 注意)。没有其他的办法传递它,因为SetWindowsHook没有void* cookie参数。OnMenuInput像下面这样处理这件事。
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()->PostMessage(WM_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.
当用户移动鼠标,OnMenuInput检查看鼠标现在是否在一个不同的按钮上。果是如此,它记下新的按钮用m_iNewPopup并且发送WM_CANCELMODE退出当前的弹出菜单。TrackPopupMenEX退出,TrackPopup显示新的弹出。
There are more mouse details I won't 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.
有更多的鼠标细节我没有在这里谈到(看在图六的源代码)。重要的事情是知道如何安装一个钩子追踪鼠标的消息,为了执行一个基本的弹出行为。这是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.
鼠标输入先放下。下一个,键盘。有几种版本在这里。首先是处理右箭头和左箭头键,来追踪弹出,就入鼠标一样。你可能怀疑,代码几乎是一样的。同样的OnMenuInput为两者处理工作。
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()->PostMessage(WM_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.
不同的是代之以呼叫HitTest来决定是不是新的按钮/弹出,OnMenuInput呼叫另一个函数,GetNextOrPrevButton。这个函数简单的增加或是减少按钮的数字,在末尾包装。
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 don't 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 doesn't. 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, CSubclassWnd. 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.
一般来说,事实不如我写的这样简单。如果高亮的菜单项有一个子项,按右箭头不应该显示下一个顶层菜单,而应该显示子菜单。换个词说,不做任何事情,让TrackPopupMenuEx做它的事情。同样的如果用户按下左箭头要退出子菜单,以没想过要显示前一个顶层菜单。因此你需要很多代码来处理这些特殊的情况。OnMenuInput怎么知道popup和item TrackPopupMenuEx是当前的高亮呢?它不知道。但是TrackPopupMenuEx 发送一个WM_MENUSELECT消息不管什么时候用户选择了一个新的菜单项。问题是Windows发送信息给无论哪一个你指定的拥有者窗口,当你呼叫TrackPopupMenuEx的时候。对于所有MFC消息映射都使CMenuBar指定了它的所有者-容纳的框架-做为弹出菜单的拥有者。因此Windows发送WM_MENUSELECT去了那里。要中途拦截它,CMenuBar用了一个我的标准编程技巧,CSubclassWnd。这个类让任何对象拦截发送到窗口的消息。CMenuBar定义了它自己的CSubclassWnd用来拦截发送到框架的WM_MENUSELECT消息。
LRESULT
CMenuBarFrameHook::WindowProc(
UINT msg, WPARAM wp, LPARAM lp)
{
if (msg==WM_MENUSELECT) {
m_pMenuBar->OnMenuSelect(
(HMENU)lp, (UINT)LOWORD(wp));
}
return CSubclassWnd::WindowProc(msg, wp, lp);
}
Following the usual paradigm, CMenuBarFrameHook passes 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, iItem) == NULL);
m_bProcessLeftArrow =
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 doesn't 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 设置了很多标志来通知OnMenuInput当要处理箭头键时。CMenuBar仅仅会处理VK_RIGHT如果当前的菜单项没有子菜单;它也仅仅处理VK_LEFT仅仅如果当前菜单和原来传给TrackPopupMenuEx的一样(就是说,没有子菜单)。当然,你不得不修改CMenuBar::TrackPopup来设置m_hMenuTracking在呼叫TrackPopupMenuEx之前,并且OnMenuInput仅要处理left/right键如果m_bProcessLeft/RightArrow被设置。认为它做完了。
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 happening, TRACK_ POPUP when you track popups, as I've been discussing, and TRACK_BUTTON state, which you enter by pressing F10 or Alt.
那个解决了跟踪问题为键盘输入,但是还有其他的键盘版本。一旦你开始实现键盘支持为命令栏,你认识到有实际上有三种可能的状态你的命令栏能在其中,对于跟踪转变状态:静止太(CMenuBar::TRACK_NONE)什么也没发生,TRACE_POPUP当你追踪到弹出,就像是我讨论的那样, 还有TRACK_BUTTON状态,你按下F10或是Alt介入。
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.
在TRACK_BUTTON状态中,按钮的一个(File,Edit,等等)is 高亮的并且按系按下right/left高亮next/previous按钮,没有显示任何弹出。实现TRACK_BUTTON状态是一个处理击键的简单事情-没有用到钩子或是任何什么。但是你如何获取击键呢?这就是CMenuBar::TranslateFrameMessage起作用的地方了。我原来告诉过你你非得做的一件事是:呼叫CMenuBar::TranslateFrameMessage执行PreTranslateMessage函数。记得吗?这就是为什么它看起来有点像伪代码。
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栓牢命令栏的状态在TRACK_NONE和TRACK_BUTTON之间。SetHotItem高亮第几个按钮-这是一个CFlatToolBar函数,为了TB_SETHOTITEM的一层包装。我又一次忽略了细节。例如,F10扮成WM_SYSKEYDOWN,而不是WM_KEYDOWN进来了,对于键击事件,你处理抬起倒不如处理按下,对于这个细节。因此如果用户按下VK_DOWN当处于TRACK_BUTTON状态,CMenuBar进入TRACK_POPUP状态;意思是,它为了热键进入弹出。没什么主要的在这儿,只是对于你曾遇到过的函式的不同运用。
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.
对于其他的键又如何呢,譬如Alt-F要激活File菜单或者Alt-E激活编辑菜单?或仅仅键入F在TRACK_BUTTON模式下?你非得扫描所有的按钮,寻找带有‘&‘的串,在它们后面跟着一个与菜单相匹配的项吗?幸好不是。这就是toolbar出力的地方。最新的Comctl32.dll有一个称之为TB_MAPACCELERATOR获取一个键和一个UINT的地址的消息,并且返回TRUE如果键和一个按钮的助记符相匹配,并带这一个command ID在UINT位之中。CFlatToolBar有一层包装为了这个呼叫MapAccelerator。因此你不得不作的所有的就是处理Alt-F并且附近一个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 doesn't 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是一个错误。它应该被称为TB_MAPMNEMONIC。加速键是一个快捷键被一个加速键表定义。加下划线的&字符-比如O在Open中-应该被称作助记符。CMenuBar不是非得做处理加速键s的所有事;MFC处理它们通过它的正常机制。
有一个别的障碍我应该告诉你。第一次我运行我的命令栏,所有我的按钮看起来都有病。就是说它们是灰的。起先我被愣住了,但是一次小小的探险揭露了罪犯。默认情况下MFC使没有处理者的menu items不能用。为了简化实现,我选择按钮的索引作为它的ID,因此IDs有项0,1,2等等的值,它没有意义考虑把按钮IDs作为命令IDs,因为它们表示顶层菜单的选择,不是命令-但是尝试告诉MFC这个吧!
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.
因此IDs 0,1,2等等没有ON_COMMAND处理者,MFC坚决使之不可用。为了使这一切工作,我实现了一个ON_UPDATA_COMMAND_UI_RANGE处理者为所有在0到255之间的范围内的IDs,明确地是按钮可用。
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 doesn't 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!
我的测试程序,MBTest(看Figures 1和2),在这页顶部的代码下载中,是一个简单的基于CEditView的文本编辑器。它综合了来源于我以前专栏的所有cool UI类:CFlatToolBar, CCoolBar, CCoolMenuManager,和CMenuBar. 图六仅显示了CMenuBar的源代码。正如我说的,好大一坨代码。如果它看起来太大了,想想这点:CMenuBar还没有处理MDI按钮(min/restore/close)对于最大MDI窗口!还有它也没有做MSAA。我留下这些次要的细节给读者作为练习。Good luck!
本文地址:http://com.8s8s.com/it/it26806.htm