171 votes

Ce type de CollectionView ne prend pas en charge les modifications apportées à sa SourceCollection à partir d'un thread différent du thread du Dispatcher.

J'ai une grille de données qui remplit des données à partir du ViewModel par une méthode asynchrone :

<DataGrid ItemsSource="{Binding MatchObsCollection}"  x:Name="dataGridParent" 
                      Style="{StaticResource EfesDataGridStyle}" 
                      HorizontalGridLinesBrush="#DADADA" VerticalGridLinesBrush="#DADADA" Cursor="Hand" AutoGenerateColumns="False" 
                      RowDetailsVisibilityMode="Visible"  >

J'utilise http://www.amazedsaint.com/2010/10/asynchronous-delegate-command-for-your.html pour mettre en œuvre une méthode asynchrone dans mon modèle de vue.

Voici le code de mon 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);
            }

        }

Comme vous pouvez le voir, dans la méthode Load() de mon ViewModel, je récupère d'abord matchList (qui est une liste d'une classe de contrat de données) à partir de mon service, puis, par une boucle foreach, j'insère les éléments de ma matchList dans ma _matchObsCollection (qui est une ObservableCollection de la classe de contrat de données)). enter image description here

De plus, si possible, j'aimerais savoir comment lier ma grille de données dans la vue et la rafraîchir de manière asynchrone, s'il existe un meilleur moyen.

271voto

Rohit Vats Points 36234

Puisque votre ObservableCollection est créée sur le thread UI, vous ne pouvez la modifier que depuis le thread UI et non depuis d'autres threads. C'est ce qu'on appelle affinité de fil .

Si vous avez besoin de mettre à jour des objets créés sur le thread de l'interface utilisateur à partir d'un thread différent, il suffit de put the delegate on UI Dispatcher et qui fera le travail pour vous en le déléguant au thread UI. Cela fonctionnera -

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

71voto

Daniel Points 825

Si je ne me trompe pas, dans WPF 4.5, vous devriez pouvoir le faire sans problème.

Pour résoudre ce problème, vous devez utiliser le contexte de synchronisation. Avant de lancer le thread, vous devez stocker le contexte de synchronisation dans le thread de l'interface utilisateur.

var uiContext = SynchronizationContext.Current;

Puis vous l'utilisez dans votre fil :

uiContext.Send(x => _matchObsCollection.Add(match), null);

Jetez un coup d'œil à ce tutoriel http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I

61voto

juFo Points 3779

Vous pouvez le faire :

App.Current.Dispatcher.Invoke((System.Action)delegate
             {
               _matchObsCollection.Add(match)
             });

Pour .NET 4.5+ : Vous pouvez suivre la réponse de Daniel. Dans son exemple, vous donnez la responsabilité à l'éditeur qu'il doit appeler ou invoquer sur le bon thread :

var uiContext = SynchronizationContext.Current;
uiContext.Send(x => _matchObsCollection.Add(match), null);

Ou bien vous pouvez confier la responsabilité à votre service/modèle de vue/autre et simplement activer la synchronisation des collections. De cette façon, si vous faites un appel, vous n'avez pas à vous préoccuper de savoir sur quel thread vous êtes et sur lequel vous faites l'appel. La responsabilité n'incombe plus à l'éditeur. (Cela peut vous donner une petite surcharge de performance mais en faisant cela dans un service central, cela peut vous épargner beaucoup d'exceptions et vous donner une maintenance d'application plus facile).

private static object _lock = new object();

public MainWindowViewModel()
{
    // ...
    _matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>();
    BindingOperations.EnableCollectionSynchronization(_matchObsCollection , _lock);
} 

Plus d'informations : https://msdn.microsoft.com/en-us/library/system.Windows.data.bindingoperations.enablecollectionsynchronization(v=vs.110).aspx

Dans Visual Studio 2015 (Pro), allez dans Débogage --> Windows --> Fils de discussion pour déboguer facilement et voir sur quels fils vous vous trouvez.

8voto

mnyarar Points 106

J'ai rencontré le même problème une fois et je l'ai résolu avec AsyncObservableCollection ( http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/ ).

4voto

Istvan Heckl Points 614

J'ai trouvé une solution ici : https://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/ Il suffit de créer une nouvelle classe et de l'utiliser à la place de ObservableCollection. Cela a fonctionné pour moi.

public class AsyncObservableCollection<T> : ObservableCollection<T>
{
    private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;

    public AsyncObservableCollection()
    {
    }

    public AsyncObservableCollection(IEnumerable<T> list)
        : base(list)
    {
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (SynchronizationContext.Current == _synchronizationContext)
        {
            // Execute the CollectionChanged event on the current thread
            RaiseCollectionChanged(e);
        }
        else
        {
            // Raises the CollectionChanged event on the creator thread
            _synchronizationContext.Send(RaiseCollectionChanged, e);
        }
    }

    private void RaiseCollectionChanged(object param)
    {
        // We are in the creator thread, call the base implementation directly
        base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (SynchronizationContext.Current == _synchronizationContext)
        {
            // Execute the PropertyChanged event on the current thread
            RaisePropertyChanged(e);
        }
        else
        {
            // Raises the PropertyChanged event on the creator thread
            _synchronizationContext.Send(RaisePropertyChanged, e);
        }
    }

    private void RaisePropertyChanged(object param)
    {
        // We are in the creator thread, call the base implementation directly
        base.OnPropertyChanged((PropertyChangedEventArgs)param);
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X