WPF in Visual Studio 2010 – Part 3 : Focus and Activation

Visual Studio Blog

 

Continuing the WPF in Visual Studio 2010 series, today’s post is on the subject of “Focus and Activation”. Of all the problems we had to deal with in the new WPF UI, this was probably the most tricky to get right. Focus problems are notoriously hard to debug, partly because interacting with the debugger moves focus again. (Tip: Using a remote debugger helps.) Here I’ll describe one class of focus problems we had to deal with in our move to a WPF UI.

What is focus?

For a desktop application, focus is (very) loosely defined as “the destination for keystrokes”. When you press a key on the keyboard, it’s the window or control which handles that keystroke. Focus moves around as users interact with the application but, at any time, it should be obvious where the focus is. This is usually indicated by a visual adornment, such as a dotted rectangle, or by a blinking caret. Users shouldn’t have to intuit where the focus is and they get horribly confused if their keystrokes don’t go where they think they should.

What is activation?

You may have many applications running on your Windows® desktop, but there’s only ever one active window. When you switch between applications using Alt+Tab or clicking on their windows, you are changing the active window. Even WPF applications have a main window which is hosted on the desktop, so there’s no exception there. For the most part activation is straightforward except when an application has more than one top-level windows. In addition to the main window, Visual Studio creates top-level windows for floating document and tool windows. When one of the floating windows is active, its caption changes color.

image

image

The active window is remembered when the user switches to a different application so that it can be restored when the user switches back.

Foci noun, pl. of focus

In an application, such as Visual Studio 2010, where we have a mixture of both WPF and Win32 content, there are two separate systems of focus, each with its own APIs.

For the most part, you can simplify these two systems into:

  1. The HWND to which WM_KEY* messages are dispatched and
  2. The “keyboard focused element” in the WPF logical tree to which WPF keyboard input is routed.

Now, for a simple WPF application with one top-level window and no hosted HWNDs, these two models work well together. As far as Win32 is concerned, there’s only one place the focus could be: the top-level HWND. As long as the application is active, all keystroke messages are dispatched to that window. Once the keystroke is dispatched, WPF’s InputManager routes the message to the element with keyboard focus.

For a WPF application with some hosted Win32 controls, at first things also work well. When focus is somewhere in WPF content, then the top-level WPF window gets all keyboard messages as before. When the user puts focus in a Win32 control, then it becomes the Win32 focused element (via a SetFocus call) and, from then on, keyboard messages are dispatched directly to that control.

Things begin to break down when these two systems get out of sync, or when the visual cues given to the user don’t match the application’s current focus state.

Temporary Focus Scopes (Focus stealing and restoration)

Imagine you’re typing in the Visual Studio text editor. The blinking caret indicates where your keystrokes will go. We could say that focus is “in the text editor”. In Visual Studio 2010, the text editor is part of the WPF tree, so keystroke messages are being dispatched to the WPF main window and WPF routes them to the element with keyboard focus. Now, let’s say you press “Alt+F” to drop down the file menu. The file menu appears and the first enabled menu item is highlighted. The caret stops blinking in the text editor. Where should the focus be now?

You could be forgiven if you said “focus should be in the menu” – after all, the menu is just another WPF element and the next keystroke should affect the highlight in the menus. Indeed, looking deeper, the menu is a new top-level popup window with a distinct HWND and it should be possible to call SetFocus on that window and ensure that all keystrokes are dispatched there. For most WPF applications, this is in fact what happens. The WPF menu system takes keyboard focus and the text editor loses it.

If you said “focus should stay in the text editor”, then good for you! You may have said this if you’re an advanced Win32 programmer, because that is, in fact, what happens with Win32 menus. When a Win32 menu is shown, it doesn’t take focus. Instead, keyboard messages are intercepted and dispatched via a special “menu modal loop”. For as long as the menu is active, this menu modal loop dispatches messages directly to the menus even though Win32 focus is not there. This sounds like the kind of thing we want to happen too. Let’s explore this a little more.

Why should focus stay in the text editor? Because when a command is selected from the menu, we want it to behave just as if focus never left the text editor. Take the “Cut” command, for example. When it executes, we want it to take the currently selected item (maybe a selection in the text editor, or a control on a graphical designer) and move it to the clipboard. To do this, focus had better not move around, otherwise we’ll end up moving the wrong item. “Cut” is bound to the Ctrl+X keyboard shortcut and that doesn’t change focus, so it also shouldn’t move focus if you interact with the menus. “A-ha!”, you might say, “WPF could remember where focus is when the menu is first shown and, just before executing the command, it could put focus back there”. True enough, that’s what WPF does, but there’s one, tiny little problem.

Expanding on the “Cut” example above, imagine that you’re responsible for all the clipboard commands in Visual Studio. Part of that responsibility means you own the code which decides whether the Cut, Copy and Paste commands are available (CanExecute) at any given time. This is so the appropriate menu items and toolbar buttons can be grayed out when the commands are not available.

image image

The CanExecute logic is dependent on where the currently selected item is and that depends on where the focus is. If focus were “in the menu” when the Edit menu is dropped down, then the logic for the clipboard commands would be looking in the wrong place for selection. If focus stayed “in the text editor”, then the CanExecute handlers would work as expected*.

OK, I hope I’ve convinced you that WPF menus should not steal focus when they become active. Since what we want is not WPF’s default behavior, we have to override that behavior.

HwndSource.DefaultAcquireHwndFocusInMenuMode

Winning the prize for (quite probably) the longest new property name in WPF 4.0 is HwndSource.DefaultAcquireHwndFocusInMenuMode. The default value is ‘true’, so to prevent focus stealing, we set it to ‘false’:

// By default, WPF menus will acquire Win32 focus for the HwndSource in which they are contained. // This flag prevents WPF menus from acquiring Win32 focus when they receive WPF focus. HwndSource.DefaultAcquireHwndFocusInMenuMode = false;

As it happens, this is only half the story. WPF also needs to be told not to restore focus when leaving menu mode by adjusting another new WPF 4.0 property, Keyboard.DefaultRestoreFocusMode:

// Ensure that the MainWindow’s HwndSource and all subsequently-created // HwndSources on the UI thread have their RestoreFocusMode set appropriately. Keyboard.DefaultRestoreFocusMode = RestoreFocusMode.None;

These two settings combined will stop WPF taking Win32 focus (that’s the “HwndFocus” part of the long property name) when interacting with menus. WPF still moves keyboard focus to the menu and restores it when the menu is dismissed.

But wait, there’s more! “MenuMode” is a new concept in WPF 4.0 and, by default, attached to Menu controls, but there are reasons to enter menu mode besides interacting with menus. Who’s to say, with WPF at your disposal, that your application even has standard menus and toolbars? You may have an application with completely custom “administrative” UI, but still need the capability to redirect all keyboard input temporarily. This is what MenuMode is for, and Visual Studio uses it for Toolbars.

To see this in action, think back to our clipboard example. The clipboard commands – cut, copy and paste – appear on the Standard toolbar:

image

When the user clicks on, say, the Cut button, if focus moved away from the currently selected item, it would miss the very thing the user intends to cut to the clipboard. To have Toolbar participate in MenuMode behavior, we need to tell WPF to enter MenuMode when the toolbar would normally get focus and to leave MenuMode afterwards. The trick here is to use the “PreviewGotKeyboardFocus” and the “LostKeyboardFocus” events on the toolbar. Hopefully the following code snippets will get the idea across.

Markup (XAML):

<ToolBar Name=”toolBar1″ PreviewGotKeyboardFocus=”toolBar1_PreviewGotKeyboardFocus” PreviewLostKeyboardFocus=”toolBar1_LostKeyboardFocus”> …</ToolBar>

Code:

PresentationSource _menuSite;

// Go into menu mode when the toolbar gets focus private void toolBar1_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { _menuSite = HwndSource.FromVisual(this); InputManager.Current.PushMenuMode(_menuSite); }

// Leave menu mode when the toolbar loses focus private void toolBar1_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { InputManager.Current.PopMenuMode(_menuSite); _menuSite = null; }

In essence, when WPF tries to move keyboard focus to the toolbar, we tell it to enter MenuMode. When focus leaves the toolbar, we tell WPF to leave menu mode. Note that these two events are hooked to WPF’s “keyboard focus” which is still moved during MenuMode, even if Win32 focus movement is suppressed with DefaultAcquireHwndFocusInMenuMode.

Conclusion

There’s a video of a presentation I gave at PDC last year which covers this same topic, including a live demonstration (fast forward to timestamp 00:33:19) showing why MenuMode may be necessary and how to integrate it in your own application. Some of the code snippets in this article are taken from that demo.

MenuMode is new for WPF 4.0 and, looking back, we wrestled for quite a while with focus problems and some exotic workarounds before realizing that a change in WPF was necessary. Even before we started work on Visual Studio 2010, we interviewed the Expression Web team about their experience with WPF and “focus bugs” was called out as a risk. I remember giving a talk about a year ago at the MVP Summit where I talked about our WPF conversion work (this was before Beta 1 was released) and I identified focus management as our most challenging unsolved problem. I’m really proud that, between Beta 1 and Beta 2, the WPF team took up the challenge, worked with us to understand our needs, and developed the MenuMode solution. With that, a whole class of focus bugs was swept away.

That’s all for this part. Next time, Part 4: “Direct Hosting of WPF content”.

Previous posts in the series:

Part 1 : Introduction

Part 2: Performance

 

*Actually, this still isn’t quite the whole story for Visual Studio since it has its own selection tracking service. However, consider the tricky case of in-place renaming of an item in the Solution Explorer. If you visit the Edit menu while renaming, the temporary edit control is not part of selection tracking and yet the clipboard commands still work correctly because we didn’t let WPF steal focus.

clip_image002

Paul Harrington – Principal Developer, Visual Studio Platform Team Biography: Paul has worked on every version of Visual Studio .Net to date. Prior to joining the Visual Studio team in 2000, Paul spent six years working on mapping and trip planning software for what is today known as Bing Maps. For Visual Studio 2010, Paul designed and helped write the code that enabled the Visual Studio Shell team to move from a native, Windows 32-based implementation to a modern, fully managed presentation layer based on the Windows Presentation Foundation (WPF). Paul holds a master’s degree from the University of Cambridge, England and lives with his wife and two cats in Seattle, Washington.

0 comments

Discussion is closed.

Feedback usabilla icon