Search code examples
c#wpfmvvm-lightobservablecollectionfody-propertychanged

Update View in "Realtime" with data from ObservableCollection in MVVMLight


I'm writing a sort algorithm visualizer in C#/WPF using MVVMLight and Fody. In my ViewModel I have an observable collection like this:

public ObservableCollection<int> NumbersCollection{ get; set; }

Now in a command (method) I change the contents of the collection (in this case I'm doing a bubble sort). Like this:

        IMutableSortAlgorithm sorter = ServiceLocator.Current.GetInstance<IMutableSortAlgorithm>();
        sorter.MuatbleSort(this.NumbersCollection);

        while (!sorter.Finished())
        {
            sorter.NextStep();                   
            this.RaisePropertyChanged("NumbersCollection"); // not neccesary   
            Thread.Sleep(400);
        }

After a call of sorter.NextStep() the collection is changed. After each step I tried to update the View with calling RaisePropertyChanged and sleeping for 400ms.

So I basically want the view to update after EVERY change (step), but the View is only updated after the method is finished. (And for that I don't even need to call RaisePropertychanged...)

I could use a background worker for convenience. But I first want to solve the problem without an additional worker thread. Since I'm in the main GUI thread, there's no need for calling the Dispatcher.Invoke, right? (I tried and it didn't work anyway..).

Does somebody have a clue how to refresh the UI after each step?

EDIT: sorter.NextStep() does not insert or remove items to the list, but it swaps values. Like col[i] = col[j] ... you know - sorting stuff. I want to get that changes in realtime on the UI.


Solution

  • The collection in the UI thread which is updating/sleeping and never processes the property changed events.

    The collection needs to be updated in a separate thread. Task.Run, BackgroundWorker, async Action etc.

    That'll get you half way, resulting in an error "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread."

    Some googling found a thread safe observable collection: Where do I get a thread-safe CollectionView?

    Decided to randomize the digits instead of sorting them and this works for me

        public MTObservableCollection<int> NumbersCollection
        {
            get { return _item.NumbersCollection; }
        }
    
        public ICommand RandomizeCommand
        {
            get
            {
                if (_randomizeCommand == null)
                    _randomizeCommand = new RelayCommand(() =>
                    {                        
                        Task.Run(()=>
                        {
                            for (int i = 0; i < 10; i++)
                            {
                                _dataService.Randomize();                                
                                Thread.Sleep(TimeSpan.FromSeconds(3));
                            }
                        });                        
                    });
    
                return _randomizeCommand;
            }
        }
    

    EDIT: Another approach (which I just learned from) Insert delay/wait in code c#

    Leverage DispatcherHelper.RunAsync() await Task.Delay to run in the UI thread without blocking and you can use a regular Observable collection instead of the thread safe one.

        public  ObservableCollection<int> NumbersCollection
        {
            get { return _item.NumbersCollection; }
        }
    
        public ICommand RandomizeCommand
        {
            get
            {
                if (_randomizeCommand == null)
                    _randomizeCommand = new RelayCommand(() =>
                    {
                        //Task.Run(() =>
                        DispatcherHelper.RunAsync(async () =>
                        {
                            for (var i = 0; i < 10; i++)
                            {
                                _dataService.Randomize();
                                //Thread.Sleep(TimeSpan.FromSeconds(3));
                                await Task.Delay(TimeSpan.FromSeconds(3));
                            }
                        });
                    });
    
                return _randomizeCommand;
            }
        }