The source code for this segment is available at http://www.cavinconsulting.com/Code/Scribble3.zip.
OK. Last time we took a perfectly understandable (though difficult to localize) version of a menu and toolbar with mnemonics but without shortcuts and turned it into a more complex, easier to localize version of a menu and toolbar without mnemonics but with shortcuts.
Mnemonics (in the old MFC speak) are the keys you press to activate a menu item from the keyboard once a menu has focus. Shortcuts are the key sequence you press to activate a menu item, even if the menu is not active. For example, Ctrl+s is the shortcut usually corresponding to Save. However, if the File menu is open (dropped down), then the user can just hit the s key. The mnemonic for a menu item is usually a letter in the item that is underlined. I say usually because new versions of windows and frameworks have stopped underlining mnemonics unless you strategically press the Alt key at some point. I haven't figured out what triggers the visibility of the underlines. I just know when I open the File menu with Alt+f, the mnemonics are displayed as underlined characters. When I open the File menu by clicking on it, the mnemonics are not displayed.
Anyway, that is not the point of this post (mainly because I haven't figured out how to fix it yet). Instead, the point of this post is to make the Exit menu look like the rest of the menu items. That is, our current implementation of the file menu looks like this:
<MenuItem Header="_File"> <MenuItem Command="ApplicationCommands.New" /> <MenuItem Command="ApplicationCommands.Open" /> <MenuItem Command="ApplicationCommands.Close" /> <MenuItem Command="ApplicationCommands.Save" /> <MenuItem Command="ApplicationCommands.SaveAs" /> <Separator /> <MenuItem Header="E_xit" InputGestureText="Alt-F4" Click="OnFileExit" ToolTip="Click here to exit" Name="fileExit"/> </MenuItem>
I'd like it to look more like this:
<MenuItem Header="_File"> <MenuItem Command="ApplicationCommands.New" /> <MenuItem Command="ApplicationCommands.Open" /> <MenuItem Command="ApplicationCommands.Close" /> <MenuItem Command="ApplicationCommands.Save" /> <MenuItem Command="ApplicationCommands.SaveAs" /> <Separator /> <MenuItem Command="ScribbleCommands.Exit" /> </MenuItem>
See the difference? Correct. The second implementation is more consistent. Consistency, of course, implies readability and maintainability. At least, that's what I've read a few places, and who am I to contradict?
First, a little about commands. WPF gives us a bunch of commands to use out-of-the-box. Some of them are in the ApplicationCommands class, but there are others as well. None of the built-in commands handles application Exit. I'm not sure why that is. That's okay, because it gives us the opportunity to learn about writing custom commands. There is a pretty comprehensive discussion about the Command Pattern and its use in WPF here. I'll wait while you read it. Done? Good. Now, in order to allow us to implement our Exit command, we will need to create something that implements ICommand and put that in between the menu item and the code to be executed.
First, let's create the command class. We'll do this by creating a new class containing our custom commands. Create a new class named ScribbleCommands. This class should look like this:
public static class ScribbleCommands { private static object syncRoot = new object(); private static RoutedUICommand exit = null; public static RoutedUICommand Exit { get { lock (syncRoot) { if (null == exit) { exit = new RoutedUICommand("Exit", "Exit", typeof(ScribbleCommands)); exit.InputGestures.Add(new KeyGesture(Key.Q, ModifierKeys.Control)); } } return exit; } } }
Notice that this class exposes a static read-only property for the commands we are allowed to use -- everything else is private. Also, since WPF is inherently multi-threaded, we lock the null check and creation section so we don't accidentally return two different instances of the command object. Finally, the two hard-coded "Exit" strings should be read from the application Resources in order to make this more easily localizable. After all, that was one of the reasons we started adding these command things in the first place. This has been implemented with a Resource lookup in the attached source code.
Next, we need to hook our new command up to the Exit menu. Unfortunately, here is where things get more complicated. Sure, it was easy to attach the built-in framework commands to a button or menu. Connecting your own, however, requires a little more work. First, we have to add a reference to our own namespace in window1.xaml. I agree, this seems a bit ridiculous, but open up window1.xaml and add this line to the Window tag at the top:
xmlns:scribble="clr-namespace:Scribble"
This tells the compiler that we can use the scribble xml namespace to designate types in our own assembly. Unfortunately, it has the side-effect of breaking the VS designer. You'll now get the big Whoops! window any time you click in the designer. Bummer.
Next, we have to make the MenuItem point to our Exit command. To do this, change the Exit MenuItem definition to this:
<MenuItem Header="Exit" Command="{x:Static scribble:ScribbleCommands.Exit}" />
The funny {x: syntax is required to let the compiler know that we are referencing things in a non-framework location. I'm sure there is more to it than that, but for our purposes, the description is adequate.
Finally, we need to tell the application where to route the Exit event. To do this, add the following line to the Window.CommandBindings list:
<CommandBinding Command="{x:Static scribble:ScribbleCommands.Exit}" Executed="OnFileExit" CanExecute="CanAlwaysExecute"/>
<CommandBinding Command="{x:Static scribble:ScribbleCommands.Exit}"
Executed="OnFileExit" CanExecute="CanAlwaysExecute"/>
There you go. Even though the designer is broken, the application compiles and executes as expected. It's a real bummer about the fragility of the designer, however. I guess that's why it's still a CTP and not a released product.
A couple of things to note here: 1) I still haven't figured out how to add mnemonics to the commands. Adding the '_' character to the command text does not work. 2) Adding references custom classes can be a painful process. With the buggy support in the VS designer, you never know until you run the application if the you have the references correct. If you have them wrong, you get to crawl through a bunch of InnerExceptions until you find what class failed to load and why.
What's next? How about actually adding some code to the event handlers and finally getting a "complete" application?
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.