为什么我需要将Dispatcher.BeginInvoke调用到SetFocus?

时间:2010-09-15 15:30:22

标签: wpf user-controls

我有一个WPF UserControl,其中包含一个由于状态更改而可见的StackPanel。当StackPanel变得可见时,我想将键盘焦点设置为特定的子TextBox。我发现(在经过大量试验和错误之后)对TextBox.Focus()的调用将无法设置焦点(并返回false),除非我在BeginInvoke调用中包装该调用,如下所示:

    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);
    }

两个LogThreadMsg调用都表明它们位于同一个(UI)线程中,如下所示:

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

那为什么需要这种“黑客”呢?这似乎是某种时间问题,我正在寻找一个下游事件,这可能是一个更好的地方调用Focus()而不诉诸于此,但还没有找到它。谁能解释一下这里发生了什么?

2 个答案:

答案 0 :(得分:3)

这确实是一个时间问题。当CtlClientLookupPanel变得可见时,我认为您的TextBox尚未显示,无法集中注意力。您可以尝试处理IsVisibleChanged上的TextBox事件而不是

答案 1 :(得分:1)

实际上可以更准确地说,当您尝试对焦时,文本框尚未呈现。 WPF的核心是一种消息泵,就像WinForms一样,但是更为先进的一种 - Dispatcher。 Dispatcher用于排队工作 - 当队列中的消息根据其优先级处理时,您触发的某些操作将在稍后的同一线程上执行。 BeginInvoke在Dispatcher队列上对另一个工作项进行排队,并在首先需要的其他项之后执行。

这是一个非常黑客的解释,我鼓励你阅读更多内容 - 只需谷歌WPF调度员阅读任何大量的文章,大多数是非常好的。

编辑:另外,在你的情况下要处理的好事件是文本框的Loaded事件;通常只有在完成所有其他布局工作并且控件实际可见后才会触发Loaded事件。但是,在Dispatcher上排队项目也是处理事情的好方法。