Context Menus, Events and the Keyboard
I just thought I share with you another example of using an event in the current application I am developing. As I mentioned in the previous post, I like to develop GUI applications that can be entirely managed by the keyboard, especially for data entry applications.
In this current application, I have a tab control. Inside the tab control, I place a UserControl. Inside this UserControl, I place four other UserControls whose designs are all identical, with each one representing a “screen” (and it takes four of these screens to represent an entire data record). Sort of like a Wizard Control (and I may write about this design more in the future) that allows web-like backwards and forward navigation. For now suffice it to say that only one of the four screens may be shown at a time, so I use a combination of the Parent property and Visibility to show the correct screen based on user navigation.
And it is the navigation that I want to discuss. The navigation controls are all on the Parent UserControl (we’ll call it Editor), which handles the switching of screens, etc. To enable keyboard events, I chose the easy route of adding a ContextMenu to Editor, and creating ContextMenuItems for each of the navigation options I wanted. Using the built in options of Visual Studio, I then assigned each of these items the keyboard shortcut I wanted. In this case, I wanted the screens to navigate like a web browser, so I wanted Alt-Left and Alt-Right to switch backwards and forwards between the screens. (I actually have seven of these, but these two will suffice for this example.) I then of course wired the events up in my code to react to the ContextMenuItems being clicked.
This worked great… as long as Editor was the control with Focus(). Once the user clicks inside one of the child UserControls, the Focus() shifts to that UserControl! Now my keyboard navigation ceases to function. But I had to allow the screen to retain Focus(), so that the user could continue to use the Tab key for form navigation. So, what to do?
I did some research and found an option in overriding the UserControl’s ProcessDialogKey method. This is an interesting method, and really opened my eyes to the sheer number of items that can be overridden and taken advantage of. I’m going to have fun examining some of these, but for now let’s get back to this one. The easiest way to use this is to use a Switch statement based on the method’s KeyData parameter to compare this to the values available in the Keys enumeration. I wrote some code to do this, thinking that I had solved my problem, but alas it did not work… or at least did not appear to work. Pressing the Alt-Left or Alt-Right keys did not fire the event. Instead, the Alt key would send Focus() to the Main Menu of the Main Form Window.
Since I was getting no joy, I decided to go another way. I copied Editor‘s ContextMenu onto each of the screens and wired up the items events. Unfortunately I experienced the same problem as above. It turns out that I was not managing Focus() enough. I had to add code to assign Focus() to each screen when it was activated. Doing that began allowing the Events to fire as desired. Because of this, I believe the ProcessDialogKey approach would have worked, but since I had already ripped it all out I did not put it back to test my theory.
So now that the event is firing as desired, the problem is that is is firing within the screen: but all the navigation is in the Editor! So now the task is to notify Editor that a navigation event has been requested. The answer of course is to create my own Event. Following the approach outlined here previously I created an EditorNavRequestedEventHandler delegate in the Editor code. Then in each of the screens I created the actual EditorNavRequestedEvent and fired it whenever the ContextMenuItem event fired. Rather than create a separate event for each of the possible navigations types (again I have 7 of them), I created my own EditorNavRequestedEventArgs class that I used to pass what navigation had been requested.
Now in my Editor code I added the EditorNavRequestedEvent listener and method for each of the four screens. It sounds like a lot of code, but it really isn’t. What it is is another great example of how simple events can make communication between objects very simple. Now I have a Wizard-like control that allows browser-style keyboard navigation. It also has the added benefit of a ContextMenu, which users are fairly comfortable with, thereby increasing flexibility and user options. If you don’t want a ContextMenu, remember you can still use the ProcessDialogKey approach: just make sure your Focus() is in the right place.
There is another way to handle this: you can have ToolStripMenuItems that activate this behavior.? The trick there is to have them percolate downwards to the currently displayed Editor control (remember this was inside a TabControl, so you could have multiple Editors at any given time.)? You would then need to execute public methods on the Editor instance to control navigation from the outside.? While it would work, I preferred all the navigation to be private, which the Event approach allowed me to do.? Also, I would not only need to identify which Editor instance was active, I would also have needed to monitor when and if there were any active so that I could set Enabled statuses for the ToolStripMenuItems.? This would need to be done everytime the TabPages property changed and whenever the SelectedIndex property changed for the TabControl. Overall, I felt like this added too much overhead, but it does offer an alternative approach.