Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update ObservableCollection from background Worker

I have a DatGrid, which is bound to var Result_Full = new ObservableCollection<IP_DataRow>(). This is a simple class containing several string & double variables. Nothing difficult.

What I do, is that I read an Excel File (with Telerik RadSpreadProcessing), which parses rows into my class. I do this on a thread so that the UI is not blocked. I have encountered a few problems though:

1) I cannot use ref keyword in a long process which reads excel file (because Result_Full is a public property bound to a DataGrid), but I have to create temporary ObservableCollection<IP_DataRow>(), where the values are placed. Once the process has finished I run the following script for copying the values:

        foreach (var item in tmpFull)
        {
            InvokeOnUIThread(() =>
            {
                Result_Full.Add(item);
            });
        }

What I would like to do, is to be able to see in a real time (if possible) how items are being added to the collection in my DataGrid.

As I am using .Net 4.5 I tried to implement BindingOperations.EnableCollectionSynchronization as was suggested by some other post, yet I could not figure out how to bind my UI bould collection Result_Full to temporary used in a process.

2) Even with the current setup, when (under my UI) I move to my Tab which contains DataGrid (my DataGrid is on a different TabPage), and I try to add new item to the collection with the above mentioned code, it returns an error saying: The calling thread cannot access this object because a different thread owns it., which is rather weird, as InvokeOnUIThread is nothing else but Dispatcher.Invoke(), which should be thread safe?

Any help would be highly appreciated.

EDIT: Showing more code:

This is the process I call from BackgroundWorker:

    public void ProcessFile()
    {
        var tmpError = new ObservableCollection<IP_DataRow>();
        var tmpFull = new ObservableCollection<IP_DataRow>();

        var _reader = new IP_ExcelReader(sExcelPath, ref tmpError, ref tmpFull);
        string sResult = _reader.ReadExcelFile();
        if (sResult != string.Empty)
        {
            System.Windows.MessageBox.Show("Error processing selected Excel File!" + Environment.NewLine + Environment.NewLine + "Error message:" + Environment.NewLine + sResult);
        }

        foreach (var item in tmpError)//populates error list
        {
            IP_InvokeOnUIThread(() =>
            {
                Result_Error.Add(item);
            });
        }

        foreach (var item in tmpFull)//populates full list
        {
            IP_InvokeOnUIThread(() =>
            {
                Result_Full.Add(item);
            });
        }

        OnPropertyChanged("Result_Full");
        //OnPropertyChanged("Result_Error");

        iSelectedTabIndex = 1;

    }

Here you can see, that I have to create temporary collection tmpError, tmpFull where I gather my data. At the end of process, I manually copy values into my main collections bound to DataGrid. I would like to change this, meaning that values are copied to the main collection (not temporary ones) during the process, so that user can see in a real time how values are added to the collection.

P.S.2: for uknown reason to me, one of the problems lied in my InvokeOnUIThread call. Once I changed from App.Current.Dispatcher.Invoke(action); to App.Current.Dispatcher.BeginInvoke(action); error with ..different thread owns it stopped.

like image 715
Robert J. Avatar asked Sep 17 '15 14:09

Robert J.


1 Answers

  1. You can use BackgroundWorker instead of thread, to report progress as it goes. Here is a simple tutorial
  2. I believe that simply calling Dispatcher will use thread of a context, which is not UI-thread in your case. Try Application.Current.Dispatcher instead

In short, I believe you should do the following:

  1. Create public ObservableCollection in UI-thread and bind it to DataGrid
  2. Create a background worker. Set reporting to true. Subscribe to ReportProgress and DoWork events.
  3. Run worker async
  4. In DoWork handler create a list and read some amount of values to it. As you reach some amount, let's say a hundred, call (sender as BackgroundWorker).ReportProgress method, passing in event args this collection you have populated.
  5. In report progress handler, populate your ObservableCollection from a list you've passed throught event args.
  6. Steps 4 - 5 are repeated until everything is done
like image 166
netaholic Avatar answered Oct 23 '22 03:10

netaholic