Why do I need to call Dispatcher.BeginInvoke to SetFocus?
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.Visib开发者_C百科le) {
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?
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
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.
精彩评论