#wpf #multithreading #backgroundworker
#wpf #Многопоточность #BackgroundWorker
Вопрос:
У меня есть фоновый рабочий процесс, который запускает подготовку нового клиента для нашей системы. Вот как выглядит метод DoWork:
ProvisioningManager manager = new ProvisioningManager(false)
{
};
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
this.MaxSteps = manager.MaxProgress;
}));
manager.StatusUpdated = new ProvisioningManager.StatusUpdatedHandler(manager_StatusUpdated);
manager.TaskCompleted = new ProvisioningManager.TaskCompleteHandler(manager_TaskCompleted);
manager.ProvisionClient();
while (!manager.Completed)
{
System.Threading.Thread.Sleep(100 * 60);
}
По сути, он создает менеджера, который обрабатывает общение с различными подсистемами, предоставляющими клиенту.
Теперь у меня есть событие обновления статуса и завершенное событие для диспетчера подготовки. Когда срабатывает событие TaskCompleted, я хочу иметь возможность установить свойство для моего отображаемого объекта, чтобы кнопка завершения в мастере была включена:
void manager_TaskCompleted(object sender, ProvisioningManager.Task taskType)
{
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
this.ProvisioningComplete = true;
}));
}
XAML для кнопки выглядит следующим образом:
<wizard:WizardPage Header="Provisioning Client..."
ShowBack="False"
AllowBack="False"
AllowFinish="{Binding Source={StaticResource ResourceKey=dataObject}, Path=ProvisioningComplete}"
Loaded="Provisioning_Loaded">
</wizard:WizardPage>
Это не работает. Несмотря на то, что я обязательно нажимаю на поток диспетчера, чтобы установить свойство отображаемого объекта, он фактически не меняет кнопку на включенную, пока я не нажму на окно. Это ошибка в AvalonWizard или я не в правильном потоке для установки INotifyPropertyChanged? Есть ли способ взломать это; в принципе, я могу программно сфокусировать окно без щелчка мыши?
Я устал размещать этот цикл while в методе DoWork, чтобы я мог использовать метод завершения BackgroundWorker:
void provisioningWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
this.ProvisioningComplete = true;
}));
}
Это тоже не работает. Что дает ?!
Обновление Здесь запрошено создание экземпляра статического ресурса для отображаемого объекта:
<Window.Resources>
<ObjectDataProvider x:Key="dataObject" ObjectType="{x:Type winDO:NewClientWizardDO}" />
</Window.Resources>
Обновление II
Вот средство запуска изменения свойств и свойств:
public bool ProvisioningComplete
{
get { return this._ProvisioningComplete; }
set
{
this._ProvisioningComplete = value;
this.NotifyPropertyChanged("ProvisioningComplete");
}
}
protected void NotifyPropertyChanged(params string[] propertyNames)
{
if (this.PropertyChanged != null)
{
foreach (string propertyName in propertyNames)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Комментарии:
1. Не могли бы вы опубликовать объявление вашего статического ресурса «StaticResource ResourceKey=DataObject»?
2. @Murven, запрошенный код добавлен в вопрос. Спасибо, что изучили эту проблему.
3. Я предполагаю, что NewClientWizardDO реализует INotifyPropertyChanged и что свойство ProvisioningComplete запускает PropertyChangedEvent . Не могли бы вы подтвердить или опубликовать код для этого класса? Извините, я тоже должен был попросить об этом, просто трудно сказать, не будучи уверенным в этом.
4. Возможно, я нашел исправление для этого. Я добавил немного кода в представление, которое использует событие PropertyChanged для отображаемого объекта. Когда он обнаруживает, что ProvisioningComplete изменился, и это правда, я получаю страницу мастера и вызываю «UpdateLayout». Кажется, это работает, но я еще не протестировал его полностью.
5. Извините за двойной пост, но «UpdateLayout» не сработал. Я думал, что это так, но, возможно, я перешел с одного экрана, а затем вернул фокус к окну, из-за которого кнопка была включена. Все еще застрял на этом…
Ответ №1:
извините, если я чего-то не понимаю, но свойство ProvisioningComplete помечено как «volatile»? Если нет, то это может быть проблемой.
Комментарии:
1. Я не уверен, что вы имеете в виду, отмечая что-то изменчивое. Это разметка привязки или атрибут свойства?
2. пример и краткое объяснение приведены на msdn.microsoft.com/it-it/library/x13ttww7 (v= против 80).aspx
3. Кажется, это имеет смысл, но, увы, при отсутствии активности пользователя на экране кнопка не меняет состояния с отключенного на включенное даже после добавления ключевого слова volatile в поле позади свойства. Я начинаю думать, что это может быть ошибка AvalonWizard.
Ответ №2:
Поэтому я не мог точно выяснить, почему у меня возникла эта проблема. Я попытался установить фокус на окно, кнопку и т.д. Я пробовал несколько способов сообщить представлению, что viewmodel обновилась. В основном каждое предложение, которое я мог найти в Интернете, не сработало. Это почти похоже на ошибку.
Один умный человек из моей команды предложил подделать щелчок мышью по окну. Его идея заключалась в том, что, поскольку для активации кнопки требовалось всего лишь простое нажатие мыши на экране, подделка должна иметь тот же эффект. Я думал (и думаю), что этот взлом был смешным. Я попробовал это, просто чтобы посмотреть, могу ли я назвать это «решением».
Что ж, это сработало. У нас была такая же проблема в другом из наших мастеров (не AvalonWizard, а доморощенном). Я думаю, что должна быть какая-то основная проблема с тем, как окно перерисовывается после того, как фоновый поток обновляет объекты, привязанные к пользовательскому интерфейсу.
В любом случае, я нашел способ решить эту проблему с помощью следующего хакерского кода.
//import user32.dll and setup the use of the mouse_event method
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
/// <summary>
/// Watches for properties to change on the data object, mainly the ProvisioningComplete method
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void DataObject_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "ProvisioningComplete":
//if the provisioning is completed then we need to make the finish button selectable.
if (this.DataObject.ProvisioningComplete)
{
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
//give the window focus
this.Focus();
//update the layout
WizardPageProvisioningClient.UpdateLayout();
//fake mouse click 50 pixels into the window
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, (uint)(this.Left 50), (uint)(this.Top 50), 0, 0);
}));
}
break;
}
}
Я тестировал это, когда окно не является активным окном и когда пользователь покидает окно как выбранное. Метод фокусировки, похоже, решает эту проблему, когда окно неактивно. Наша команда контроля качества не провела полного тестирования пользовательского интерфейса, поэтому я не могу сказать, есть ли какие-либо ситуации, когда это не работает, но, похоже, это лучшее решение, которое я придумал на сегодняшний день.
Я открыт для любых других предложений, если у кого-нибудь есть лучшее представление о том, что может быть причиной того, что кнопка не обновляется.