WPF:在附件 属性 中,如何等待可视化树正确加载?

WPF: In an attached property, how to wait until visual tree loaded properly?

我在 WPF 应用程序中有一个附件 属性。

下面的代码在 OnLoad 事件中,但它不起作用,除非我添加一个 hacky 500 毫秒延迟。

有什么方法可以避免这种延迟,并检测何时加载了可视化树?

private static void FrameworkElement_Loaded(object sender, RoutedEventArgs e)
{
    // ... snip...
    Window window = GetParentWindow(dependencyObject);

    // Without this delay, changing properties does nothing.
    Task task = Task.Run(
        async () =>
        {
            {
                // Without this delay, changing properties does nothing.
                await Task.Delay(TimeSpan.FromMilliseconds(500));

                Application.Current.Dispatcher.Invoke(
                    () =>
                    {
                        // Set False >> True to trigger the dependency property.
                        window.Topmost = false;
                        window.Topmost = true;                                              
                    });
            }
        });
 }

更新 1

根据@Will 的回答,"use the dispatcher and select the appropriate priority"。这非常有效:

private static void FrameworkElement_Loaded(object sender, RoutedEventArgs e)
{
   // Wrap *everything* in a Dispatcher.Invoke with an appropriately
   // low priority, to defer until the visual tree has finished updating.
   Application.Current.Dispatcher.Invoke(
   async () =>
        {
            // This puts us to the back of the dispatcher queue.
            await Task.Yield();

            // ... snip...
            Window window = GetParentWindow(dependencyObject);

            window.Topmost = true;                                              
        }, 
        // Use an appropriately low priority to defer until 
        // visual tree updated.
        DispatcherPriority.ApplicationIdle);
 }

更新 2

如果使用 DevExpress,LayoutTreeHelper class 可用于上下扫描可视化树。

有关处理可视化树上下扫描的示例代码,请参阅:

更新 3

如果您在附加的 属性 中,可靠加载可视化树的唯一方法是在 Loaded 事件处理程序中或之后执行代码。如果我们没有意识到这个限制,那么一切都会间歇性地失败。如上所述,等待 OnLoaded 事件触发远优于任何其他试图引入其他随机形式延迟的方法。

如果您使用的是 DevExpress,这就更重要了:在某些情况下,在 Loaded 事件之前尝试做任何事情都可能导致崩溃。

例如:


免责声明:我与 DevExpress 无关,它是许多优秀的 WPF 库之一,我也推荐 Infragistics。

如果您想等待在 UI 线程中执行某些操作,请使用 Dispatcher。你怎么抓住它?那是个骗子。

How do I get the UI thread Dispatcher?

您可以使用DispatcherPriority到select以后的某个时间。优先级基于 UI 项活动,因此您不能说例如下周。但是你可以说 "let me wait until after bindings have been processed",例如

我更喜欢这个版本,因为它可以更好地与 ReSharper(建议方法存根)一起使用,并按优先顺序获取参数。

public static class FrameworkElementExtensions
{
    public static void BeginInvoke(this DispatcherObject element, Action action, DispatcherPriority priority = DispatcherPriority.ApplicationIdle)
    {
        element.Dispatcher.BeginInvoke(priority, new Action(async () =>
        {
            await Task.Yield();
            action();
        }));
    }
}