У меня есть DataGrid, который заполняет данные из ViewModel асинхронным методом. Мой DataGrid:
<DataGrid ItemsSource="{Binding MatchObsCollection}" x:Name="dataGridParent"
Style="{StaticResource EfesDataGridStyle}"
HorizontalGridLinesBrush="#DADADA" VerticalGridLinesBrush="#DADADA" Cursor="Hand" AutoGenerateColumns="False"
RowDetailsVisibilityMode="Visible" >
Я использую http://www.amazedsaint.com/2010/10/asynchronous-delegate-command-for-your.html для реализации асинхронного способа в моей модели просмотра.
Вот мой код viewmodel:
public class MainWindowViewModel:WorkspaceViewModel,INotifyCollectionChanged
{
MatchBLL matchBLL = new MatchBLL();
EfesBetServiceReference.EfesBetClient proxy = new EfesBetClient();
public ICommand DoSomethingCommand { get; set; }
public MainWindowViewModel()
{
DoSomethingCommand = new AsyncDelegateCommand(
() => Load(), null, null,
(ex) => Debug.WriteLine(ex.Message));
_matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>();
}
List<EfesBet.DataContract.GetMatchDetailsDC> matchList;
ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC> _matchObsCollection;
public ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC> MatchObsCollection
{
get { return _matchObsCollection; }
set
{
_matchObsCollection = value;
OnPropertyChanged("MatchObsCollection");
}
}
//
public void Load()
{
matchList = new List<GetMatchDetailsDC>();
matchList = proxy.GetMatch().ToList();
foreach (EfesBet.DataContract.GetMatchDetailsDC match in matchList)
{
_matchObsCollection.Add(match);
}
}
Как вы можете видеть в моем методе Load() в моей модели ViewModel, сначала я получаю matchList (который является списком класса DataContract) из моего сервиса. Затем через цикл foreach я вставляю элементы matchList в свой _matchObsCollection (который это ObservableCollection of DataContract Class)). Теперь я получаю вышеуказанную ошибку (как показано в заголовке). "Этот тип CollectionView не поддерживает изменения в SourceCollection из потока, отличного от потока Dispatcher",
![enter image description here]()
Может ли кто-нибудь предложить мне какое-либо решение. Если возможно, мне хотелось бы знать, как связать мой DataGrid в представлении, а также обновлять его асинхронно, если есть лучший способ.
Ответ 1
Так как ваш ObservableCollection создан в потоке пользовательского интерфейса, его можно изменить только из потока пользовательского интерфейса, а не из других потоков. Это называется привязкой к потоку.
Если вам когда-либо понадобится обновлять объекты, созданные в потоке пользовательского интерфейса из другого потока, просто put the delegate on UI Dispatcher
, и это будет работать для вас делегирования его в поток пользовательского интерфейса. Это будет работать -
public void Load()
{
matchList = new List<GetMatchDetailsDC>();
matchList = proxy.GetMatch().ToList();
foreach (EfesBet.DataContract.GetMatchDetailsDC match in matchList)
{
App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE
{
_matchObsCollection.Add(match);
});
}
}
Ответ 2
Если я не ошибаюсь, в WPF 4.5 вы можете сделать это без проблем.
Теперь, чтобы решить эту проблему, вы должны использовать контекст синхронизации. Перед запуском потока вам нужно сохранить контекст синхронизации в потоке ui.
var uiContext = SynchronizationContext.Current;
Затем вы используете его в своем потоке:
uiContext.Send(x => _matchObsCollection.Add(match), null);
Взгляните на эту туточку
http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I
Ответ 3
Вы можете сделать это:
App.Current.Dispatcher.Invoke((System.Action)delegate
{
_matchObsCollection.Add(match)
});
Для .NET 4.5+: вы можете следовать ответам Даниэля. В своем примере вы даете ответчику издателю, что им нужно вызвать или вызвать в правильной теме:
var uiContext = SynchronizationContext.Current;
uiContext.Send(x => _matchObsCollection.Add(match), null);
Или вы можете поставить ответственность за свой сервис /viewmodel/whatever и просто включить CollectionSynchronization. Таким образом, если вы звоните, вам не нужно беспокоиться о том, на каком потоке вы находитесь, и по которому вы совершаете вызов. Ответственность не для издателя.
(Это может дать вам небольшую служебную нагрузку, но это делается в центральной службе, это может сэкономить вам много исключений и упростить обслуживание приложений.)
private static object _lock = new object();
public MainWindowViewModel()
{
// ...
_matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>();
BindingOperations.EnableCollectionSynchronization(_matchObsCollection , _lock);
}
Дополнительная информация: https://msdn.microsoft.com/en-us/library/system.windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx
В Visual Studio 2015 (Pro) перейдите в Debug → Windows → Threads, чтобы легко отладить и посмотреть, в каких потоках вы находитесь.
Ответ 4
В моем случае (я заполняю ObservableCollection
асинхронными задачами и не имею доступа к экземпляру App
). Я использую TaskScheduler.FromCurrentSynchronizationContext()
для очистки коллекции при сбое:
// some main task
Task loadFileTask = Task.Factory.StartNew(...);
Task cleanupTask = loadFileTask.ContinueWith(
(antecedent) => { CleanupFileList(); },
/* do not cancel this task */
CancellationToken.None,
/* run only if faulted main task */
TaskContinuationOptions.OnlyOnFaulted,
/* use main SynchronizationContext */
TaskScheduler.FromCurrentSynchronizationContext());
Ответ 5
Если вы используете BackgroundWorker, вы должны поднять событие в том же потоке пользовательского интерфейса.
Например, если у вас есть два вида A и B, а следующий код внутри A вызывает событие WakeUpEvent
//Code inside codebehind or viewmodel of A
var worker = new BackgroundWorker();
worker.DoWork += WorkerDoWork; //<-- Don't raise the event WakeUpEvent inside this method
worker.RunWorkerCompleted += workerRunWorkerCompleted; // <-- Raise the event WakeUpEvent inside this method instead
worker.RunWorkerAsync();
//Code inside codebehind or viewmodel of view B
public ViewB () {
WakeUpEvent += UpdateUICallBack;
}
private void UpdateUICallBack() {
//Update here UI element
}
Метод WorkerDoWork выполняется в потоке, который не совпадает с пользовательским интерфейсом.
Ответ 6
Я один раз столкнулся с одной проблемой и решил проблему с AsyncObservableCollection (http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/).