Особенности обновления пользовательского интерфейса WPF - PullRequest
2 голосов
/ 03 ноября 2011

Иллюстрация к моему общему вопросу:
Работая с WPF, я часто сталкиваюсь со следующей проблемой: перед запуском операции с большим пользовательским интерфейсом я включаю визуальный контроль обратной связи, чтобы показать, что система занята (какой-то индикатор занятости); однако пользовательский интерфейс зависает, не показывая перед ним индикатор занятости.

private void MyTree_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        this.MyBusyIndicator.IsBusy = true;
        ...
        //then some UI related operation
        ...
        this.MyBusyIndicator.IsBusy = false;
    }

(Я не могу переместить эту трудоемкую операцию в другой поток, потому что это операция, связанная с потоком пользовательского интерфейса.)
В поисках возможного решения или рекомендации по решению такой проблемы я прочитал кое-что о некоторых особенностях обновления пользовательского интерфейса WPF (или даже .NET Framework), которые являются основной причиной проблемы такого типа.

Итак, мой вопрос:
Каковы особенности механизма обновления пользовательского интерфейса WPF (или .NET Framework), , который препятствует обновлению пользовательского интерфейса в той же последовательности, как это было определено в коде?

Это действительно только для WPF?

Каковы возможные решения или обходные пути?

Это реальная проблема или, возможно, просто результат моего плохого понимания?

Ответы [ 2 ]

3 голосов
/ 03 ноября 2011

Проблема не характерна для WPF - большинство каркасов пользовательского интерфейса ведут себя одинаково.

Проблема здесь в том, что ваш пользовательский интерфейс не будет обновляться, пока поток пользовательского интерфейса не сможет обрабатывать сообщения.Пока вы выполняете работу (операции, связанные с пользовательским интерфейсом), поток пользовательского интерфейса занят и не может обрабатывать сообщения, необходимые для перерисовки экрана.В этом случае это означает, что индикатор занятости не будет перерисовываться до тех пор, пока все не будет завершено.

Реальное решение здесь - переместить операции в фоновый поток.Хотя вы говорите, что все они связаны с потоками пользовательского интерфейса, обычно есть способ выделить его части в WPF.Главное - подумать о деталях, которые занимают много времени, и переместить их, и только их, наружу.Обычно это означает загрузку ваших данных, но не установку их в пользовательском интерфейсе (т. Е. Установку привязанного свойства) до тех пор, пока он не будет полностью загружен и обработан.

Если это совершенно невозможно, единственная реальная возможность - установитьиндикатор занятости, затем используйте какой-либо механизм, чтобы оттолкнуть другую работу до тех пор, пока поток пользовательского интерфейса не сможет ее обработать.Это можно сделать с помощью Dispatcher.BeginInvoke, чтобы отодвинуть оставшуюся работу до «позже», хотя это несколько хакерски.Это будет выглядеть следующим образом:

private void MyTree_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    this.MyBusyIndicator.IsBusy = true;

    this.Dispatcher.BeginInvoke( (Action) () =>
    {
        // This is now sent to the dispatcher to process later, which gives the busy indicator a chance to refresh...

        //then some UI related operation
        this.MyBusyIndicator.IsBusy = false;
    });
}

При этом, однако, это также не всегда совершенно надежно - так как вы все еще блокируете свой поток пользовательского интерфейса.В идеале, вы никогда не должны блокировать пользовательский интерфейс, так как это предотвратит корректное выполнение перерисовок ... DispatcherTimer будет работать более надежно, но, опять же, это действительно не очень хороший способ справиться с этим.

0 голосов
/ 09 мая 2012

В .NET Вы можете использовать очередь сообщений

private void Button_Click( object sender, RoutedEventArgs e )
{
  ThreadPool.QueueUserWorkItem(
        delegate
        {
            // GUI Thread call
            this.Dispatcher.BeginInvoke( 
                    DispatcherPriority.Normal,
                    new Action( () => { this.MyBusyIndicator.IsBusy = true; }) )

            // Action in non-GUI Thread
            ... 

            // GUI Thread call
            this.Dispatcher.BeginInvoke( 
                    DispatcherPriority.Normal,
                    new Action( () => { this.MyBusyIndicator.IsBusy = false; }) )
        } );
   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...