Why does setting keyboard focus from code behind r

2019-03-01 18:29发布

问题:

I have a UserControl that contains a TextBox. When the user control becomes visible, I give the TextBox focus. Could somebody clarify why I have to do this using the Dispatcher?

public MyUserControl() 
{
    InitializeComponent();
    this.IsVisibleChanged += VisibilityChanged;
}

Case 1 (works):

private void VisibilityChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (this.Visibility == Visibility.Visible)
    {
        this.Dispatcher.BeginInvoke((Action)delegate
        {
            Keyboard.Focus(this.InputTextBox);
        }, DispatcherPriority.Render);
    }
}

Case 2 (does not work):

private void VisibilityChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (this.Visibility == Visibility.Visible)
    {
        Keyboard.Focus(InputTextBox);
    }
}

回答1:

could you call Keyboard.Focus(InputTextBox); in the event handler for InputTextBox.IsVisibleChanged instead of this.IsVisibleChanged?

If this works then I suspect the this.IsVisibleChanged event is raised before the layout panel has updated the children controls, i.e perhaps InputTextBox is still not visible when you put focus on it without BeginInvoke.



回答2:

Probably because the IsVisibleChanged event is raised on another thread (not on the UI thread).



回答3:

The control you are manipulating belongs to the UI thread (because that is where it was created). All controls derive from DispatcherObject, so control.Dispatcher (or this.Dispatcher from within the control) will give you the reference to the Dispatcher belonging to the thread the control was created on.

You are then queueing an action on that Dispatcher from the background thread that the event handler is running on. Why are you running on a background thread? It is a control so it is at the mercy of its host, at a guess there is some programmatic logic on a background thread that is causing the visibility to change (maybe via data binding), and consequently the event handler is also invoked on that background thread.

In the interests of stopping you from going wild with the Dispatcher and attempting to use it to perform magical mystical feats that it isn't intended for, make sure you familiarise yourself with the Dispatcher.CurrentDispatcher property and its difference (I've seen some truly awful code due to developers not realising this).

For a good overview of the Dispatcher associated with a UI element check this article: MSDN Advanced WPF: Threading Model. Note that sample code that calls VerifyAccess() before attempting the action that manipulates the control.