Sunday, December 06, 2009

An alternative way to create the menu bar

I don't like resources. I really don't. I can understand their usefulness for features such as string tables or embedded icons, but when it comes to the Windows Mobile menu bar definition, I just hate the whole thing. Take a good look at how the CrypSafe sample app's menu bar is defined:

ATL_IDW_MENU_BAR SHMENUBAR DISCARDABLE
BEGIN
IDR_MAINFRAME,
2,
I_IMAGENONE, ID_ACTION, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE,
ID_ACTION, 0, NOMENU,
I_IMAGENONE, ID_MENU, TBSTATE_ENABLED, TBSTYLE_DROPDOWN TBSTYLE_AUTOSIZE,
ID_MENU, 0, 0,
END
Intuitive, right? Incidentally, this resource is defined in the .rc2 file and the ATL_IDW_MENU_BAR identifier is used as a default parameter of the CreateSimpleCEMenuBar method of the CFrameWindowImplBase class template. So what do you have to do in order to change the menu bar? Gasp!

There is a better way, fortunately. A way that you can encapsulate and that makes the whole process a bit more transparent.

You create a Windows Mobile menu bar by calling the SHCreateMenuBar API typically from your main window's WM_CREATE handler. As you can see the function takes a single parameter, a pointer to a SHMENUBARINFO structure where you define the menu bar. There are two ways to create a menu bar: you either specify a menu bar resource ID or you provide your own HMENU by specifying the SHCMBF_HMENU flag. This is in fact a much more palatable alternative because you can declaratively create your menu bar in a single location of your application's code. Using my beloved WTL, here's how the code would look like on the main frame's OnCreate handler:

if(m_mainMenu.CreateMenu())
{
m_mainMenu.AppendMenu(MF_BYCOMMAND MF_ENABLED MF_STRING,
ID_BACK, _T("Back"));
m_mainMenu.AppendMenu(MF_BYCOMMAND MF_ENABLED MF_STRING,
ID_MENU, _T("Menu"));

SHMENUBARINFO mbi = { 0 };
mbi.cbSize = sizeof(mbi);
mbi.hwndParent = m_hWnd;
mbi.dwFlags = SHCMBF_HMENU;
mbi.nToolBarId = (UINT)(HMENU)m_mainMenu;
mbi.hInstRes = ModuleHelper::GetResourceInstance();
mbi.nBmpId = 0;
mbi.cBmpImages = 0;
mbi.hwndMB = NULL;

BOOL bRet = ::SHCreateMenuBar(&mbi);
if(bRet != FALSE)
{
m_hWndCECommandBar = mbi.hwndMB;
SizeToMenuBar();
}
}
The m_mainMenu variable is a CMenu, of course. The advantage of this approach is that you create the main menu bar in a very explicit way and if you later want to change it you know where to find the code, and stop guessing what that crappy SHMENUBAR resource declaration means...

Tuesday, December 01, 2009

A simple header button

Now that we have a header for the application, what about adding some useful stuff to it, like buttons? The picture on the left shows the second version of the touch header in action with a "back" button that implements the same functionality as the menu bar's "Back" menu. This button should only be visible when there is somewhere to go back to, so we should be able to add it and remove it as we please.

For now, the header button is a very simple object containing up to 3 CImage objects (for the normal, depressed and disabled states). This is not a regular Windows button but instead a "sensitive area" of the header that just behaves like a button (just like what happens with regular toolbars).

The button itself is implemented in the CTouchToolbarItem class (see sample code below). It derives from CRefCount so you can use the CRefPtr smart pointer class template to manage it and forget about deleting... The class contains three CImage instances, one for each possible state (these will later be changed to smart pointers as well in order to allow for better reuse).

Buttons are set through the virtual SetupTouchHeader method in each of the child views. Instead of just setting the header text, you can now set up to two buttons (left with index zero and right with index one). The button activation messaging is very similar to a regular menu: the WM_COMMAND message is sent to the parent window (the main frame) with the command ID as the wParam parameter.

A final thought: the header belongs to the frame window and is set up by the child view when they are shown. Is this a good design? I'm wondering if these should "belong" to the child view instead and be replaced along with it, possibly with some fancy animation. What do you think?

Sample code: CrypSafe08.zip (406 KB)