Please keep your focus!

I learned something that I didn’t know about WindowsForms the other day. It all started while debugging a problem with an application in which there’s a web browser pane hosted in an otherwise WindowsForms application. The problem concerned the focus. If you gave the focus to the web browser pane, then shifted focus to another application, and then clicked the title bar of the WindowsForms application, the focus would no longer be in the embedded web browser, but would have shifted to the last control that had been given focus in the WindowsForms.

I got out Spy++ and watched the windows messages that were being sent around. Sure enough, the web browser element was being given the focus, but when the WindowsForms application was reactivated, the WM_FOCUS message was being sent to the last control of the WindowsForms controls that had been given the focus.

I spent a while looking around for clues, and it was only when I saw that the Form supported an ActiveControl property that I understood that WindowsForms itself must be tracking the focus. It was then easy to put things together. Control, in System.Windows.Forms, subclasses the various WindowsFroms controls, overriding the WndProc method. If you look inside that method, you’ll notice that focus changes are propagated up to the container control that contains the element that was given the focus. This container is then responsible for tracking who has the focus. This explained the issue I was seeing. In that case, Windows has given the focus to a control that hasn’t been subclassed by WindowsForms, and hence nothing is told that the focus has moved, leaving the tracked focus on the last WindowsForms control that had it.

Why does WindowsForms track the focus? So that it can do all kinds of useful things for the application writer. For example, it can ensure that the control with the focus is visible in the GUI. As an example of this, try the following small program. It sets up a Form that contains a scrollable panel, and the panel contains two text boxes only one of which is visible. After a suitable delay, we set the focus to the textbox which is currently outside the viewing area. This message is handled by the WindowsForm code, which notifies the container which can then take care of scrolling the focussed element into view.

[DllImport(“user32.dll”, CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr SetFocus(HandleRef hWnd);

 
[STAThread]
static void Main(string[] args)
{
    var form = new Form { Height = 150, Width = 150 };
    var panel = new Panel { Height = 100, Width = 100, AutoScroll = true };
    var txt1 = new TextBox { Location = new Point(0,0) };
    var txt2 = new TextBox { Location = new Point(500,500) };
    form.Controls.Add(panel);
    panel.Controls.Add(txt1);
    panel.Controls.Add(txt2);
    form.Load += delegate {
        HandleRef txt2Handle = new HandleRef(txt2, txt2.Handle);
        SynchronizationContext context = SynchronizationContext.Current;
        ThreadPool.QueueUserWorkItem(delegate
        {
            Thread.Sleep(5000);
            context.Post(delegate { SetFocus(txt2Handle); }, null);
        });
    };
    Application.Run(form);
}

I hadn’t realised before that WindowsForms offered all of this functionality. As long as you stay in its domain, all of this magic happens without you having to think about it. However, when you interact with elements that are not under its control, then things get a little more confusing.

Advertisements
This entry was posted in Computers and Internet. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s