Sign in to follow this  
  • entries
  • comments
  • views

Mode context menus and accelerators

Sign in to follow this  


I'm a fan of menus and accelerators (keyboard shortcuts) that depend on the context. I like to edit with one hand on the keyboard, and one hand on the mouse. However, I don't always remember what shortcuts are applicable in which mode, so I like the menu to show the shortcuts.

To implement menus and shortcuts, I reserved the second position in the window main menu for a mode-specific edit menu.

I added 2 variables to the VzEditMode class (from which all mode classes inherit):

VzEditMode() : hAccel(nullptr), bEditMenuSet(false) {}...HACCEL GetAccelerator() { return hAccel; }...bool bEditMenuSet;HACCEL hAccel;

I also added 2 support functions to the app manager:

bool VzAppManager::SetEditMenu(std::vector &menuStrings, WORD startMenuId)
HACCEL VzAppManager::CreateAccelerator(std::vector modeTable)

and created a VzAcceleratorFactory class, which implements:

HACCEL VzAcceleratorFactory::CreateAccelerator(std::vector userTable)

The SetEditMenu function attempts to create an HMENU from the menu strings, assigning command IDs serially starting with startMenuId. Then, if the window main menu has an Edit menu in position 2, that "old" menu is removed and destroyed. The new mode Edit menu is then inserted into the main window menu at position 1.

The CreateAccelerator function takes a std::vector of ACCEL structures, calls the accelerator factory to create a new accelerator object which, by default, includes the persistent accelerators to which the mode accelerators are added:


When a new mode is set, the mode class has the option (in its Init call) to setup parameters for the Edit menu and/or accelerator table. E.g., the VzModeObject class sets up the menu and accelerators like so:

bool VzModeObject::SetModeMenuAndAccelerators(){ std::vector menuStrings; if (nullptr != mesh) // is there a mesh to edit? { std::wstring str = L"&Verts\tV"; menuStrings.push_back(str); str = L"&Faces\tF"; menuStrings.push_back(str); } if (!mgr->SetEditMenu(menuStrings, IDM_EDIT_MENUSTART)) return false; if (nullptr != mesh) { std::vector accelTable; ACCEL modeAccel[] = { 0, WORD('v'), IDM_EDIT_MENUSTART, 0, WORD('V'), IDM_EDIT_MENUSTART, 0, WORD('f'), IDM_EDIT_MENUSTART + 1, 0, WORD('F'), IDM_EDIT_MENUSTART + 1, }; size_t numEntries = sizeof(modeAccel) / sizeof(ACCEL); for (size_t n = 0; n < numEntries; ++n) { accelTable.push_back(modeAccel[n]); } hAccel = mgr->CreateAccelerator(accelTable); } return true;}

The defaults if the new mode does not create a new Edit menu is just to remove the Edit menu from the main window. If no keyboard shortcuts are specified, the accelerator table is set to just the defaults shown above.

Then, in the run loop:

while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { //if (!TranslateAccelerator(msg.hwnd, hAccel, &msg)) if (!TranslateAccelerator(msg.hwnd, appMgr->GetCurMode()->GetAccelerator(), &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }

With the menu and accelerators set for each mode, things are becoming more context specific. I'm thinking the next step is for each mode to have a window message procedure to process mode specific commands. When a new mode is set, the main window GWL_USERDATA will be set to a pointer to VzEditMode, and the derived mode's MsgProc will process commands of interest (whether menu items or keyboard shortcuts), call the base class MsgProc to let generic mode responses be processed, and return through the app manager's MsgProc (if one is needed any more).
Sign in to follow this  


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now