I'm working on a requirement, where any amount of dynamic properties can be added to an entity. These dynamic properties can be shown in a data grid column besides the actual object properties.
To respect the existing architecture, these properties are being stored in a list:
public List<AdaErgaenzungsfeldEntity> Ergaenzungsfelder { get; set; }
In order to bind to each property in the list, I've exposed the values which will be shown in the grid like this:
public Dictionary<Guid, object> ErgaenzungsfeldValues {
get { return m_ergaenzungsfeldValues; }
}
The list and the dictionary are being synchronized when the Ergaenzungsfelder list changes:
private void RefreshErgaenzungsfeldValues() {
if (m_ergaenzungsfeldValues == null) {
m_ergaenzungsfeldValues = new Dictionary<Guid, object>();
}
m_ergaenzungsfeldValues.Clear();
foreach (AdaErgaenzungsfeldEntity entity in Ergaenzungsfelder) {
m_ergaenzungsfeldValues.Add(entity.Ergaenzungsfeld.ID, entity.Value);
}
}
The binding onto the grid is finally done like this:
List<ErgaenzungsfeldEntity> ergaenzungsfeldEntities = m_presenter.ErgaenzungsfeldService.GetAllErgaenzungsfeldEntities();
foreach (ErgaenzungsfeldEntity entity in ergaenzungsfeldEntities) {
m_lstAdas.Columns.Add(new Column {
Title = entity.Name,
FieldName = string.Format("ErgaenzungsfeldValues[{0}]", entity.ID)
});
}
The issue with this implementation is that the dictionary doesn't contain a value for all dynamic fields for all entities, which obviously results in a key not found exception:
System.Windows.Data Error: 16 : Cannot get 'Item[]' value (type 'Object') from 'ErgaenzungsfeldValues' (type 'Dictionary
2'). BindingExpression:Path=ErgaenzungsfeldValues[04d1be1c-2d83-48ba-b179-aaa9f0d0f7bc]; DataItem='AdaEntity' (HashCode=-800079524); target element is 'DataCell' (Name=''); target property is 'Content' (type 'Object') TargetInvocationException:'System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary. at System.ThrowHelper.ThrowKeyNotFoundException() at System.Collections.Generic.Dictionary
2.get_Item(TKey key) --- End of inner exception stack trace --- at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner) at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture) at MS.Internal.Data.PropertyPathWorker.GetValue(Object item, Int32 level) at MS.Internal.Data.PropertyPathWorker.RawValue(Int32 k)'
The entity is not aware of all possible fields and thus, it is not possible to add a default value for each dynamic property to every entity.
Question: How can those dynamic values be bound properly to the data grid to avoid the above mention exception?
I've created a small application to illustrate the behavior.
MainWindow.xaml:
<Window x:Class="DynamicdataGridBindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xcdg="http://schemas.xceed.com/wpf/xaml/datagrid"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<xcdg:DataGridControl
Name="m_dataGridControl"
AutoCreateColumns="False"
AutoRemoveColumnsAndDetailConfigurations="False"
ReadOnly="True"
ItemsSource="{Binding TestEntities}">
<xcdg:DataGridControl.Columns>
<xcdg:Column Title="Property"
FieldName="DefinedProperty" />
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Xceed.Wpf.DataGrid;
namespace DynamicdataGridBindingTest {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow {
private readonly Dictionary<Guid, string> m_dynamicColumnNames = new Dictionary<Guid, string> {
{Guid.NewGuid(),"DynText"},
{Guid.NewGuid(),"DynBool"},
{Guid.NewGuid(),"DynArray"}
};
public ObservableCollection<TestEntity> TestEntities { get; private set; }
public MainWindow() {
//Licenser.LicenseKey = "xxx";
TestEntities = new ObservableCollection<TestEntity>();
InitializeComponent();
InitializeEntities();
InitializedataGridColumns();
}
private void InitializeEntities() {
TestEntity testEntity1 = new TestEntity {
DefinedProperty = "Property Value 1",
};
testEntity1.DynamicProperties.Add(m_dynamicColumnNames.ElementAt(0).Key, "My text");
testEntity1.DynamicProperties.Add(m_dynamicColumnNames.ElementAt(1).Key, true);
testEntity1.DynamicProperties.Add(m_dynamicColumnNames.ElementAt(2).Key, new[] { "val1.1", "val1.2", "val1.3" });
TestEntities.Add(testEntity1);
TestEntity testEntity2 = new TestEntity {
DefinedProperty = "Property Value 2"
};
testEntity2.DynamicProperties.Add(m_dynamicColumnNames.ElementAt(0).Key, "My text 2");
TestEntities.Add(testEntity2);
}
private void InitializedataGridColumns() {
foreach (string columnName in m_dynamicColumnNames.Values) {
m_dataGridControl.Columns.Add(new Column {
Title = columnName,
FieldName = string.Format("DynamicProperties[{0}]", m_dynamicColumnNames.First(kv => kv.Value == columnName).Key)
});
}
}
}
}
TestEntity.cs:
namespace DynamicdataGridBindingTest {
public class TestEntity {
public string DefinedProperty { get; set; }
public Dictionary<Guid, object> DynamicProperties { get; private set; }
public TestEntity() {
DynamicProperties = new Dictionary<Guid, object>();
}
}
}
Which looks like this when being run: