简体   繁体   中英

Modifying Datagrid.Columns from any thread in WPF

(I use .Net 4.5, with Visual Studio 2017. Minimal example link has been added at the end of the post, and is ready to run/crash without anything to do if you want to see the things by yourselves)

I have a UserControl that contains a DataGrid in WPF. It's bound to a Viewmodel that contains a DataTable, AND a custom list of columns, in order to hide some of them when I can't delete them right at the moment for example.

DataGrid.Columns is readonly, so in the UserControl.DataContextChanged (I explain below why), I get the new DataContext, clear my grid.Columns collection and feed it with the custom list of DataGridColums I've read at load time.

Initially, I was doing a sync load. Everything was running fine (I'm loading many things, including a list of these Items). But I've put the loading into a backgroundworker (which ran almost fine) and, later, replaced the backgroundworker by an async loading using Task.Run().

All of the other lists of item in my window are well loaded, the code runs fine. BUT the moment I'm trying to replace mygrid.Columns by other ones, I have a InvalidOperationException saying that the calling thread can't access this object because another thread owns it.

I've tried many things, many BeginInvoke from myGrid.Dispatcher, App's current Dispatcher and so on, and every piece of async code I found, but I cannot figure out how to add a simple item to this ObservableCollection.

I've seen custom asyncObservableCollection but can't use them, DataGrid.Columns being read-only.

I'm puzzled by the fact that I thought UserControl_DataContextChanged was belonging to the UI thread, so it should be able to change user controls safely.

I've updloaded a "minimal example" here => https://files.fm/u/k2srba6m The problem is in ItemViewmodel.FilterColumns, as my original code does (hope the solution to this will work on the original code too)

Any help would be appreciated (and sorry for my english)

If you are using .Net 4.5 or later you can try using BindingOperations.EnableCollectionSynchronization .

In your window constructor:

// Sync collection with UI
BindingOperations.CollectionRegistering += BindingOperations_CollectionRegistering;

And add this method:

/// <summary>
/// Handles the CollectionRegistering event of the BindingOperations control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="CollectionRegisteringEventArgs"/> instance containing the event data.</param>
private void BindingOperations_CollectionRegistering(object sender, CollectionRegisteringEventArgs e)
{
    BindingOperations.EnableCollectionSynchronization(e.Collection, e.Collection);
}

This will automatically call the EnableCollectionSynchronization method whenever an ObservableCollection is created.

I set it up to simply use the collection itself as the lockObject, don't know if that is a bad idea, but it worked on my end.


If you are using .Net 4.0 , you might have to get the AsyncObservableCollection to work. I have not found another way to do this in .Net 4.0.

I found an answer thanks to help on another forum. I leave the question here because answer was hard to find.

Adding to DataGrid.Columns a DataGridColumn instanciated by hand in the viewmodel was faulty, the instanciation itself should have been invoked in the UI thread. The OC.Add threw an exception but mainly because of the argument (and in some cases the call itself, yes).

Sorry, again, no quotes full of code, just "belletristic novel", but it could help.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM