C
C#•2y ago
grimpows

Dynamic Binding for DataGrid WPF

following this article : https://paulstovell.com/dynamic-datagrid/ i'm stuck at the "Rendering basic columns in a DataGrid" section. I dont really see where i should put that code in a MVVM practice. should i create the entire datagrid in the VM ? should i trigger on some ItemsSource updated event in the code behind ? ... fixed with help of https://stackoverflow.com/questions/64687172/how-to-autogenerate-datagrid-columns-for-collection-of-child-type-from-collectio first i do create a Data class that contain my Records :
//the BindableBase is from prism but is same as implementation of INotifyPropertyChanged
public class Data : BindableBase
{
private ObservableCollection<Record> _records = new();
public ObservableCollection<Record> Records
{
get { return _records; }
set {
SetProperty(ref _records, value);
}
}
}
//the BindableBase is from prism but is same as implementation of INotifyPropertyChanged
public class Data : BindableBase
{
private ObservableCollection<Record> _records = new();
public ObservableCollection<Record> Records
{
get { return _records; }
set {
SetProperty(ref _records, value);
}
}
}
record :
public class Record
{
private readonly ObservableCollection<Property> properties = new ObservableCollection<Property>();

public Record(params Property[] properties)
{
foreach (var property in properties)
Properties.Add(property);
}

public ObservableCollection<Property> Properties
{
get { return properties; }
}


}
public class Record
{
private readonly ObservableCollection<Property> properties = new ObservableCollection<Property>();

public Record(params Property[] properties)
{
foreach (var property in properties)
Properties.Add(property);
}

public ObservableCollection<Property> Properties
{
get { return properties; }
}


}
property
public class Property
{
public Property(string name, object value)
{
Name = name;
Value = value;
}

public string Name { get; private set; }
public object Value { get; set; }

}
public class Property
{
public Property(string name, object value)
{
Name = name;
Value = value;
}

public string Name { get; private set; }
public object Value { get; set; }

}
in your VM you can instanciate your prop with
public class SomeViewModel: ViewModelBase
{
private Data _data = new();
public Data Data
{
get { return _data; }
set { SetProperty(ref _data, value); }
}
}
public class SomeViewModel: ViewModelBase
{
private Data _data = new();
public Data Data
{
get { return _data; }
set { SetProperty(ref _data, value); }
}
}
Paul Stovell's Blog
WPF Dynamically Generated DataGrid
Sometimes you might have a very dynamic source of data, with classes to represent rows and properties. Here's how you can use a WPF DataGrid with it.
Stack Overflow
How to autogenerate DataGrid columns for collection of child type f...
So I have a bunch of classes that are derived from some base class. I have classes (collectors) that have a methods that returns collections of these classes. I also have a TabControl where each ta...
22 Replies
grimpows
grimpows•2y ago
but the trick on handling change from the ItemsSources is to create a CustomDataGrid that override the OnItemsSourceChanged :
//namespace is YourApp.Controls in this exemple for the xaml
public class DynamicDataGrid : DataGrid
{
public DynamicDataGrid() : base()
{
}
protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
if (newValue != null)
{
var enumerator = newValue.GetEnumerator();
if (enumerator.MoveNext())
{
Columns.Clear();
var firstElement = enumerator.Current;
var actualType = firstElement.GetType();
foreach (var prop in actualType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.CanRead))
{
var IsCollectionOfProperties = typeof(ObservableCollection<Property>) == prop.PropertyType;
if (IsCollectionOfProperties)
{
var properties = (ObservableCollection<Property>)prop.GetValue(firstElement);
foreach (var (property, index) in properties.Select((item, index) => (item, index)))
{
Columns.Add(new DataGridTextColumn
{
// dynamic bind is doing there !!
Header = property.Name,
Binding = new Binding(prop.Name + $"[{index}].Value")
});
}
}
else
{ //broked formating cose discord
//we could add some other prop than properties Array to a Record and bind them here ...
}
}
}
}
}
}
//namespace is YourApp.Controls in this exemple for the xaml
public class DynamicDataGrid : DataGrid
{
public DynamicDataGrid() : base()
{
}
protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
if (newValue != null)
{
var enumerator = newValue.GetEnumerator();
if (enumerator.MoveNext())
{
Columns.Clear();
var firstElement = enumerator.Current;
var actualType = firstElement.GetType();
foreach (var prop in actualType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.CanRead))
{
var IsCollectionOfProperties = typeof(ObservableCollection<Property>) == prop.PropertyType;
if (IsCollectionOfProperties)
{
var properties = (ObservableCollection<Property>)prop.GetValue(firstElement);
foreach (var (property, index) in properties.Select((item, index) => (item, index)))
{
Columns.Add(new DataGridTextColumn
{
// dynamic bind is doing there !!
Header = property.Name,
Binding = new Binding(prop.Name + $"[{index}].Value")
});
}
}
else
{ //broked formating cose discord
//we could add some other prop than properties Array to a Record and bind them here ...
}
}
}
}
}
}
grimpows
grimpows•2y ago
and in our XAML :
<UserControl>
...
xmlns:controls="clr-namespace:YourApp.Controls"
...
</UserContol>

<controls:DynamicDataGrid
x:Name="Datagrid"
Margin="10"
ItemsSource="{Binding Data.Records}"
CanUserAddRows="False"
IsReadOnly="True"
AutoGenerateColumns="False" />
<UserControl>
...
xmlns:controls="clr-namespace:YourApp.Controls"
...
</UserContol>

<controls:DynamicDataGrid
x:Name="Datagrid"
Margin="10"
ItemsSource="{Binding Data.Records}"
CanUserAddRows="False"
IsReadOnly="True"
AutoGenerateColumns="False" />
grimpows
grimpows•2y ago
BindableBase is from Prism and is an abstract class for INotifyPropertyChanged Record class is same as the article, also for property class and my view just contain same thing :
grimpows
grimpows•2y ago
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
grimpows
grimpows•2y ago
the rest of the xaml isnt important here there is lot of other stuff not related to this issue but i can show you the result if i put autoGenerate to true
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
grimpows
grimpows•2y ago
i didnt make templates from the articles as i didnt even generate columns yet template come after right ?
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
grimpows
grimpows•2y ago
as the article right it, the template is autoCalled by the : <Window.Resources> <DataTemplate x:Key="CustomTemplate"> <Border Padding="3" Background="Purple"> <TextBox Text="{Binding Path=Value}" /> </Border> </DataTemplate> </Window.Resources> and my prob is still before when we need to tell the datagrid to use X or Y template : foreach (var column in columns) { var binding = new Binding(string.Format("Properties[{0}]", column.Index)); dataGrid.Columns.Add(new CustomBoundColumn() { Header = column.Name, Binding = binding, TemplateName = "CustomTemplate" }); }
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
grimpows
grimpows•2y ago
the exemple from rendering template columns its a false exemple, this isnt dynamic if you speak about : <DataGridTemplateColumn> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <Border Padding="3" Background="Purple"> <TextBox Text="{Binding Path=FirstName}" /> </Border> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> the dynamic way is made by calling dataGrid.Columns.Add(new CustomBoundColumn() ...
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
grimpows
grimpows•2y ago
i just want reproduce the dynamic DataGrid of course i bind it to other code that can load the Ressources for specific use like read a CSV for my case so in fact i want to add the call of "dataGrid.Columns.Add(new CustomBoundColumn() ..." from somewhere the somewhere look like in code behind but i dont know how to trigger it on my ItemsSource changed or if you have better solution for using only MVVM i'm listening too :p
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
grimpows
grimpows•2y ago
actually i'll try using OnPropertyChanged but still not resolve how i can change the DataGrid.Columns :p
Unknown User
Unknown User•2y ago
Message Not Public
Sign In & Join Server To View
grimpows
grimpows•2y ago
i couldnt ^^ but ty anyway 🙂 and i already use the event aggregator for other thing like sending message, but i guess this may not the thing i need for this special case well about the event change i have found for prism at least but should work for any INotifyPropertyChanged object in the VM i can register a function for Data.PropertyChanged += Data_PropertyChanged; and can check if the property name changed is the "Records" in my VM contrustor :
grimpows
grimpows•2y ago
grimpows
grimpows•2y ago
then :
grimpows
grimpows•2y ago
grimpows
grimpows•2y ago
count is just a prop for check how many time the prop changed 🙂 wellll almost fixed my prob 🙂 for those who could be interested i'll post the solution when everything will be done well i finaly adapted the author post for MVVM .... and that wasnt easy x') edited original post to add the solution so i guess i will may change to directly pass data to the DynamicDataGrid but i'm over for tonight :p and i will have a reusable DynamicData Model that can be passed to a DynamicDataGrid !