This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.June 1996C++ Q&APaul DiLascia Paul DiLascia is a freelance software consultant specializing in training and software development in C++ and Windows. He is the author of Windows++: Writing Reusable Code in C++ (Addison-Wesley, 1992).Click to open or copy the DLGIDLE project files.Click to open or copy the SCBMOD3 project files.Click to open or copy the SCBMOD4 project files.The first question this month is really a continuation of a question from last month's column. Phillipe Bernous asked how to implement a "modified indicator" that displays an asterisk in the title bar next to the document name if the document is modified. He implemented an indicator in the status bar using the normal MFC ON_UPDATE_COMMAND_UI mechanism. This enabled or disabled his status bar's "MOD" pane based on the return value from CDocument::IsModified. Phillipe wanted to know if there is a similar way to update the title bar with ON_UPDATE_COMMAND_UI.In my answer, I explained that MFC doesn't use ON_UPDATE_COMMAND_UI to update the window title. I went on to show two different ways to solve his problem. In the first solution, I overrode CDocument::SetModifiedFlag to call CDocument::SetTitle whenever some other part of the code changed the document's modified flag. In the second solution, I overrode CFrameWnd::OnUpdateFrameTitle to append an asterisk to the document title if the document was modified, and wrote a CChildFrame::OnIdleUpdateCmdUI handler for WM_IDLEUPDATECMDUI. This handler called OnUpdateFrameTitle, making MFC update the title, whenever the current modified state changed from the last WM_IDLEUPDATCMDEUI message. This second solution resembled the ON_UPDATE_COMMAND_UI mechanism, and it wouldn't take much extra effort to actually implement title bar "panes" with ON_UPDATE_COMMAND_UI. I promised I'd show you how in the next column. Well, here we are!I'll start with a question that makes sense whether or not you saw the previous column: how can you implement general-purpose title bar panes that are analogous to status bar panes and that you can update using ON_UPDATE_COMMAND_UI? By title bar pane I mean some portion of the window title that displays something interesting. It could display "*" for a modified document, the document's time and date of creation, or the name of the item you've selected (if your document has a notion of named items). Each pane should have an integer ID that you use to identify it in your message map entries. ON_UPDATE_COMMAND_UI(ID_MOD_INDICATOR, OnUpdateMod)void CScribbleDoc::OnUpdateMod(CCmdUI* pCmdUI)
{
      pCmdUI->SetText(IsModified() ? "*" : "");
}
This code should display "*" in the title bar if you modify the current document.It's not hard to set this up if you understand how MFC updates user-interface objects. The best way is to see how MFC handles status panes, then mimic that scheme for the title bar. MFC updates status bar panes, toolbar buttons, and menu items by implementing a special CCmdUI-derived class for each particular kind of UI element. The base class, CCmdUI, provides access to a menu item. CStatusCmdUI and CToolCmdUI let you control the state of a status bar pane or toolbar button. CCmdUI has virtual functions like SetText and Enable that manipulate the item. In the case of a menu item, CCmdUI::SetText sets the menu item text and CCmdUI::Enable grays or ungrays it. For a toolbar button, CToolCmdUI::SetText does nothing (toolbar buttons don't have text) and CToolCmdUI::Enable enables the button. For status bar panes, CStatusCmdUI::SetText sets the text in the status bar pane and CStatusCmdUI::Enable shows or hides it. You don't ever need to know that there are several different kinds of CCmdUI floating around; you only need to call the right functions, SetText, Enable, SetCheck, and so on, from your ON_COMMAND_UPDATE_UI handler. Because these functions are virtual, they do the right thing for whatever kind of UI element the CCmdUI really represents: menu item, toolbar button, or status bar pane. So, to implement title bar panes, you need a new kind of CCmdUI.The next question is, how and when does your UI handler get called? As part of its normal idle processing, MFC sets up a CCmdUI object for every menu item, toolbar button, and status bar pane in your app, then calls CCmdUI::DoUpdate for each one. CCmdUI::DoUpdate ends up groveling over your message maps looking for ON_UPDATE_ COMMAND_UI handlers with IDs that match the ID of the menu item, toolbar button, or status bar pane. (See my article "Meandering through the Maze of MFC Message and Command Routing," MSJ, July 1995.) Each object that owns user-interface items does this. For example, CStatusBar and CToolBar update their panes and buttons, respectively. CWinFrm updates menu items in response to WM_INITMENUPOPUP, not during the idle loop, because you need to update the menus only when the user clicks on one.To review quickly, there are three steps for implementing title bar panes. First, implement a CTitleBar class that describes the panes. Second, derive a new class from CCmdUI with overrides for SetText, Enable, SetCheck, and so on. Third, hitch a ride on WM_IDLEUPDATECMDUI to sail these little CCmdUI beasties around the system so anybody can catch one just by adding an ON_UPDATE_COMMAND_UI handler in their message map. By "anybody," I mean any object that would normally receive command notifications, such as the frame, view, or document.Since Scribble is the mother of all sample programs, I started with the Scribble program from the MFC tutorial and modified it to display two panes in the title: first, the asterisk that indicates the current document's modified state; and second, the number of strokes in the Scribble drawing formatted as "[S:n]" where n is the number of strokes. Since real estate on the title bar is expensive, I figured it would be OK to display "[S:n]" instead of something more user-friendly like, "The number of strokes in this here document you are right now looking at is: n". Besides, we're all nerds here.Last month's solutions were SCBMOD1 and SCBMOD2, so I gave this solution the surprising name of SCBMOD3 (see Figure 1). I've only shown the changes from the original Scribble program, which are confined to ScribDoc, ChildFrm, and an entirely new module, TitleBar. Figure 2 shows SCBMOD3 in action with a document that has five strokes. (In Scribble, a stroke represents a single mouse-down/mouse-up movement, and may contain many individual line segments.)Figure 2 SCBMOD3 changes in Scribble
I also need a class to represent the whole title bar, which I will call CTitleBar. As titlebar.h shows, CTitleBar is just a pointer to an array of TITLEPANEs. To create a title bar in your app, instantiate a CTitleBar object in your frame window class and call CTitleBar::SetIndicators with an array of ints, the IDs for the indicators. class CChildFrame : public CMDIChildWnd {
      CTitleBar      m_titleBar;   // title bar
                             ?
                             ?
                             ?
};CChildFrame::CChildFrame()
{
      static UINT indicators[] = 
            { ID_TITLE_MOD, ID_TITLE_NUMSTROKES };      // Create title bar indicators
      m_titleBar.SetIndicators(indicators, 
            sizeof(indicators)/sizeof(indicators[0]));
}
This looks just like it would for status bar panes, only I do my initialization in the CChildFrame constructor instead of OnCreate. (I already had an empty constructor function and was too lazy to implement OnCreate and hook it up to the message map. Anything to avoid typing.) Also, note that the title bar belongs to the child frame, not the main frame. CTitleBar::SetIndicators allocates and initializes an array of TITLEPANEs based on the array of ints passed.The only other thing you have to do to hook up the title panes is implement a handler for WM_IDLEUPDATECMDUI. LRESULT 
CChildFrame::OnIdleUpdateCmdUI(WPARAM wParam, LPARAM)
{
      m_titleBar.OnIdleUpdate(this, (BOOL)wParam);
      CMDIChildWnd::OnIdleUpdateCmdUI();
      return 0L;
}
The implementation is trivial; you just call the CTitleBar::OnIdleUpdate function I wrote, then call the default implementation for CMDIChildWnd (the order isn't really important). This is the only ugly part of the implementation. Ideally, you wouldn't need CChildFrame::OnIdleUpdateCmdUI; however, MFC is notoriously difficult when it comes to writing extension classes, and CTitleBar is really an extension class. There's no way for CTitleBar to hook WM_IDLEUPDATECMDUI invisibly on behalf of another window-aside from inserting its own WndProc, thereby circumventing MFC. Working by the book, you would derive a new class (CNewMDIChildWnd, for example) that handles the message, and make developers derive from that. Unfortunately, this tactic begs the question: what if someone else defines a CEvenNewerMDIChildWnd? Which one does the developer derive from? You can only use one.My compromise solution is to give you the handler function, CTitleBar::OnIdleUpdate, but make you hook it up to WM_IDLEUPDATECMDUI yourself. What does CTitleBar::OnIdleUpdate actually do? This is where the CCmdUIs come in.In titlebar.cpp, I derive a new class, CTitleCmdUI, that represents a user interface to a single TITLEPANE. It has SetText, Enable, and SetCheck overrides. SetText sets the text, Enable sets the m_bEnabled flag on, and SetCheck does nothing, but is needed to hide the SetCheck CTitleCmdUI inherits from CCmdUI. CTitleBar::OnIdleUpdate creates a CTitleCmdUI on the stack and loops over all the TITLEPANEs, successively pointing the CTitleCmdUI at each one before calling the magic CCmdUI::DoUpdate. If you remember, DoUpdate is the CCmdUI function that routes the darn thing to all the message maps. If there's an ON_UPDATE_COMMAND_UI anywhere for this title pane, MFC calls the handler, which calls SetText, Enable, or whatever it wants to update the title pane based on the current state of your app.Once CTitleBar::OnIdleUpdate routes the panes all over the world, each TITLEPANE has the correct text and enable flag and you can actually set the title in the title bar. This is where my implementation differs slightly from menu items and status bar panes. When you call CCmdUI::SetText for a menu or status bar pane, the change is reflected immediately; with my CTitleCmdUI, SetText only changes the text in the TITLEPANE structure. Only after all the TITLEPANEs are updated do I actually change the window's title bar. This is necessary because the whole window title must be assembled by concatenating the normal MFC title (app and document names) with all the TITLEPANEs before setting the title with SetWindowText. Note that the order of the IDs in the original SetIndicators array of ints is the order they appear in the title bar. CTitleBar::OnIdleUpdate compares the new title with the previous one and calls SetWindowText only if the title changed since the last update. This avoids the screen flicker that would result if you set the window title on every idle update cycle.That's about it. The only thing left to do is write the actual ON_UPDATE_COMMAND_UI handlers and put them in the message maps. Here's one for the stroke indicator: BEGIN_MESSAGE_MAP(CScribbleDoc, CDocument)
                                    ?
                                    ?
                                    ?
      ON_UPDATE_COMMAND_UI(ID_TITLE_NUMSTROKES, 
                           OnUpdateNumStrokes)
END_MESSAGE_MAP()void CScribbleDoc::OnUpdateNumStrokes(CCmdUI* pCmdUI)
{
      CString s;
      s.Format(" [S:%d]", m_strokeList.GetCount());
      pCmdUI->SetText(s);
      pCmdUI->m_bContinueRouting = TRUE;
}
The last line lets other objects update the same CCmdUI item. In my case, I update the contents ("[S:%d]") in CScribbleDoc, but I update the enabled status in CChildFrame since it owns the m_bViewTitleInfo flag. Without setting m_bContinueRouting to TRUE, MFC would stop routing the CCmdUI as soon as CScribbleDoc handled it.So, there you have it. Whether or not you want to implement title bar panes, at least now you understand how CCmdUI works. You should be able to implement any kind of UI indicator with ON_UPDATE_COMMAND_UI. For example, you can update a gallery, palette, or other UI widget with ON_UPDATE_CMD_UI. In general, it's much easier to update UI elements on demand than maintain them as your program's state changes. It also makes your code cleaner and more object-oriented.Just to prove it could be done, I implemented another version of Scribble, SCBMOD4, where I derive a CMyTitleBar that handles the "View Title Info" command and manages the indicator panes. (In SCBMOD3, this is done in CChildFrame.) It seems more logical that the title bar should manage itself, rather than having the child frame do it. Both versions are included in the source code for this column. Use them with my blessing.

解决方案 »

  1.   

    帮忙Up
    http://www.csdn.net/Expert/topic/481/481079.shtm
      

  2.   

    我知道,在改了后用DOC.SETMODIED啊!但我不知道用什么函数使标题改为有个*!
      

  3.   

    不知道怎么可以激发SETTITLE函数,我用SETFILEPAHT可以,但要设置路径的,难道没其他标准些的方法吗?
      

  4.   

    看这两句
    I overrode CFrameWnd::OnUpdateFrameTitle to append an asterisk to the document title if the document was modified, and wrote a CChildFrame::OnIdleUpdateCmdUI handler for WM_IDLEUPDATECMDUI.