Question

I have a DataGridView in a WPF application. Retrieving the whole data is a long-running task that involves intensive I/O.

I thought it could be a good idea to populate the grid asynchronously, i.e. once an element is decoded it gets added live to the listing.

In my specific case, I'm loading emails from file system to display on a grid very similar to Outlook's main window.

The code

Currently I have defined a ViewModel (here simplified)

public class EmailViewModel: INotifyPropertyChanged {
    public string From, To, Subject, Size;
    public DateTime Date;
    //THIS IS A SIMPLIFIED VERSION. THE REAL CODE REALLY IMPLEMENTS INOTIFYPROPERTYCHANGED
}

Then I use the following fragments:

//Window fragment
<Window.Resources>
    <CollectionViewSource x:Key="Emails" Source="{Binding}"/>
</Window.Resources>

//DataGrid fragment
<DataGrid Name="dataGridEmails" Grid.Row="1" AutoGenerateColumns="False" ItemsSource="{Binding Source={StaticResource ResourceKey=Emails}}">
     <DataGrid.DataContext>
            <EmailReader:EmailViewModel/>
        </DataGrid.DataContext>

//Code behind
void onClick(object sender, RoutedEventArgs e){
     ObservableCollection<EmailViewModel > collection = new ObservableCollection<EmailViewModel>();
     DataContext = collection;
     _binderThread = new Thread(o => EmailController.GetFromDirectory((string)o, collection));
     _binderThread.Start(rootPath);
}
 //GetFromDirectory
 public static void GetFromDirectory(string directoryPath, ICollection<EmailViewModel> targetCollection)
    {
        string[] files = Directory.GetFiles(directoryPath, "*.eml", SearchOption.AllDirectories);

        foreach (string file in files)
        {
            try
            {
                targetCollection.Add(LoadFromFile(file));
            }
            catch { }
        }
    }

Explanation

Since the DataContext of a Window cannot be accessed from a foreign thread, I create an instance of the new collection, assign it to the DataContext and pass its reference to the worker thread. If I check in debug mode I can see the DataContext object gets populated.

The worker thread simply adds a new email to what it thinks is a generic collection but instead is an ObservableCollection, which should fire an event when a new object is added. That event should be caught by UI to update the DataGrid

The problem

When I run the application I get only one (empty, need to look more onto it for formatting problems) row. If I try to click on the row I get an exception about the collection being in an incoherent state (because count doesn't match with grid). The exception is explained, for me, by the fact that the DataGrid doesn't get updated on every single Add of a new item.

The question

  • Is something wrong in my design?
  • How can I achieve asynchronousness in data binding with WPF?
Was it helpful?

Solution

Try adding a listener for collection.CollectionChanged:

collection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(collection_CollectionChanged);

void collection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    if (this.PropertyChanged != null) this.PropertyChanged(this, new PropertyChangedEventArgs("collection"));
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top