Мгновенное обновление связанного свойства WPF - PullRequest
2 голосов
/ 15 ноября 2011

У меня есть текстовое поле в окне, которое сообщает о состоянии и / или успешном действии. Он привязан к свойству в ViewModel.

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

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

public class ViewModel : INotifyPropertyChanged
{
        private string _report;
        public string Report
        {
            get { return _report; }
            set
            {
                _report = value;
                RaisePropertyChanged("Report");
            }
        }

        public void DoHeavyAction()
        {
            Report += "Heavy action readying";

            ReadyHeavyAction();

            Report += "Heavy action starting";

            var result = DoTheHeavyAction();

            if(!result.success)
            {
                report += "Heavy action failed";
                return;
            }

            Report += result.value;
        }
}

В этом случае программа отстает на 2 секунды, а затем появляется все:

Heavy action readying
Heavy action starting
Heavy action failed

Вместо того, чтобы появляться один за другим.

Можно ли каким-то образом использовать диспетчер для обновления представления (обратите внимание, что я использую MVVM).

Ответы [ 3 ]

2 голосов
/ 15 ноября 2011

Вы можете переместить «тяжелую работу» в фоновую задачу, чтобы удалить ее из потока пользовательского интерфейса.

var task = Task.Factory.StartNew(() => DoHeavyAction());
Report += "Heavy Action Starting";
task.Wait();
2 голосов
/ 15 ноября 2011

Да, это легко. Ваша тяжелая операция выполняется в отдельном потоке, и вы сообщаете о прогрессе как

Dispatcher.Invoke((Action)(() => {
    Report.value = "Started";
}));

Edit:
В реальном случае операция активируется с помощью ICommand. Указанный ICommand отправляется из пользовательского интерфейса, поэтому он входит в поток пользовательского интерфейса. Таким образом, проблема в том, что пользовательский интерфейс (и его обновление) заблокирован из-за тяжелой операции.

Решением будет запуск нового потока по прибытии команды и выполнение работы там. Пользовательский интерфейс может быть обновлен с помощью кода, как в примере выше.

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

1 голос
/ 15 ноября 2011

Почему бы вам не использовать BackgroundWorker и настроить событие ProgressChanged?

Пример:

var worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.ProgressChanged += WorkerReportProgress;
...

private static void WorkerReportProgress(object sender, ProgressChangedEventArgs e) {
    report += e.UserState.ToString();
}

и в DoWork сделать что-то вроде

private static void DoHeavyAction(object sender, DoWorkEventArgs e) {
    var worker = (BackgroundWorker)sender;
    worker.ReportProgress(0, "Heavy action readying");
    ReadyHeavyAction();
    ...
...