WPF: в прикрепленном свойстве, как дождаться правильной загрузки визуального дерева?

У меня есть прикрепленное свойство в приложении WPF.

Код ниже находится внутри события OnLoad, но он не работает, если я не добавлю хакерскую задержку в 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 полезен для сканирования визуального дерева вверх и вниз.

Пример кода, который имеет дело со сканированием вверх и вниз по визуальному дереву, см.:

Обновление 3

Если вы находитесь в присоединенном свойстве, единственный способ надежно загрузить визуальное дерево — выполнить код в обработчике событий Loaded или после него. Если мы не знаем об этом ограничении, то периодически все будет давать сбои. Ожидание, пока не сработает событие OnLoaded, намного лучше любого другого метода, который пытается ввести другие случайные формы задержек, как отмечалось выше.

Если вы используете DevExpress, это еще более важно: попытка сделать что-либо до события Loaded может при некоторых обстоятельствах привести к сбою.

Например:

  • Вызов window.Show() до события Loaded в некоторых случаях приводил к сбою.
  • Подключение к обработчику событий для IsVisibleChanged до события Loaded в некоторых случаях приводило к сбою.

Отказ от ответственности: я не связан с DevExpress, это одна из многих хороших библиотек WPF, я также рекомендую Infragistics.


person Contango    schedule 16.03.2016    source источник
comment
Воспользуйтесь диспетчером и выберите соответствующий приоритет.   -  person    schedule 16.03.2016
comment
@Уилл Великолепно! Какой приоритет лучше выбрать?   -  person Contango    schedule 16.03.2016
comment
Не знаю, без проверки. Похоже, вы ждете завершения привязки, поэтому попробуйте что-нибудь с более низким приоритетом, чем Binding.   -  person    schedule 16.03.2016
comment
Отлично, DispatcherPriority.ApplicationIdle работает отлично.   -  person Contango    schedule 16.03.2016
comment
@Will Это сработало отлично, если вы добавите ответ, я сразу же отмечу его как официальный.   -  person Contango    schedule 16.03.2016
comment
Проблема в том, что событие Loaded не всегда срабатывает, например, при повторном использовании элемента управления.   -  person Shahar Prish    schedule 01.03.2018


Ответы (2)


Если вы хотите подождать, чтобы сделать что-то в потоке пользовательского интерфейса, используйте Диспетчер. Как вы поймаете это? Это обман.

Как получить диспетчер потоков пользовательского интерфейса?

Вы можете использовать DispatcherPriority для выбора времени в будущем. Приоритеты основаны на действиях пользовательского интерфейса, поэтому вы не можете сказать, например, на следующей неделе. Но вы можете, например, сказать: «Позвольте мне подождать, пока не будут обработаны привязки».

person Community    schedule 16.03.2016
comment
Замечательный ответ. - person AnjumSKhan; 23.03.2017

Я предпочитаю эту версию, потому что она лучше работает с 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();
        }));
    }
}
person Dbl    schedule 19.04.2016