WPF:在附加属性中,如何等到可视树正确加载?
我在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的回答,“使用调度程序并选择适当的优先级”。 这非常出色:
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类可用于扫描可视树。
有关处理可视树上下扫描的示例代码,请参阅:
- DevExpress:在附属物中,如何等到我们100%确定DevExpress已经完成构建其Visual Tree?
- DevExpress以及如何确保LayoutPanel总是在其他所有方面?
更新3
如果您在附加属性中,可靠加载可视树的唯一方法是在Loaded
事件处理程序中或之后执行代码。 如果我们不知道这个限制,那么一切都将间歇性地失败。 等待OnLoaded
事件发生后,等待尝试引入其他随机forms的延迟的任何其他方法都要好得多,如上所述。
如果您使用的是DevExpress,则更为关键:在某些情况下,在Loaded
事件之前尝试执行任何操作都会导致崩溃。
例如: – 在Loaded
事件之前调用window.Show()
在某些情况下导致崩溃。 – 在Loaded
事件导致某些情况下崩溃之前,挂钩IsVisibleChanged
的事件处理程序。
免责声明:我与DevExpress无关,它是许多优秀的WPF库之一,我也推荐使用Infragistics。
如果您想等待在UI线程中执行某些操作,请使用Dispatcher 。 你怎么抓住它? 这是一个骗局。
如何获取UI线程Dispatcher?
您可以使用DispatcherPriority选择将来的时间。 优先级基于UI活动,因此您不能说,例如,下周。 但是你可以说“让我等到绑定处理完毕后”,例如。
我更喜欢这个版本,因为它可以更好地使用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(); })); } }