Search code examples
c#wpfmultithreadingdatagriddispatcher

Update the ItemSource of a Datagrid from another thread


I've seen many of these types of questions asked and answered here but none of those seems to solve my problem.

I have a Page that retrieves and shows a list of data from a database. my initial code looked like this

   private void HistoryPage_OnLoaded(object sender, RoutedEventArgs e)
    {
       //''''''
        _invoices = Invoice.GetAll(); // returns a list of invoices
         InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage);
        DgInvoices.ItemsSource = InvoiceList.CurrentItems;
      //''''''''' 
    }

this worked ok until the data list got bigger. now it takes about 6-8 seconds for this operation. then I tried to fetch data from a different thread and update the Datagrid ( DGInvoices ) from there.

   private void HistoryPage_OnLoaded(object sender, RoutedEventArgs e)
    {
       //''''''''

        new Thread(() =>
        {
            _invoices = Invoice.GetAll();
              InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage);
            DgInvoices.ItemsSource = InvoiceList.CurrentItems;
        }).Start();
    }

which throws this exception

The Calling thread cannot access this object because a different thread owns it

After searching around, I found that the Dispatcher is the way to go about this. but I cannot get it to work.

    private void HistoryPage_OnLoaded(object sender, RoutedEventArgs e)
    {
       //''''''''
        new Thread(() =>
        {
            _invoices = Invoice.GetAll();
              InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage);
            Dispatcher.Invoke(() =>
            {
                DgInvoices.ItemsSource = InvoiceList.CurrentItems;
            });

        }).Start();
    }

this still throws the above exception.

can you recommend a way to get this working?


Solution

  • I personally think a BackgroundWorker would be the best option. Dispatcher may work, but it's a more "forced" operation in WPF and it can sometimes present a litany of other problems. With a BackgroundWorker you can do your data work in the background, and then do your UI work on the main thread upon its completion.

    As an example:

    BackgroundWorker bw = new BackgroundWorker();
    
    public MainWindow()
    {
        InitializeComponent();
    
        //Subscribe to the events
        bw.DoWork += Bw_DoWork;
        bw.RunWorkerCompleted += Bw_RunWorkerCompleted;
    }
    
    private void HistoryPage_OnLoaded(object sender, RoutedEventArgs e)
    {
         //Start background worker on page load
         bw.RunWorkerAsync(); //This is the DoWork function
    }
    
    //Background worker executes on separate thread
    private void Bw_DoWork(object sender, DoWorkEventArgs e)
    {
         //Do long running operations
         _invoices = Invoice.GetAll();           
    }
    
    //Fires when the DoWork operation finishes. Executes on the main UI thread
    private void Bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
         //Update UI when the worker completes on the main thread
         InvoiceList = new PagingCollection<Invoice>(_invoices, _itemsPerPage);
         DgInvoices.ItemsSource = InvoiceList.CurrentItems;
    }
    

    If your operation gets really long you can even tap into the BackgrounWorker.ReportProgess operation and give status updates to the UI. It's a great tool for loading operations that you can use to avoid locking the UI.