I have a complex table of data (about 150 rows, between 1 and 100 columns) which I want to display and edit using a DataGrid in WPF, but I've hit a big stumbling block. Please forgive me (and correct me) if my terminology is off in points, as I'm quite new to WPF and XAML.
To understand my problem, my requirements are:
- The data consists of a variable number of rows and columns which are loaded through AJAX
- Every row (class "Record" in my test implementation) has a few fixed properties that need to be displayed as well as a varying number of properties (though all rows have the same number of such properties) in a collection
- Each row/Record has an type (e.g. String, Integer, Boolean) for its properties inferred through an Enum property "VType". Properties should be displayed and edited with a template according to the VType value.
- Columns may be added or removed at run time
- (Some) rows may also be added or removed at run time
- Rows can change their "type" at run time
So far, I've built a working example with DataGridTextColumns that creates the columns from simulated data and fills the bound collection. I've implemented INotifyPropertyChanged and used ObservableCollections where necessary, so reactivity works, and my propoerty values are pulled from the binding to the individual property and correctly shown.
When adding the columns, I passed the correct binding. For my example app, I use the column index to bind each column to the correct Property object in the Record's collection:
// Amounts to "Properties[0].Value", "Properties[1].Value", etc.
var binding = new Binding(string.Format("Properties[{0}].Value", column.Index));
dataGrid.Columns.Add(new DataGridTextColumn() { Header = column.Name, Binding = binding });
Now I tried to tackle using different templates for different "Record types", i.e. a vType property in my Record class. I've created data templates in Window.Resources (very crude ones to start), set up a lookup and implemented the RecordTemplateSelector:
<!--BOOL TEMPLATE-->
<DataTemplate x:Key="booleanTemplate">
<CheckBox IsChecked="{Binding Value}" Background="LightGray" Margin="5, 0, 0, 0"/>
</DataTemplate>
<!--STRING TEMPLATE-->
<DataTemplate x:Key="stringTemplate">
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
<!--INTEGER TEMPLATE-->
<DataTemplate x:Key="integerTemplate">
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
<local:RecordTemplateSelector x:Key="myRecordTemplateSelector"
BooleanTemplate="{StaticResource booleanTemplate}"
StringTemplate="{StaticResource stringTemplate}"
IntegerTemplate="{StaticResource integerTemplate}"/>
And this is my TemplateSelector:
class RecordTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var selectedTemplate = StringTemplate;
var record = item as Record;
if (item == null) return selectedTemplate;
switch (record.VType)
{
case Record.ValueType.Checkbox:
selectedTemplate = BooleanTemplate;
break;
case Record.ValueType.Integer:
selectedTemplate = IntegerTemplate;
break;
case Record.ValueType.String:
selectedTemplate = StringTemplate;
break;
}
return selectedTemplate;
}
}
It does pull the correct template, but when I thought I had it working, I noticed that I can't correctly bind my DataGridTemplateColumn - it is always implicitly bound to the whole row (i.e. Record object) and I don't see a way how my template can know which element in the Record's Property collection it should apply to.
I'm at a loss where to go from here. Is there a way to inherit the column's binding down to the template? Is there some other way to pass the correct item (an index would be okay too) to the template? Or do I have to use a completely different approach?
Big Thanks in advance for any input you can give me.