Dealing with menus and floating windows is a real pain in the butt! If you have written any type of WPF MDI (Multi Document Interface) application you know exactly what I am talking about. Focus management in WPF is a real pain in itself. Having to worry about Keyboard focus, Logical focus, and focus scope just makes you want to pull the hair out of your head. Now start throwing in child windows and things get really painful.
So Brian, what problem are you actually talking about? Good question! So on to the point. The scenario I am talking about assumes you have a WPF application that has a MainWindow which contains a menu and maybe a toolbar. Now, your application can open an unlimited number of Window instances. The expectation of your application is that when I have a specific child window focused, I want to be able to click on a menu item on the MainWindow and have that action execute on the current active Window, and ONLY that Window instance. Sound familiar? Think Visual Studio.
This blog post is going to answer a question a received regarding my post on a Prism region adapter for the xamDockManager in which the developer wanted to know how to have buttons on the xamRibbon control execute Prism DelegateCommands on the floating panes of the xamDockManager. Just so you know, a floating pane in hosted inside a Window instance. That means I am going to use the sample application in my previous post as a starting point.
First, I modified the sample to use the xamRibbon control in place of the menu that was there. Next I modified the View to add a TextBox so that I could prove that focus remained on a specific control inside a floating pane. I also added a Prism CompositeCommand that will be used to update the views by calling the active ViewModel’s UpdateCommand. I am not going to get into the details of CompositeCommands, DelegateCommands, IActiveAware and how Prism supports these objects. If you use Prism, I am assuming you are familiar with how these work. If you aren’t familiar, then just ask, and I will create a separate blog post on IActiveAware and command support. Anyways, the gist of this is that Prism will know which View/ViewModel is active, and only execute the commands of the active View/ViewModel. Pretty cool huh?
Now, these modification don’t really fix my problem. If I run the application and double click on a row in the XamDataGrid, a View will be injected into the XamDockManager. I can then tear off the tab and place it in a floating Window which will have focus as seen below.
Next, I want to click on the “Update” button on the xamRibbon and have the View number update accordingly with a new random number. The problem is that the second I click on the xamRibbon “Update” button, the floating Window loses focus, the MainWindow gains focus, and nothing happens. Well, that sucks!
So, how do we solve this little problem of ours? It’s actually extremely easy, but you might not think of it because it deals with listening to the Windows Message Loop. The what? The Windows Message Loop. You might not know this, but the are message being sent throughout your application under the covers. We want to tap into those message and listen for the ones we care about. So let’s check our the code that will fix this for us.
/// Interaction logic for Shell.xaml
///</summary>
publicpartialclassShell : Window
{
public Shell(ShellViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
protectedoverridevoid OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
var source = HwndSource.FromVisual(this) asHwndSource;
source.AddHook(FilterMessage);
}
///<summary>
/// Windows message loop filter.
/// Watches for WM_MOUSEACTIVATE and returns MA_NOACTIVATE for the main window.
/// This prevents the main window from getting focus due to a menu click.
///</summary>
privateIntPtr FilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, refbool handled)
{
constint WM_MOUSEACTIVATE = 0x0021;
constint MA_NOACTIVATE = 3;
switch (msg)
{
case WM_MOUSEACTIVATE:
handled = true;
// TELL WINDOWS NOT TO ACTIVATE MAIN WINDOW.
returnnewIntPtr(MA_NOACTIVATE);
}
returnIntPtr.Zero;
}
privatevoid XamRibbon_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
//prevent the XamRibbon from gaining keyboard focus
e.Handled = true;
}
}
First we override the OnSourceInitialized method from our base Window class. This is where we are going to add our HWND hook. Our Filter basically checks to see if the MainWindow is being activated my a mouse click, and if it is, we simply stop it from being activated which keep focus on our floating pane window that currently has focus. One more important note is that we have added an event handler for the xamRibbon.PreviewGotKeyboardFocus event. In this handler, we simply set e.handled = true so that the xamRibbon does not receive keyboard focus and keeps the focus on the floating pane window. Now, let’s try our little experiment again.
I open a new floating pane window and make sure I click in the TextBox to give it focus. Next I hover my mouse over the xamRibbon “Update” button. Click it. Drum roll please…
BOOM! The current active floating pane window is updated and it still has focus. You can tell by the bright blue color of the window chrome. Now it doesn’t matter how many floating pane windows I have open, only the active floating pane window will retain focus and any menu item click will invoke the commends on the current View’s ViewModel. Pretty slick! Don’t worry, if you click anywhere else on the MainWindow, focus will be given to the MainWindow just as you would expect it to be.
Go ahead and download the source code and start playing around. Feel free contact me on my blog, connect with me on Twitter (@brianlagunas), or leave a comment below for any questions or comments you may have.
Hi Brian,
thanks for this great article. I was having this problem some weeks ago and found a similar solution on stackoverflow.com, so it’s great to see that this is the correct way of doing this.
One problem remains though: Keyboard navigation on the ribbon (KeyTips or cursor keys in navigation mode) does not work this way. What would be the best strategy to tackle this problem? Not being the most knowledgable person on WPF, I was thinking about forwarding all keystrokes to the Ribbon after ALT has been pressed in the floating window (and disable this behavior if the Ribbon leaves navigation mode). Can the Automation APIs be used to perform this (I have not yet done any automation in WPF) – or can you think of a better approach?
In this case you will have to use menu mode and not lock focus to the floating window. Basically you will have the same behavior, except the floating window will not show focus. Of course this is just off the top of my head and I will need to test it out. I will blog about the second approach soon.