Win32 Activation and Focus

For the most part, activation and focus work pretty darn well in windows, until you want to tweak something that doesn’t fit within the typical windowing model or roll your own menu.

 

Most folks don’t separate out activation and focus, but they are really separate things. Experimenting for a moment – if you bring up the run dialog (start->run) and tab over to the cancel button – a dotted rect indicates that the cancel button currently has focus. This rectangle goes away when you subsequently alt+tab to a different application. Separately, you may notice that the color of the window goes from dark blue (when the window is active) and light blue (when the window is in-active).

 

With that observation in mind, we could come up with a crude distinction between activation and focus.

 

Activation is the currently active top-level window.

 

Focus is the control in the system that will receive keyboard input.

 

Walkthrough – understanding the interaction of window messages

 

In the run dialog example, I have taken the liberty of capturing some spy++ output. I picked Spy->Log Messages, chose “windows of same thread”, dragged the finder tool to the run dialog and checked off the following messages:

 

WM_MOUSEACTIVATE – Occurs when someone mouse-s down on an inactive window. Sent to control being activated.

 

WM_NCACTIVATE – Occurs when the non-client area needs to be activated or de-activated (most commonly the title bar changes shades of blue). Sent to the toplevel window.

 

WM_ACTIVATE – Occurs when the client area needs to be activated or deactivated. Contains information about the other window being activated/deactivated. Sent to the toplevel window.

 

WM_SETFOCUS – Occurs when setting focus to a control. Sent to the control itself.

 

WM_KILLFOCUS - Occurs when a control is losing focus. Sent to the control itself.

 

 

I then alt+tab’ed to the run dialog and got the following output:

<00011> 001101DC S WM_NCACTIVATE fActive:True

<00012> 001101DC S WM_ACTIVATE fActive:WA_ACTIVE fMinimized:False hwndPrevious:(null)

<00013> 00080166 S WM_SETFOCUS hwndLoseFocus:(null)

 

00011 – WM_NCACTIVATE: The title bar was previously light blue; this message changes the non-client to be in an active state – the wParam of fActive tells us that the window should become active.

 

00012 – WM_ACTIVATE: This message activates the client area of the toplevel window. The wParam specifies the fActive, and the lParam specifies which other window in the system was previously active. This handle can be null and will be when the previously active window is not in the same thread.

 

00013 – WM_SETFOCUS: The cancel button is getting the focus in this case. When you get a WM_ACTIVATE, it is usually immediately followed by a WM_SETFOCUS.

 

When I alt+tabbed away from the run dialog I got the following output:

 

<00014> 001101DC S WM_NCACTIVATE fActive:False

<00015> 001101DC S WM_ACTIVATE fActive:WA_INACTIVE fMinimized:False hwndPrevious:(null)

<00016> 00080166 S WM_KILLFOCUS hwndGetFocus:(null)

 

00014 – WM_NCACTIVATE: The title bar is now switching from an active to an in-active state.

 

00015 – WM_ACTIVATE: The client area has now lost activation. Because the window becoming active is outside of the current thread, the handle of the window activating in the wParam is null.

 

00016 – WM_KILLFOCUS: The button has now lost focus. Because the window getting focus is outside of the current thread, the handle of the control getting focus in the wParam is null.

 

Finally I clicked on the run dialog, while it was in-active, and activated it.

<00017> 001101DC S WM_MOUSEACTIVATE hwndTopLevel:001101DC nHittest:HTCAPTION uMsg:WM_LBUTTONDOWN

<00018> 001101DC S WM_NCACTIVATE fActive:True

<00019> 001101DC S WM_ACTIVATE fActive:WA_CLICKACTIVE fMinimized:False hwndPrevious:(null)

<00020> 001501E8 S WM_SETFOCUS hwndLoseFocus:(null)

 

Messages 18-20 are the same as before, but there’s a new message, WM_MOUSEACTIVATE. This message goes to the control that was clicked on to ask whether it is interested in accepting the mouse down and/or activating the window.

 

Some other interesting functions

Since a windows message queue is per-thread, a lot of the notion around focus and activation is also per-thread.

 

GetFocus – returns the handle of the currently focused control on the thread.

SetFocus – sends a WM_KILLFOCUS to the old control, activates the new window if necessary and sends a WM_SETFOCUS to the new control. In Windows Forms this is wrapped in the Focus() method.

GetActiveWindow – returns the handle of the currently active top-level window within the thread.

SetActiveWindow - activates a top-level window within your current thread. If the application is not in the foreground, it sets what the active window will be.

 

GetForegroundWindow – returns the top-level window that is currently active on the desktop.

SetForegroundWindow – changes the top-level window that is currently active on the desktop. Not really – see remarks for restrictions. And why.

 

 

How can I prevent clicks from activating and focusing my window?

 

You can override the WndProc and handle the WM_MOUSEACTIVATE message. You can either let the click go through – which is MA_NOACTIVATE, or you can eat the click – which is MA_NOACTIVATEANDEAT. A sample of preventing clicks is here.

 

How can I prevent my window from getting activation and focus when shown?

 

In Windows Forms 2.0 there is a new property called ShowWithoutActivation – which you would need to override on the Form. In native applications you can use SetWindowPos with the SWP_NOACTIVATE flag or the ShowWindow with the SW_SHOWNA flag.

How can I get focus, but not activation to my window?

 

This is typically a menu-ing situation – you want to show a treeview or something in a popup window. I’ve talked a bit about the pain and recommendations here. The 2.0 framework supports this, I would recommend starting there rather than rolling your own solution.

 

Best practices with focus and activation (or rather, lessons learned the hard way)

 

Avoid manually changing focus when getting and/or losing focus. This usually proves to be error prone. Instead look for ways to prevent the focus from changing – preventing activation will usually prevent focus changes.

 

Avoid manually changing the active window. In most cases, it is not possible to activate a window that is not within your process. There are a whole bunch-o-rules in the remarks section.

 

The debugger can get in the way of debugging these issues by setting itself as the active window when hitting a breakpoint. Try using tracepoints, Debug.WriteLines or remote debugging.