I am currently working on a C# System.Windows.Controls.DataGrid that needs to generate the columns dynamically depending on the data. It can add and/or remove columns during runtime.
I am using a Thread in the ViewModel class to update the ObservableCollection that feeds the DataGrid.
I have read that post which explains the best solution I have found for my problem. Although, the Columns.CollectionChanged Delegate from the DataGridExtension class throws a InvalideOperationException : The calling thread cannot access this object because a different thread owns it.
Heres some code to picture it all :
The View XAML
<DataGrid ItemsSource="{Binding CollectionView, Source={StaticResource ViewModel}}" local:DataGridExtension.Columns="{Binding DataGridColumns, Source={StaticResource ViewModel}}" AutoGenerateColumns="False" Name="dataGrid">
ViewModel Class
public ObservableCollection<DataGridColumn> DataGridColumns
{
get { return columns; }
set { columns = value; }
}
private void getViewData()
{
while (true)
{
Thread.Sleep(1000);
foreach (DataObject data in dataObjects)
{
int index = -1;
foreach (DataGridColumn c in columns)
{
if (c.Header.Equals(column.Header))
index = columns.IndexOf(c);
}
DataGridColumn column = new DataGridTextColumn();
... Creating the column based on data from DataObject ...
DataGridExtension._currentDispatcher = Dispatcher.CurrentDispatcher;
if (index == -1)
{
this.columns.Add(column);
}
else
{
this.columns.RemoveAt(index);
this.columns.Add(column);
}
}
}
}
DataGridExtension class
public static class DataGridExtension
{
public static Dispatcher _currentDispatcher;
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.RegisterAttached("Columns",
typeof(ObservableCollection<DataGridColumn>),
typeof(DataGridExtension),
new UIPropertyMetadata(new ObservableCollection<DataGridColumn>(), OnDataGridColumnsPropertyChanged));
private static void OnDataGridColumnsPropertyChanged(DependencyObject iObj, DependencyPropertyChangedEventArgs iArgs)
{
if (iObj.GetType() == typeof(DataGrid))
{
DataGrid myGrid = iObj as DataGrid;
ObservableCollection<DataGridColumn> Columns = (ObservableCollection<DataGridColumn>)iArgs.NewValue;
if (Columns != null)
{
myGrid.Columns.Clear();
if (Columns != null && Columns.Count > 0)
{
foreach (DataGridColumn dataGridColumn in Columns)
{
myGrid.Columns.Add(dataGridColumn);
}
}
Columns.CollectionChanged += delegate(object sender, NotifyCollectionChangedEventArgs args)
{
if (args.NewItems != null)
{
UserControl control = ((UserControl)((Grid)myGrid.Parent).Parent);
foreach (DataGridColumn column in args.NewItems.Cast<DataGridColumn>())
{
/// This is where I tried to fix the exception. ///
DataGridColumn temp = new DataGridTextColumn();
temp.Header = column.Header;
temp.SortMemberPath = column.SortMemberPath;
control.Dispatcher.Invoke(new Action(delegate()
{
myGrid.Columns.Add(temp);
}), DispatcherPriority.Normal);
////////////////////////////////////////////////////
}
}
if (args.OldItems != null)
{
foreach (DataGridColumn column in args.OldItems.Cast<DataGridColumn>())
{
myGrid.Columns.Remove(column);
}
}
};
}
}
}
public static ObservableCollection<DataGridColumn> GetColumns(DependencyObject iObj)
{
return (ObservableCollection<DataGridColumn>)iObj.GetValue(ColumnsProperty);
}
public static void SetColumns(DependencyObject iObj, ObservableCollection<DataGridColumn> iColumns)
{
iObj.SetValue(ColumnsProperty, iColumns);
}
}
The section where I put /// This is where I tried to fix the exception. /// is where the exception is getting thrown, exactly at myGrid.add(...);
The myGrid object does not allow me to add that column to be added to the collection of columns of the DataGrid. Which is why I surrounded it with a Dispatcher.Invoke. Strangely, if I execute myGrid.Columns.Add(new DataGridTextColumn()); it works and I can see the empty columns getting added in the view but myGrid.Columns.Add(temp); throws the exception.
There must be something I don't catch with this thing.
Please HELP!!!!
EDIT following Stipo suggestion
UserControl control = ((UserControl)((Grid)myGrid.Parent).Parent);
Columns.CollectionChanged += delegate(object sender, NotifyCollectionChangedEventArgs args)
{
control.Dispatcher.Invoke(new Action(delegate()
{
if (args.NewItems != null)
{
foreach (DataGridColumn column in args.NewItems.Cast<DataGridColumn>())
{
DataGridColumn temp = new DataGridTextColumn();
temp.Header = column.Header;
temp.SortMemberPath = column.SortMemberPath;
myGrid.Columns.Add(temp);
}
}
if (args.OldItems != null)
{
foreach (DataGridColumn column in args.OldItems.Cast<DataGridColumn>())
{
myGrid.Columns.Remove(column);
}
}
}), DispatcherPriority.Normal);
};