MainFrm.h
////////////////////////////////////////////////////////////////
// GRAYSUB 1998 Microsoft Systems Journal.
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
////////////////
// Standard MFC main frame window
//
class CMainFrame : public CFrameWnd {
public:
virtual ~CMainFrame();
protected:
BOOL m_bEnableSubmenu;
BOOL m_bEnableFoo;
CMainFrame();
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnEnableSubmenu();
afx_msg void OnUpdateEnableSubmenu(CCmdUI *pCmdUI);
afx_msg void OnFoo();
afx_msg void OnUpdateFoo(CCmdUI *pCmdUI);
afx_msg void OnEnableFoo();
afx_msg void OnUpdateEnableFoo(CCmdUI *pCmdUI);
DECLARE_MESSAGE_MAP()
DECLARE_DYNCREATE(CMainFrame)
};
MainFrm.cpp
////////////////////////////////////////////////////////////////
// GRAYSUB 1998 Microsoft Systems Journal.
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
// GRAYSUB illustrates how to gray a submenu item.
// Compiles with Visual C++ 5.0 or later, under Windows 95.
//
#include "StdAfx.h"
#include "GraySub.h"
#include "MainFrm.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_CREATE()
ON_COMMAND(ID_ENABLE_SUBMENU, OnEnableSubmenu)
ON_UPDATE_COMMAND_UI(ID_ENABLE_SUBMENU, OnUpdateEnableSubmenu)
ON_COMMAND(ID_FOO, OnFoo)
ON_UPDATE_COMMAND_UI(ID_FOO, OnUpdateFoo)
ON_COMMAND(ID_ENABLE_FOO, OnEnableFoo)
ON_UPDATE_COMMAND_UI(ID_ENABLE_FOO, OnUpdateEnableFoo)
END_MESSAGE_MAP()
CMainFrame::CMainFrame()
{
m_bEnableSubmenu = FALSE;
m_bEnableFoo = FALSE;
}
CMainFrame::~CMainFrame()
{
}
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
return 0;
}
//////////////////
// Set initial window size to 200 x 200
//
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
BOOL bRet = CFrameWnd::PreCreateWindow(cs);
cs.cx = cs.cy = 200;
return bRet;
}
//////////////////
// Enable/Disable the submenu
//
void CMainFrame::OnEnableSubmenu()
{
m_bEnableSubmenu = !m_bEnableSubmenu;
}
//////////////////
// Set text for File | Enable/Disable Submenu
//
void CMainFrame::OnUpdateEnableSubmenu(CCmdUI *pCmdUI)
{
pCmdUI->SetText(m_bEnableSubmenu ?
_T("&Disable Submenu") : _T("&Enable Submenu"));
}
//////////////////
// Handle File | Submenu | Foo
//
void CMainFrame::OnFoo()
{
AfxMessageBox(_T("Foo to you too."));
}
//////////////////
// Update handler for File | Submenu | Foo handles updating
// of the command and the submenu item itself.
//
void CMainFrame::OnUpdateFoo(CCmdUI *pCmdUI)
{
if (pCmdUI->m_pSubMenu) {
// update submenu item (File | Submenu)
UINT fGray = m_bEnableSubmenu ? 0 : MF_GRAYED;
pCmdUI->m_pMenu->EnableMenuItem(pCmdUI->m_nIndex,
MF_BYPOSITION|fGray);
} else
// update command item (File | Submenu | Foo)
pCmdUI->Enable(m_bEnableFoo);
}
/////////////////
// Enable/Disable the File | Submenu | Foo command
//
void CMainFrame::OnEnableFoo()
{
m_bEnableFoo = !m_bEnableFoo;
}
/////////////////
// Set text for File | Submenu | Enable/Disable Foo
//
void CMainFrame::OnUpdateEnableFoo(CCmdUI *pCmdUI)
{
pCmdUI->SetText(m_bEnableFoo ?
_T("&Disable Foo") : _T("&Enable Foo"));
}
Figure 4 CTrayIcon
TrayIcon.h
////////////////////////////////////////////////////////////////
// CTrayIcon 1999 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
#ifndef _TRAYICON_H
#define _TRAYICON_H
#include "Subclass.h"
////////////////
// CTrayIcon manages an icon in the Windows 95 system tray.
// The sample program TRAYTEST shows how to use it.
//
class CTrayIcon : public CCmdTarget {
public:
CTrayIcon(UINT uID);
~CTrayIcon();
// Call this to receive tray notifications
void SetNotificationWnd(CWnd* pNotifyWnd, UINT uCbMsg);
// SetIcon functions. To remove icon, call SetIcon(0)
//
BOOL SetIcon(UINT uID); // main variant you want to use
BOOL SetIcon(HICON hicon, LPCTSTR lpTip);
BOOL SetIcon(LPCTSTR lpResName, LPCTSTR lpTip)
{ return SetIcon(lpResName ?
AfxGetApp()->LoadIcon(lpResName) : NULL, lpTip); }
BOOL SetStandardIcon(LPCTSTR lpszIconName, LPCTSTR lpTip)
{ return SetIcon(::LoadIcon(NULL, lpszIconName), lpTip); }
// Following is obsolete. CTrayIcon does default handling automatically.
// virtual LRESULT OnTrayNotification(WPARAM uID, LPARAM lEvent);
virtual LRESULT OnTrayNotify(WPARAM uID, LPARAM lEvent);
virtual LRESULT OnTaskBarCreate(WPARAM wp, LPARAM lp);
protected:
NOTIFYICONDATA m_nid; // struct for Shell_NotifyIcon args
// private class used to hook tray notification and taskbarcreated
class CTrayHook : public CSubclassWnd {
private:
CTrayIcon* m_pTrayIcon;
virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp);
friend CTrayIcon;
};
friend CTrayHook;
CTrayHook m_notifyHook; // trap tray notifications
CTrayHook m_parentHook; // trap taskbarcreated message
DECLARE_DYNAMIC(CTrayIcon)
};
#endif
TrayIcon.cpp
//////////////////////////////////////////////////////////////////
// 1999 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 5.0 on Windows 95
//
#include "stdafx.h"
#include "trayicon.h"
#include <afxpriv.h> // for AfxLoadString
#define countof(x) (sizeof(x)/sizeof(x[0]))
//////////////////
// Windows sends this message when the taskbar is created. This can happen
// if it crashes and Windows has to restart it. CTrayIcon responds by
// re-installing its icon.
//
const UINT WM_TASKBARCREATED = ::RegisterWindowMessage(_T("TaskbarCreated"));
IMPLEMENT_DYNAMIC(CTrayIcon, CCmdTarget)
CTrayIcon::CTrayIcon(UINT uID)
{
// Initialize NOTIFYICONDATA
memset(&m_nid, 0 , sizeof(m_nid));
m_nid.cbSize = sizeof(m_nid);
m_nid.uID = uID; // never changes after construction
m_notifyHook.m_pTrayIcon = this; // notification window hook
m_parentHook.m_pTrayIcon = this; // parent window hook
// Use resource string as tip if there is one
AfxLoadString(uID, m_nid.szTip, sizeof(m_nid.szTip));
}
CTrayIcon::~CTrayIcon()
{
SetIcon(0); // remove icon from system tray
}
//////////////////
// Set notification window. It must be created already.
//
void CTrayIcon::SetNotificationWnd(CWnd* pNotifyWnd, UINT uCbMsg)
{
// If the following assert fails, you're probably
// calling me before you created your window. Oops.
ASSERT(pNotifyWnd==NULL || ::IsWindow(pNotifyWnd->GetSafeHwnd()));
m_nid.hWnd = pNotifyWnd->GetSafeHwnd();
ASSERT(uCbMsg==0 || uCbMsg>=WM_USER);
m_nid.uCallbackMessage = uCbMsg;
CWnd* pParentWnd = pNotifyWnd ? pNotifyWnd->GetTopLevelParent() : NULL;
// Install window hooks. Must be different because
// taskbar creation message only goes to top-level parent.
m_notifyHook.HookWindow(pNotifyWnd);
if (pParentWnd!=pNotifyWnd)
m_parentHook.HookWindow(pParentWnd);
}
//////////////////
// This is the main variant for setting the icon.
// Sets both the icon and tooltip from resource ID
// To remove the icon, call SetIcon(0)
//
BOOL CTrayIcon::SetIcon(UINT uID)
{
HICON hicon=NULL;
if (uID) {
AfxLoadString(uID, m_nid.szTip, sizeof(m_nid.szTip));
hicon = AfxGetApp()->LoadIcon(uID);
}
return SetIcon(hicon, NULL);
}
//////////////////
// Common SetIcon for all overloads.
//
BOOL CTrayIcon::SetIcon(HICON hicon, LPCTSTR lpTip)
{
UINT msg;
m_nid.uFlags = 0;
// Set the icon
if (hicon) {
// Add or replace icon in system tray
msg = m_nid.hIcon ? NIM_MODIFY : NIM_ADD;
m_nid.hIcon = hicon;
m_nid.uFlags |= NIF_ICON;
} else { // remove icon from tray
if (m_nid.hIcon==NULL)
return TRUE; // already deleted
msg = NIM_DELETE;
}
// Use the tip, if any
if (lpTip)
_tcsncpy(m_nid.szTip, lpTip, countof(m_nid.szTip));
if (m_nid.szTip[0])
m_nid.uFlags |= NIF_TIP;
// Use callback if any
if (m_nid.uCallbackMessage && m_nid.hWnd)
m_nid.uFlags |= NIF_MESSAGE;
// Do it
BOOL bRet = Shell_NotifyIcon(msg, &m_nid);
if (msg==NIM_DELETE || !bRet)
m_nid.hIcon = NULL; // failed
return bRet;
}
//////////////////
// Same hook class used for both notification window and top-level
// parent; hook function determines which.
//
LRESULT CTrayIcon::CTrayHook::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
if (msg==m_pTrayIcon->m_nid.uCallbackMessage &&
m_hWnd==m_pTrayIcon->m_nid.hWnd) {
m_pTrayIcon->OnTrayNotify(wp, lp);
} else if (msg==WM_TASKBARCREATED) {
m_pTrayIcon->OnTaskBarCreate(wp, lp);
}
return CSubclassWnd::WindowProc(msg, wp, lp);
}
/////////////////
// Default event handler handles right-menu and doubleclick.
// Override to do something different.
//
LRESULT CTrayIcon::OnTrayNotify(WPARAM wID, LPARAM lEvent)
{
if (wID!=m_nid.uID ||
(lEvent!=WM_RBUTTONUP && lEvent!=WM_LBUTTONDBLCLK))
return 0;
// If there's a resource menu with the same ID as the icon, use it as
// the right-button popup menu. CTrayIcon will interpret the first
// item in the menu as the default command for WM_LBUTTONDBLCLK
//
CMenu menu;
if (!menu.LoadMenu(m_nid.uID))
return 0;
CMenu* pSubMenu = menu.GetSubMenu(0);
if (!pSubMenu)
return 0;
if (lEvent==WM_RBUTTONUP) {
// Make first menu item the default (bold font)
::SetMenuDefaultItem(pSubMenu->m_hMenu, 0, TRUE);
// Display the menu at the current mouse location. There's a "bug"
// in Windows 95 that requires calling SetForegroundWindow.
// To find out more, search for Q135788 in MSDN.
//
CPoint mouse;
GetCursorPos(&mouse);
::SetForegroundWindow(m_nid.hWnd);
::TrackPopupMenu(pSubMenu->m_hMenu, 0, mouse.x, mouse.y, 0,
m_nid.hWnd, NULL);
} else // double click: execute first menu item
::SendMessage(m_nid.hWnd, WM_COMMAND, pSubMenu->GetMenuItemID(0), 0);
return 1; // handled
}
//////////////////
// Explorer had to restart the taskbar: add icons again
//
LRESULT CTrayIcon::OnTaskBarCreate(WPARAM wp, LPARAM lp)
{
// Reinstall taskbar icon
HICON hIcon = m_nid.hIcon;
m_nid.hIcon = NULL;
if (hIcon)
SetIcon(hIcon, NULL); // will reuse current tip
return 0;
}
Figure 6 CMenuBar
MenuBar.h
// 1998 Paul DiLascia
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// CMenuBar uses this private class to intercept messages on behalf
// of its owning frame.
//
class CMenuBarFrameHook : public CSubclassWnd {
protected:
friend class CMenuBar;
CMenuBar* m_pMenuBar;
CMenuBarFrameHook();
~CMenuBarFrameHook();
BOOL Install(CMenuBar* pMenuBar, HWND hWndToHook);
virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp);
};
// CMenuBar implements an Office 97-style menu bar. Use it the way you would
// a CToolBar, only you need not call LoadToolBar. All you have to do is
// * Create the CMenuBar from your OnCreate or OnCreateBands handler.
// * Call LoadMenu to load a menu. This will set your frame's menu to NULL.
// * Implemenent your frame's PreTranslateMessage function, to call
// CMenuBar::TranslateFrameMessage.
//
class CMenuBar : public CFlatToolBar {
public:
BOOL m_bAutoRemoveFrameMenu; // set frame's menu to NULL
CMenuBar();
~CMenuBar() { }
// You must call this from your frame's PreTranslateMessage fn
virtual BOOL TranslateFrameMessage(MSG* pMsg);
HMENU LoadMenu(HMENU hmenu, HMENU hmenuWindow); // load menu
HMENU LoadMenu(LPCSTR lpszMenuName); // ...from resource file
HMENU LoadMenu(UINT nID) {
return LoadMenu(MAKEINTRESOURCE(nID));
}
HMENU GetMenu() { return m_hmenu; } // get current menu
enum TRACKINGSTATE { // menubar has three states:
TRACK_NONE = 0, // * normal, not tracking anything
TRACK_BUTTON, // * tracking buttons (F10/Alt mode)
TRACK_POPUP // * tracking popups
};
static BOOL bTRACE; // set TRUE to see TRACE msgs
protected:
CMenuBarFrameHook m_frameHook; // hooks frame window messages
CStringArray m_arStrings; // array of menu item names
HMENU m_hmenu; // the menu
// menu tracking stuff:
int m_iPopupTracking; // which popup I'm tracking if any
int m_iNewPopup; // next menu to track
BOOL m_bProcessRightArrow; // process l/r arrow keys?
BOOL m_bProcessLeftArrow; // ...
BOOL m_bEscapeWasPressed; // user pressed escape to exit menu
CPoint m_ptMouse; // mouse location when tracking popup
HMENU m_hMenuTracking; // current popup I'm tracking
TRACKINGSTATE m_iTrackingState; // current tracking state
// helpers
void RecomputeToolbarSize();
void RecomputeMenuLayout();
int GetNextOrPrevButton(int iButton, BOOL bPrev);
void SetTrackingState(TRACKINGSTATE iState, int iButton=-1);
void TrackPopup(int iButton);
void ToggleTrackButtonMode();
void CancelMenuAndTrackNewOne(int iButton);
void OnMenuSelect(HMENU hmenu, UINT nItemID);
CPoint ComputeMenuTrackPoint(const CRect& rcButn, TPMPARAMS& tpm);
BOOL IsValidButton(int iButton) const
{ return 0 <= iButton && iButton < GetButtonCount(); }
virtual BOOL OnMenuInput(MSG& m); // handle popup menu input
// overrides
int HitTest(CPoint p) const;
// command/message handlers
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint pt);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnUpdateMenuButton(CCmdUI* pCmdUI);
afx_msg LRESULT OnSetMenuNull(WPARAM wp, LPARAM lp);
static LRESULT CALLBACK MenuInputFilter(int code, WPARAM wp, LPARAM lp);
DECLARE_DYNAMIC(CMenuBar)
DECLARE_MESSAGE_MAP()
};
MenuBar.cpp
// CMenuBar implements menu bar for MFC. See MenuBar.h for how
// to use, and also the MBTest sample application.
//
#include "StdAfx.h"
#include "MenuBar.h"
const UINT MB_SET_MENU_NULL = WM_USER + 1100;
IMPLEMENT_DYNAMIC(CMenuBar, CFlatToolBar)
BEGIN_MESSAGE_MAP(CMenuBar, CFlatToolBar)
ON_WM_CREATE()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
ON_WM_SIZE()
ON_UPDATE_COMMAND_UI_RANGE(0, 256, OnUpdateMenuButton)
ON_MESSAGE(MB_SET_MENU_NULL, OnSetMenuNull)
END_MESSAGE_MAP()
CMenuBar::CMenuBar()
{
m_iTrackingState = TRACK_NONE; // initial state: not tracking
m_iPopupTracking = m_iNewPopup = -1; // invalid
m_hmenu = NULL;
m_bAutoRemoveFrameMenu = TRUE; // set frame's menu to NULL
}
int CMenuBar::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
VERIFY(CFlatToolBar::OnCreate(lpCreateStruct)==0);
CWnd* pFrame = GetOwner();
m_frameHook.Install(this, pFrame->GetSafeHwnd());
return 0; // OK
}
// The reason for having this is so MFC won't automatically disable
// the menu buttons. Assumes < 256 top-level menu items. The ID of
// the ith menu button is i. IOW, the index and ID are the same.
//
void CMenuBar::OnUpdateMenuButton(CCmdUI* pCmdUI)
{
ASSERT_VALID(this);
if (IsValidButton(pCmdUI->m_nID))
pCmdUI->Enable(TRUE);
}
// Recompute layout of menu bar
//
void CMenuBar::RecomputeMenuLayout()
{
SetWindowPos(NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOACTIVATE |
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
}
// Make frame recalculate control bar sizes after menu change
//
void CMenuBar::RecomputeToolbarSize()
{
// Force toolbar to recompute size
CFrameWnd* pFrame = (CFrameWnd*)GetOwner();
ASSERT_VALID(pFrame);
ASSERT(pFrame->IsFrameWnd());
pFrame->RecalcLayout();
// floating frame
pFrame = GetParentFrame();
if (pFrame->IsKindOf(RUNTIME_CLASS(CMiniFrameWnd)))
pFrame->RecalcLayout();
}
// Set tracking state: none, button, or popup
//
void CMenuBar::SetTrackingState(TRACKINGSTATE iState, int iButton)
{
ASSERT_VALID(this);
if (iState != m_iTrackingState) {
if (iState == TRACK_NONE)
iButton = -1;
SetHotItem(iButton); // could be none (-1)
if (iState==TRACK_POPUP) {
// set related state stuff
m_bEscapeWasPressed = FALSE; // assume Esc key not pressed
m_bProcessRightArrow = // assume left/right arrow..
m_bProcessLeftArrow = TRUE; // ..will move to prev/next popup
m_iPopupTracking = iButton; // which popup I'm tracking
}
m_iTrackingState = iState;
}
}
// Toggle state from home state to button-tracking and back
//
void CMenuBar::ToggleTrackButtonMode()
{
ASSERT_VALID(this);
if (m_iTrackingState == TRACK_NONE || m_iTrackingState == TRACK_BUTTON) {
SetTrackingState(m_iTrackingState == TRACK_NONE ?
TRACK_BUTTON : TRACK_NONE, 0);
}
}
// Get button index before/after a given button
//
int CMenuBar::GetNextOrPrevButton(int iButton, BOOL bPrev)
{
ASSERT_VALID(this);
if (bPrev) {
iButton--;
if (iButton <0)
iButton = GetButtonCount() - 1;
} else {
iButton++;
if (iButton >= GetButtonCount())
iButton = 0;
}
return iButton;
}
// This is to correct a bug in the system toolbar control: TB_HITTEST only
// looks at the buttons, not the size of the window. So it returns a button
// hit even if that button is totally outside the size of the window!
//
int CMenuBar::HitTest(CPoint p) const
{
int iHit = CFlatToolBar::HitTest(p);
if (iHit>0) {
CRect rc;
GetClientRect(&rc);
if (!rc.PtInRect(p)) // if point is outside window
iHit = -1; // can't be a hit!
}
return iHit;
}
// Load a different menu. The HMENU must not belong to any CMenu,
// and you must free it when you're done. Returns old menu
// hMenuWindow is MDI "Window" menu, if any
//
HMENU CMenuBar::LoadMenu(HMENU hmenu, HMENU hmenuWindow)
{
MBTRACEFN(_T("CMenuBar::LoadMenu\n"));
UINT iPrevID=(UINT)-1;
ASSERT(::IsMenu(hmenu));
ASSERT_VALID(this);
if (m_bAutoRemoveFrameMenu) {
CFrameWnd* pFrame = GetParentFrame();
if (::GetMenu(*pFrame)!=NULL) {
// I would like to set the frame's menu to NULL now, but if I do, MFC
// gets all upset: it calls GetMenu and expects to have a real menu.
// So Instead, I post a message to myself. Because the message is
// posted, not sent, I won't process it until MFC is done with all its
// initialization stuff. (MFC needs to set CFrameWnd::m_hMenuDefault
// to the menu, which it gets by calling GetMenu.)
//
PostMessage(MB_SET_MENU_NULL, (WPARAM)pFrame->GetSafeHwnd());
}
}
HMENU hOldMenu = m_hmenu;
m_hmenu = hmenu;
// delete existing buttons
int nCount = GetButtonCount();
while (nCount--) {
VERIFY(DeleteButton(0));
}
SetImageList(NULL);
// SetButtonSize(CSize(0,0)); // This barfs in VC 6.0
DWORD dwStyle = GetStyle();
BOOL bModifyStyle = ModifyStyle(0, TBSTYLE_FLAT|TBSTYLE_TRANSPARENT);
// add text buttons
UINT nMenuItems = hmenu ? ::GetMenuItemCount(hmenu) : 0;
for (UINT i=0; i < nMenuItems; i++) {
TCHAR name[64];
memset(name, 0, sizeof(name)); // guarantees double-0 at end
if (::GetMenuString(hmenu, i, name, countof(name)-1, MF_BYPOSITION)) {
TBBUTTON tbb;
memset(&tbb, 0, sizeof(tbb));
tbb.idCommand = ::GetMenuItemID(hmenu, i);
// Because the toolbar is too brain-damaged to know if it already has
// a string, and is also too brain-dead to even let you delete strings,
// I have to determine if each string has been added already. Otherwise
// in a MDI app, as the menus are repeatedly switched between doc and
// no-doc menus, I will keep adding strings until somebody runs out of
// memory. Sheesh!
//
int iString = -1;
for (int j=0; j<m_arStrings.GetSize(); j++) {
if (m_arStrings[j] == name) {
iString = j; // found it
break;
}
}
if (iString <0) {
// string not found: add it
iString = AddStrings(name);
m_arStrings.SetAtGrow(iString, name);
}
tbb.iString = iString;
tbb.fsState = TBSTATE_ENABLED;
tbb.fsStyle = TBSTYLE_AUTOSIZE;
tbb.iBitmap = -1;
tbb.idCommand = i;
VERIFY(AddButtons(1, &tbb));
}
}
if (bModifyStyle)
SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);
if (hmenu) {
AutoSize(); // size buttons
RecomputeToolbarSize(); // and menubar itself
}
return hOldMenu;
}
// Load menu from resource
//
HMENU CMenuBar::LoadMenu(LPCSTR lpszMenuName)
{
return LoadMenu(::LoadMenu(AfxGetResourceHandle(), lpszMenuName), NULL);
}
// Set the frame's menu to NULL. WPARAM is HWND of frame.
//
LRESULT CMenuBar::OnSetMenuNull(WPARAM wp, LPARAM lp)
{
HWND hwnd = (HWND)wp;
ASSERT(::IsWindow(hwnd));
::SetMenu(hwnd, NULL);
return 0;
}
// Handle mouse click: if clicked on button, press it
// and go into main menu loop.
//
void CMenuBar::OnLButtonDown(UINT nFlags, CPoint pt)
{
MBTRACEFN(_T("CMenuBar::OnLButtonDown\n"));
ASSERT_VALID(this);
int iButton = HitTest(pt);
if (iButton >= 0 && iButton<GetButtonCount()) // if mouse is over a button:
TrackPopup(iButton); // track it
else // otherwise:
CFlatToolBar::OnLButtonDown(nFlags, pt); // pass it on...
}
void CMenuBar::OnLButtonUp(UINT nFlags, CPoint pt)
{
}
// Handle mouse movement
//
void CMenuBar::OnMouseMove(UINT nFlags, CPoint pt)
{
ASSERT_VALID(this);
if (m_iTrackingState==TRACK_BUTTON) {
// In button-tracking state, ignore mouse-over to non-button area.
// Normally, the toolbar would de-select the hot item in this case.
// Only change the hot item if the mouse has actually moved.
// This is necessary to avoid a bug where the user moves to a different
// button from the one the mouse is over, and presses arrow-down to get
// the menu, then Esc to cancel it. Without this code, the button will
// jump to wherever the mouse is--not right.
int iHot = HitTest(pt);
if (IsValidButton(iHot) && pt != m_ptMouse)
SetHotItem(iHot);
return; // don't let toolbar get it
}
m_ptMouse = pt; // remember point
CFlatToolBar::OnMouseMove(nFlags, pt);
}
// Window was resized: need to recompute layout
//
void CMenuBar::OnSize(UINT nType, int cx, int cy)
{
CFlatToolBar::OnSize(nType, cx, cy);
RecomputeMenuLayout();
}
// when user selects a new menu item, note whether it has a submenu
// and/or parent menu, so I know whether right/left arrow should
// move to the next popup.
//
void CMenuBar::OnMenuSelect(HMENU hmenu, UINT iItem)
{
if (m_iTrackingState > 0) {
// process right-arrow iff item is NOT a submenu
m_bProcessRightArrow = (::GetSubMenu(hmenu, iItem) == NULL);
// process left-arrow iff curent menu is one I'm tracking
m_bProcessLeftArrow = hmenu==m_hMenuTracking;
}
}
// globals--yuk! But no other way using windows hooks.
//
static CMenuBar* g_pMenuBar = NULL;
static HHOOK g_hMsgHook = NULL;
// Menu filter hook just passes to virtual CMenuBar function
//
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);
}
// Handle menu input event: Look for left/right to change popup menu,
// mouse movement over a different menu button for "hot" popup effect.
// Returns TRUE if message handled (to eat it).
//
BOOL CMenuBar::OnMenuInput(MSG& m)
{
ASSERT_VALID(this);
ASSERT(m_iTrackingState == TRACK_POPUP); // sanity check
int msg = m.message;
if (msg==WM_KEYDOWN) {
// handle left/right-arow.
TCHAR vkey = m.wParam;
if ((vkey == VK_LEFT && m_bProcessLeftArrow) ||
(vkey == VK_RIGHT && m_bProcessRightArrow)) {
MBTRACE(_T("CMenuBar::OnMenuInput: handle VK_LEFT/RIGHT\n"));
CancelMenuAndTrackNewOne(
GetNextOrPrevButton(m_iPopupTracking, vkey==VK_LEFT));
return TRUE; // eat it
} else if (vkey == VK_ESCAPE) {
m_bEscapeWasPressed = TRUE; // (menu will abort itself)
}
} else if (msg==WM_MOUSEMOVE || msg==WM_LBUTTONDOWN) {
// handle mouse move or click
CPoint pt = m.lParam;
ScreenToClient(&pt);
if (msg == WM_MOUSEMOVE) {
if (pt != m_ptMouse) {
int iButton = HitTest(pt);
if (IsValidButton(iButton) && iButton != m_iPopupTracking) {
// user moved mouse over a different button: track its popup
CancelMenuAndTrackNewOne(iButton);
}
m_ptMouse = pt;
}
} else if (msg == WM_LBUTTONDOWN) {
if (HitTest(pt) == m_iPopupTracking) {
// user clicked on same button I am tracking: cancel menu
MBTRACE(_T("CMenuBar:OnMenuInput:
handle mouse click to exit popup\n"));
CancelMenuAndTrackNewOne(-1);
return TRUE; // eat it
}
}
}
return FALSE; // not handled
}
// Cancel the current popup menu by posting WM_CANCELMODE, and track a new
// menu. iNewPopup is which new popup to track (-1 to quit).
//
void CMenuBar::CancelMenuAndTrackNewOne(int iNewPopup)
{
MBTRACE(_T("CMenuBar::CancelMenuAndTrackNewOne: %d\n"), iNewPopup);
ASSERT_VALID(this);
if (iNewPopup != m_iPopupTracking) {
GetOwner()->PostMessage(WM_CANCELMODE); // quit menu loop
m_iNewPopup = iNewPopup; // go to this popup (-1 = quit)
}
}
// Track the popup submenu associated with the i'th button in the menu bar.
// This fn actually goes into a loop, tracking different menus until the user
// selects a command or exits the menu.
//
void CMenuBar::TrackPopup(int iButton)
{
MBTRACE(_T("CMenuBar::TrackPopup %d\n"), iButton);
ASSERT_VALID(this);
ASSERT(m_hmenu);
CMenu menu;
menu.Attach(m_hmenu);
int nMenuItems = menu.GetMenuItemCount();
while (iButton >= 0) { // while user selects another menu
m_iNewPopup = -1; // assume quit after this
PressButton(iButton, TRUE); // press the button
UpdateWindow(); // and force repaint now
// post a simulated arrow-down into the message stream
// so TrackPopupMenu will read it and move to the first item
GetOwner()->PostMessage(WM_KEYDOWN, VK_DOWN, 1);
GetOwner()->PostMessage(WM_KEYUP, VK_DOWN, 1);
SetTrackingState(TRACK_POPUP, iButton); // enter tracking state
// Need to install a hook to trap menu input in order to make
// left/right-arrow keys and "hot" mouse tracking work.
//
ASSERT(g_pMenuBar == NULL);
g_pMenuBar = this;
ASSERT(g_hMsgHook == NULL);
g_hMsgHook = SetWindowsHookEx(WH_MSGFILTER,
MenuInputFilter, NULL, ::GetCurrentThreadId());
// get submenu and display it beneath button
TPMPARAMS tpm;
CRect rcButton;
GetRect(iButton, rcButton);
ClientToScreen(&rcButton);
CPoint pt = ComputeMenuTrackPoint(rcButton, tpm);
HMENU hMenuPopup = ::GetSubMenu(m_hmenu, iButton);
ASSERT(hMenuPopup);
BOOL bRet = TrackPopupMenuEx(hMenuPopup,
TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_VERTICAL,
pt.x, pt.y, GetOwner()->GetSafeHwnd(), &tpm);
// uninstall hook, etc.
::UnhookWindowsHookEx(g_hMsgHook);
g_hMsgHook = NULL;
g_pMenuBar = NULL;
PressButton(iButton, FALSE); // un-press button
UpdateWindow(); // and force repaint now
// If the user exited the menu loop by pressing Escape,
// return to track-button state; otherwise normal non-tracking state.
SetTrackingState(m_bEscapeWasPressed ?
TRACK_BUTTON : TRACK_NONE, iButton);
// If the user moved mouse to a new top-level popup (eg from File to
// Edit button), I will have posted a WM_CANCELMODE to quit
// the first popup, and set m_iNewPopup to the new menu to show.
// Otherwise, m_iNewPopup will be -1 as set above.
// So just set iButton to the next popup menu and keep looping...
iButton = m_iNewPopup;
}
menu.Detach();
}
// This function translates special menu keys and mouse actions.
// You must call it from your frame's PreTranslateMessage.
//
BOOL CMenuBar::TranslateFrameMessage(MSG* pMsg)
{
ASSERT_VALID(this);
ASSERT(pMsg);
UINT msg = pMsg->message;
if (WM_LBUTTONDOWN <= msg && msg <= WM_MOUSELAST) {
if (pMsg->hwnd != m_hWnd && m_iTrackingState > 0) {
// user clicked outside menu bar: exit tracking mode
MBTRACE(_T("CMenuBar::TranslateFrameMessage:
user clicked outside menu bar: end tracking\n"));
SetTrackingState(TRACK_NONE);
}
} else if (msg==WM_SYSKEYDOWN || msg==WM_SYSKEYUP || msg==WM_KEYDOWN) {
BOOL bAlt = HIWORD(pMsg->lParam) & KF_ALTDOWN; // Alt key down
TCHAR vkey = pMsg->wParam; // get virt key
if (vkey==VK_MENU ||
(vkey==VK_F10 && !((GetKeyState(VK_SHIFT) & 0x80000000) ||
(GetKeyState(VK_CONTROL) & 0x80000000) || bAlt))) {
// key is VK_MENU or F10 with no alt/ctrl/shift: toggle menu mode
if (msg==WM_SYSKEYUP) {
MBTRACE(_T("CMenuBar::TranslateFrameMessage: handle menu key\n"));
ToggleTrackButtonMode();
}
return TRUE;
} else if ((msg==WM_SYSKEYDOWN || msg==WM_KEYDOWN)) {
if (m_iTrackingState == TRACK_BUTTON) {
// I am tracking: handle left/right/up/down/space/Esc
switch (vkey) {
case VK_LEFT:
case VK_RIGHT:
// left or right-arrow: change hot button if tracking buttons
MBTRACE(_T("CMenuBar::TranslateFrameMessage: VK_LEFT/RIGHT\n"));
SetHotItem(GetNextOrPrevButton(GetHotItem(), vkey==VK_LEFT));
return TRUE;
case VK_SPACE: // (personally, I like SPACE to enter menu too)
case VK_UP:
case VK_DOWN:
// up or down-arrow: move into current menu, if any
MBTRACE(_T("CMenuBar::TranslateFrameMessage:
VK_UP/DOWN/SPACE\n"));
TrackPopup(GetHotItem());
return TRUE;
case VK_ESCAPE:
// escape key: exit tracking mode
MBTRACE(_T("CMenuBar::TranslateFrameMessage: VK_ESCAPE\n"));
SetTrackingState(TRACK_NONE);
return TRUE;
}
}
// Handle alphanumeric key: invoke menu. Note that Alt-X
// chars come through as WM_SYSKEYDOWN, plain X as WM_KEYDOWN.
if ((bAlt || m_iTrackingState == TRACK_BUTTON) && isalnum(vkey)) {
// Alt-X, or else X while in tracking mode
UINT nID;
if (MapAccelerator(vkey, nID)) {
MBTRACE(_T("CMenuBar::TranslateFrameMessage: map acclerator\n"));
TrackPopup(nID); // found menu mnemonic: track it
return TRUE; // handled
} else if (m_iTrackingState==TRACK_BUTTON && !bAlt) {
MessageBeep(0);
return TRUE;
}
}
// Default for any key not handled so far: return to no-menu state
if (m_iTrackingState > 0) {
MBTRACE(_T("CMenuBar::TranslateFrameMessage: unknown key,
stop tracking\n"));
SetTrackingState(TRACK_NONE);
}
}
}
return FALSE; // not handled, pass along
}
// CMenuBarFrameHook is used to trap menu-related messages sent to the owning
// frame. The same class is also used to trap messages sent to the MDI client
// window in an MDI app. I should really use two classes for this,
// but it uses less code to char the same class. Note however: there
// are two different INSTANCES of CMenuBarFrameHook in CMenuBar: one for
// the frame and one for the MDI client window.
//
CMenuBarFrameHook::CMenuBarFrameHook()
{
}
CMenuBarFrameHook::~CMenuBarFrameHook()
{
HookWindow((HWND)NULL); // (unhook)
}
// Install hook to trap window messages sent to frame or MDI client.
//
BOOL CMenuBarFrameHook::Install(CMenuBar* pMenuBar, HWND hWndToHook)
{
ASSERT_VALID(pMenuBar);
m_pMenuBar = pMenuBar;
return HookWindow(hWndToHook);
}
// Trap frame/MDI client messages specific to menubar.
//
LRESULT CMenuBarFrameHook::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
{
CMenuBar& mb = *m_pMenuBar;
switch (msg) {
case WM_MENUSELECT:
mb.OnMenuSelect((HMENU)lp, (UINT)LOWORD(wp));
break;
}
return CSubclassWnd::WindowProc(msg, wp, lp);
}