views:

25

answers:

2

I have a WPF UserControl that contains a StackPanel that is made visible as a result of a state change. When the StackPanel becomes visible, I want to set the keyboard focus to a particular child TextBox. I have found (after a lot of trial and error) that the call to TextBox.Focus() will fail to set focus (and returns false) unless I wrap that call in a BeginInvoke call as shown here:

    private void CtlClientLookupPanel_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) {
        LogThreadMsg(string.Format("CtlClientLookupPanel_IsVisibleChanged to {0}", CtlClientLookupPanel.Visibility));
        if (CtlClientLookupPanel.Visibility == Visibility.Visible) {
            Dispatcher.BeginInvoke((ThreadStart)delegate {
                bool gotFocus = CtlClientSearchText.Focus();
                LogThreadMsg(string.Format("CtlClientSearchText.Focus() returned {0}", gotFocus));
            });
        }
    }

    private void LogThreadMsg(string msg) {
        string fullMsg = string.Format("Thread: {0} - {1}",   Thread.CurrentThread.ManagedThreadId, msg);
        System.Diagnostics.Trace.WriteLine(msg);
    }

Both LogThreadMsg calls indicate they are on the same (UI) thread as shown here:

[5232] Thread: 1 - CtlClientLookupPanel_IsVisibleChanged to Visible 
[5232] Thread: 1 - CtlClientSearchText.Focus() returned True 

So why is this "hack" needed? It seems to be some sort of timing issue and I was looking for a downstream event that perhaps would be a better place to call Focus() without resorting to this, but haven't found it. Can anyone explain what's going on here?

+1  A: 

It is indeed a timing issue. When CtlClientLookupPanel becomes visible, I think your TextBox isn't visible yet, and can't be focused. You could try to handle the IsVisibleChanged event on the TextBox instead

Thomas Levesque
This did the trick. Thanks!
Decker
A: 

Actually it can be more accurately said that the textbox is not rendered yet when you attempt to focus it. WPF has a sort of message pump at its heart, much like WinForms, but a much more advanced one - the Dispatcher. The Dispatcher is used to queue work - some actions you triggered are executed later on, on the same thread, when the messages in the queue get processed according to their priorities. BeginInvoke queues another work item on the Dispatcher queue, and it is executed after some other items that are needed first.

That's a very hack-ish explanation, and I encourage you to read up more on it - simply google WPF Dispatcher and read any of the ton of articles, most are very good.

Edit: also, a good event to handle in your case would be the Loaded event of the textbox; generally the Loaded event is triggered only after all other layout work is done and a control is actually visible. However, queueing items on the Dispatcher is also a good way of going about things.

Alex Paven
Thanks for the great explanation. Actually, the TextBox (and parent StackPanel) are loaded earlier as part of the UserControl and initially set to Collapsed; the Loaded event doesn't work in my case. However, setting the Focus on the TextBox's IsVisibleChanged did the trick.
Decker