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

4
我在WPF应用程序中有一个附加属性。
下面的代码位于"OnLoad"事件内,但如果我不添加500毫秒的hacky延迟,它就无法正常工作。
有没有办法避免这种延迟,并检测视觉树何时已加载?
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的回答,“使用分发器并选择适当的优先级”。这个方法非常有效:

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 类对于扫描可视树的上下文非常有用。

有关扫描可视树的示例代码,请参见:

更新 3

如果您在使用附加属性,可靠地加载可视树的唯一方法是在Loaded事件处理程序中执行代码或之后。如果我们不了解这个限制,那么所有事情都会间歇性地失败。等到OnLoaded事件触发后再执行代码要比尝试引入其他随机延迟的任何其他方法更好,如上所述。
如果您正在使用DevExpress,这甚至更为关键:在Loaded事件之前尝试执行任何操作,在某些情况下可能会导致崩溃。
例如:
  • Loaded事件之前调用window.Show()在某些情况下会导致崩溃。
  • Loaded事件之前钩入IsVisibleChanged事件处理程序在某些情况下会导致崩溃。

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


2
使用调度程序并选择适当的优先级。 - user1228
@Will 太棒了!选择什么最优先? - Contango
不知道,没有测试过。你似乎在等待绑定之后,所以尝试使用比绑定更低优先级的东西。 - user1228
非常好,DispatcherPriority.ApplicationIdle 完美地运作。 - Contango
@Will 这个完美地解决了问题,如果您添加一个答案,我会立即将其标记为官方答案。 - Contango
1
问题在于Loaded事件并不总是触发——例如当控件被重用时。 - Shahar Prish
2个回答

2
如果你想要在UI线程中等待执行某些操作,请使用Dispatcher。如何获取它?这是一个重复的问题。 如何获取UI线程的Dispatcher? 你可以使用DispatcherPriority来选择未来的时间。优先级基于UI活动,因此你不能说例如下周。但是你可以说“让我等到绑定处理完毕后再执行”,例如。

精彩的答案。 - AnjumSKhan

0

我喜欢这个版本,因为它与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();
        }));
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接