Кнопка привязки не включается после завершения фонового рабочего процесса

#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;
    }
}
 

Я тестировал это, когда окно не является активным окном и когда пользователь покидает окно как выбранное. Метод фокусировки, похоже, решает эту проблему, когда окно неактивно. Наша команда контроля качества не провела полного тестирования пользовательского интерфейса, поэтому я не могу сказать, есть ли какие-либо ситуации, когда это не работает, но, похоже, это лучшее решение, которое я придумал на сегодняшний день.

Я открыт для любых других предложений, если у кого-нибудь есть лучшее представление о том, что может быть причиной того, что кнопка не обновляется.