I've made a minimalistic example of my problem. First here a class diagram Class Diagram
What I want: I want to display each property from class A in a WPF DataGrid with the ability to edit each property.
If I just add the object A to an ObservableCollection and add it as ItemsSource to the DataGrid the result is as expected not what I need.
This is why I added the following class:
public class DataGridRowObjects
{
public A ObjectA { get; set; }
public B ObjectB { get; set; }
public AbstractC ObjectC { get; set; }
}
The problem is that some cells will be empty, caused by the AbstractC. If I have ConcreteC1 I want the columns given by ConcreteC1 be editable. If I have a ConcreteC2 object in this row, I want to disable these cells with some text like property not available.
I got a solution to this problem, but my problem is the code quality (very ugly solution) with the thought of maintenance.
This is my approach how to solve the problem to be able to edit each property in this datagrid Result in action:
<DataGrid x:Name="dataGridForA" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="PropertyAA" Binding="{Binding ObjectA.PropertyAA}"/>
<DataGridTextColumn Header="PropertyAB" Binding="{Binding ObjectA.PropertyAB}"/>
<DataGridTextColumn Header="PropertyBA" Binding="{Binding ObjectB.PropertyBA}"/>
<DataGridTextColumn Header="PropertyAbstractCA" Binding="{Binding ObjectC.PropertyAbstractCA}" />
<DataGridTemplateColumn Header="PropertyConcreteC1A">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding ObjectC}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type model:ConcreteC1}">
<TextBlock Text="{Binding PropertyConcreteC1A}" />
</DataTemplate>
<DataTemplate DataType="{x:Type model:ConcreteC2}">
<TextBlock Text="Property not available" />
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<!-- Some cool stuff to edit properties -->
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="PropertyConcreteC2A">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding ObjectC}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type model:ConcreteC2}">
<TextBlock Text="{Binding PropertyConcreteC2A}" />
</DataTemplate>
<DataTemplate DataType="{x:Type model:ConcreteC1}">
<TextBlock Text="Property not available" />
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<!-- Some cool stuff to edit properties -->
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Well, the problem is the part with the DataGridTemplateColumn. In this example, it looks quite simple. But in my real-world problem I have many more properties and currently four classes which derives from AbstractC. Which would be ending up in like four DataTemplates for each ConcreteC-Class and for each property of these ConcreteC-Classes. The XAML in my real-world problem will explode and is not maintainable. Maybe some guys of you got another solution?
Appendix
Here are the classes, maybe for a better overview and how I set up the Datagrid ItemsSource
public class A
{
public string PropertyAA { get; set; }
public string PropertyAB { get; set; }
public List<B> ListB { get; set; }
}
public class B
{
public string PropertyBA { get; set; }
public List<AbstractC> ListAbstractC { get; set; }
}
public abstract class AbstractC
{
public string PropertyAbstractCA { get; set; }
}
public class ConcreteC1 : AbstractC
{
public string PropertyConcreteC1A { get; set; }
}
public class ConcreteC2 : AbstractC
{
public string PropertyConcreteC2A { get; set; }
}
Setup:
public class DataGridRowObjects
{
public A ObjectA { get; set; }
public B ObjectB { get; set; }
public AbstractC ObjectC { get; set; }
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public List<A> aList = new List<A>();
public ObservableCollection<DataGridRowObjects> modelList = new ObservableCollection<DataGridRowObjects>();
public MainWindow()
{
InitializeComponent();
ConcreteC1 concreteC1A = new ConcreteC1 { PropertyAbstractCA = "TestA1", PropertyConcreteC1A = "TestConcrete1A" };
ConcreteC2 concreteC2A = new ConcreteC2 { PropertyAbstractCA = "TestA2", PropertyConcreteC2A = "TestConcrete2A" };
ConcreteC1 concreteC1B = new ConcreteC1 { PropertyAbstractCA = "TestB1", PropertyConcreteC1A = "TestConcrete1B" };
ConcreteC2 concreteC2B = new ConcreteC2 { PropertyAbstractCA = "TestB2", PropertyConcreteC2A = "TestConcrete2B" };
List<AbstractC> concreteAs = new List<AbstractC>();
concreteAs.Add(concreteC1A);
concreteAs.Add(concreteC2A);
B bA = new B { ListAbstractC = concreteAs, PropertyBA = "bA" };
List<AbstractC> concreteBs = new List<AbstractC>();
concreteBs.Add(concreteC1B);
concreteBs.Add(concreteC2B);
B bB = new B { ListAbstractC = concreteBs, PropertyBA = "bB" };
List<B> bList = new List<B>();
bList.Add(bA);
bList.Add(bB);
A objectA = new A {PropertyAA = "AA", PropertyAB = "AB", ListB = bList};
aList.Add(objectA);
foreach (A a in aList)
{
foreach (B b in a.ListB)
{
foreach (AbstractC c in b.ListAbstractC)
{
this.modelList.Add(new DataGridRowObjects { ObjectA = a, ObjectB = b, ObjectC = c });
}
}
}
this.dataGridForA.ItemsSource = modelList;
}
}
It seems you are missing the Fallbackvalue property of the binding.
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding PropertyConcreteC2A, FallbackValue='Property Not Available' }" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
This will do the thing and probably you might be able to simplify it further. Please check the following links also for additional information,