Замена ссылки на объект связанного свойства с помощью BackgroundWorker - PullRequest
2 голосов
/ 22 декабря 2010

Небольшой фон

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

Некоторые сведения оструктура потоков

У меня есть два объекта "хоста", один инициирует структуры подключаемых модулей, в конечном итоге предоставляет два представления: представление доступных подключаемых модулей и (если был выбран подключаемый модуль) представлениевсех атрибутов, известных одному плагину.Другой в конечном итоге запускает STA-поток и запускает главное окно в приложении WPF.Второй хост использует BackgroundWorkers для выбора, инициализации, обновления и отправки задач.События / делегаты DoWork (я полагаю, что они называются делегатами) определены в классе ViewController, который находится в структуре MVVM и предоставляет функциональный интерфейс для обновления и т. Д. И реализует интерфейс INotifyPropertyChanged.

Некоторыеподробнее

При создании главного окна его контекст устанавливается на объект контроллера представления.Затем графический интерфейс привязывает два списка к обоим представлениям.

Проблема

Когда я вызываю метод selectPlugin из потока пользовательского интерфейса, он останавливается до завершения операции подключенияи каждый отдельный атрибут загружается в список, содержащийся в ViewModel.Однако это работает, и после этого привязка источника элементов ListBox обновляется и отображаются все атрибуты.

Однако я не хочу, чтобы пользовательский интерфейс зависал при каждой операции, поэтому я реализовал фоновые рабочие.Все работает нормально, объекты обновляются, а источник привязки заменяется новым экземпляром View.Но сама привязка не обновляется.

Я пытался выделить различные решения, используя событие Dispatcher.Invoke или DoWorkComplete в потоке пользовательского интерфейса.Я обнаружил, что само событие PropertyChanged остается нулевым, используя следующий установщик свойств:

    public IAttributesViewModel AttributesView
    {
        get 
        {
                StaticLogger.WriteDebugLog(log,
                                           String.Format("Executing {0}",
                                                         System.Reflection.MethodBase.GetCurrentMethod()));
                return _currentAttributes;
        }
        set 
        {
            _currentAttributes = value;
            OnPropertyChanged("AttributesView");
        }
    }

Реализация INotifyPropertyChanged выглядит следующим образом:

    #region INotifyPropertyChange implementation
    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
    /// <summary>
    /// Called when [property changed].
    /// </summary>
    /// <param name="name">The name.</param>
    protected void OnPropertyChanged(string name)
    {
        StaticLogger.WriteDebugLog(log, String.Format("Executing {0}", System.Reflection.MethodBase.GetCurrentMethod()));
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
    #endregion

Событие PropertyChanged, как объявлено выше, всегда равно нулю,Это может или должно иметь какое-то отношение к фактическому связыванию в XAML, однако я установил de datacontext главного окна в потоке пользовательского интерфейса следующим образом:

    private static void Host(object viewControler)
    {
        var controler = viewControler as IViewController;
        _instance._main = new MainWindow(controler) { DataContext = controler };
        var gui = new App();
        if (_instance._main == null) throw new ArgumentNullException("viewControler");
        gui.Run(_instance._main);
    }

Я использую экземпляр Singleton с хоста пользовательского интерфейсаобъект, в случае, если это имеет значение.

В XAML я использую пользовательское свойство зависимости для UserControll, содержащее AttributesListbox и некоторый код стиля.Фактический ListBox привязывается к этому свойству для своего собственного свойства ItemSource.Не думайте, что это должно отличаться от непосредственного использования списка, но только в том случае, если это вызывает проблему с событием PropertyChanged, равным нулю, и реализуется следующим образом:

C # /// /// Логика взаимодействия для AttributesListBox.xaml /// открытый частичный класс AttributesListBox: UserControl {частная статическая только для чтения UIPropertyMetadata _sourceMetadata = new UIPropertyMetadata (String.Empty, SourceChangedCallback) 10 1045 * 10 10 * 10 * 10 * 10 * 10 * 10* XAML

<ListBox Name="List" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" ItemsSource="{Binding Source}" BorderBrush="{x:Null}" />

Первый вопрос (ы)

Что плохого в том, как я использую объект ViewController в сочетании с пользовательским интерфейсом?И, таким образом, почему событие PropertyChanged всегда равно null?Это должно означать, что я где-то испортил процесс привязки, не так ли?

Второй вопрос (ы)

Как получить событие OnPropertyChanged для уведомления пользовательского интерфейсанить, специфичность привязки в UserControll (AttributesListbox)?

По какому маршруту проходит мероприятие?

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

Я не могу разобраться с событием пузыри / туннелирования, это событие ограничено потоком, который создал экземпляр, или потоком, который вызвал определенный метод (с использованием диспетчера или чего-то еще)?

Гипотеза

Я подозреваю, что в моем случае есть две проблемы: 1. Обработчик PropertyChanged остается нулевым, поскольку объект либо не связан, либо потому, что он не создан в правильном потоке. 2. Событие, если оно действительно запущено, никогда не достигает нужного потока, поскольку оно застревает либо в потоке BackgroundWorker, либо во внутреннем потоке «хоста».

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

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

Ответы [ 2 ]

0 голосов
/ 22 декабря 2010

Самое замечательное в задании вопроса - это то, что он заставляет вас по-настоящему думать о причинах вашей проблемы, поэтому, сделав небольшой перерыв, вы, наконец, через два дня найдете совершенно простое решение.

Проблема заключалась в ParameterizedThreadStart, который запрашивал метод void (объект arg), поэтому компилятор не проверял тип, пока вы передаете объект. Я передал неправильный объект, потому что я отредактировал метод. Безопасное приведение типов с использованием оператора «as» кажется болезненным, похоже, что оно проглатывает исключение CastingException, поэтому не сообщает вам о маленькой ошибке.

Итак, простое решение:

private static void Host(object viewControler)
{
    var controler = viewControler as IViewController;
    _instance._main = new MainWindow(controler) { DataContext = controler };
    var gui = new App();
    if (_instance._main == null) throw new ArgumentNullException("viewControler");
    gui.Run(controler);
}

Я не ожидал, что мой BackgroundWorker будет работать на 100% "из коробки", но, похоже, это так. Является ли мой код «наилучшей практикой» - это отдельный вопрос.

Ключ к тому, чтобы я думал в этом направлении, был: Краткий пример: плохая и хорошая практика для привязки данных и инициализации объекта

При работе с DataBindings в visaul studio также ознакомьтесь с этой небольшой статьей: Как показать трассировку WPF

0 голосов
/ 22 декабря 2010

Я думаю, что вы, возможно, испортили привязку вашего списка, а именно (сокращено для простоты):

<ListBox ItemsSource="{Binding Source}" />

эта привязка означает «искать свойство с именем Source в моем текущем DataContext», что, как я подозреваю, не соответствует вашим намерениям. Если вы хотите привязать к свойству Source из AttributesListBox (которое, я полагаю, содержит указанный выше список), вы должны сделать это следующим образом:

<ListBox ItemsSource="{Binding Source, RelativeSource={RelativeSource AncestorType={x:Type AttributesListBox}}}" />

, что означает - «искать свойство с именем Source в первом объекте вверх по дереву, имеющего тип AttributesListBox». Конечно, вы все равно должны привязать это свойство Source к правильному объекту вашего контроллера, но я полагаю, вы это сделали.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...