WPF: передача объектов между потоком пользовательского интерфейса и фоновым потоком

В моем конструкторе Window после InitializeComponents мне нужно создать объект и привязать его к сетке данных. Поскольку создание объекта занимает слишком много времени, окнам нужно время, чтобы появиться. Поэтому я решил переместить создание объекта в фоновый поток и «передать обратно» потоку пользовательского интерфейса, выполнив команду dispatcher.invoke для выполнения привязки. Но это не удается.

Странно то, что если я пытаюсь установить видимость прямоугольника внутри Dispatcher.invoke, это работает, но DataGrid.setbinding - нет! Любые идеи? Я пробовал то же самое с фоновым рабочим и threadstart, но все равно получаю ту же ошибку. Я не могу получить доступ к объекту DataGrid, хотя это происходит внутри делегата вызова диспетчера. Я уверен, что что-то упустил в своем понимании того, как это работает. Любые предложения будут ценны. Спасибо!

StartupDelegate s = new StartupDelegate(CreateModel);
s.BeginInvoke(delegate(IAsyncResult aysncResult) { s.EndInvoke(aysncResult); }, null);

internal CreateModel()
{
    Model d = new Model();
    Dispatcher.Invoke( DispatcherPriority.Normal, 
                       new Action<Model>(
                          delegate(Model d1)
                          {
                              mModel = d1;   // mModel is a property defined in Window
                              Binding b = new Binding();
                              b.Source = mModel; 
                              MainDataGrid.SetBinding(TreeView.ItemsSourceProperty, mainb); // << dies here with - The calling thread cannot access this object because a different thread owns it.
                          }            
}

ОБНОВЛЕНИЕ: закончилось использование диспетчера таймера, который запускался всего один раз. Помещение кода привязки в его делегат Tick сработало. Но мне все еще любопытно, почему приведенный выше код не работает.


person Sharun    schedule 25.07.2009    source источник


Ответы (2)


Я бы предложил другой способ.

Связывание не следует вызывать из кода, вместо этого вы должны определить его в XAML.

Вы можете добавить еще одно DependencyProperty типа Model в свое окно, назвать его CurrentModel и установить для него начальное значение NULL. Похоже, у вас уже есть свойство под названием mModel, это DependencyProperty?

Вы можете определить привязку CurrentModel к DataGrid или другому элементу управления в XAML.

И в вашем конце делегата Dispatcher.Invoke должен устанавливать только CurrentModel, привязка будет выполняться автоматически.

person Akash Kava    schedule 25.07.2009
comment
Я пробовал это не в xaml, а просто установил привязку к свойству со свойством, установленным в null. Таким образом, у делегата была только одна строка mModel = d1. Но ничего не произошло. Это не свойство зависимости, а обычное свойство, реализующее notifypropertychanged. - person Sharun; 25.07.2009
comment
Я сомневаюсь, потому что, если объект является производным от DependencyObject (ваше окно), INotifyPropertyChanged не работает, даже у меня была аналогичная проблема, поэтому я изменил его на DependencyProperty. - person Akash Kava; 25.07.2009

В какой экземпляр диспетчера вы звоните Invoke?

Я предполагаю, что это диспетчер из фонового потока, который выполняет CreateModel, а не из потока пользовательского интерфейса.

DataGrid - это элемент управления, производный от DispatcherObject. Каждый такой объект предоставляет диспетчер своего потока-владельца через свое свойство Dispatcher, которое вы должны использовать для вызова методов в элементе управления.

Смена диспетчера в звонке должна работать:

internal CreateModel()
{
    Model d = new Model();

    // Invoke the action on the dispatcher of the DataGrid
    MainDataGrid.Dispatcher.Invoke( DispatcherPriority.Normal, 
                       new Action<Model>(
                          delegate(Model d1)
                          {
                              mModel = d1;   // mModel is a property defined in Window
                              Binding b = new Binding();
                              b.Source = mModel; 
                              MainDataGrid.SetBinding(TreeView.ItemsSourceProperty, mainb);
                          }            
}

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

Обновление: я только что понял, что это метод экземпляра вашего элемента управления, поэтому экземпляр диспетчера, который вы используете, является правильным. Вот вам и ответ поздно ночью. Кроме того, ваш код работает для меня, заменяя вашу модель на IEnumerable. Есть ли что-то особенное в вашей модели?

person Mathieu Garstecki    schedule 10.08.2011