I have a record structure that I am trying to bind to a DataGrid
. Basically, my columns are dynamically specified by the view model. The value that should be displayed (and edited) in grid cell must be retrieved using a function on each record (the view model behind each row of the grid).
I looked at this question and it got me half the way there. Unfortunately, I cannot use a DynamicObject
because my properties on the record are namespaced names and as such cannot be represented by a string. I tried a variation where I converted this namespaced name into a bindable string (replacing all illegal characters with an underscore) but it seems like the DynamicObject
would call TryGetMember
every time I navigated the DataGrid
(i.e. it would call the function for every cell every time I scrolled). That performance is not going to be good enough.
This is what I have so far that works for displaying data (i.e. GetValue
):
public interface IDataSet
{
IEnumerable<IRecord> Records { get; }
IEnumerable<IProperty> PropertiesToDisplay { get; }
}
public interface IProperty
{
XName Name { get; }
}
public interface IRecord
{
IEnumerable<KeyValuePair<IProperty, object>> Values { get; set; }
object GetValue(IProperty property);
void SetValue(IProperty property, object value);
}
internal class SomeClass
{
DataGrid myGrid; // this is defined in the XAML
IDataSet viewModel; // this is the DataContext
private void BindToViewModelUsingConverter()
{
foreach (IProperty property in viewModel.PropertiesToDisplay)
{
Binding binding = new Binding();
binding.Converter = new ConvertMyDataConverter();
binding.ConverterParameter = property;
var column = new DataGridTextColumn
{
Header = property.Name.LocalName,
Binding = binding
};
myGrid.Columns.Add(column);
}
myGrid.ItemsSource = viewModel.Records;
}
}
internal class ConvertMyDataConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var record = value as IRecord;
var property = parameter as IProperty;
if (record != null && property != null)
{
return record.GetValue(property);
}
return DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// not sure what to do here, yet
throw new NotImplementedException();
}
}
The above code works when it comes to showing values. However, as soon as I try to edit values I get an exception:
**Two-way binding requires Path or XPath**
This makes sense because even in my converter above, I'm not sure what I would write in the ConvertBack
function since I want the source to remain the same (since I'm binding to IRecord
and have already called the IRecord.SetValue
function to update the data.) However, it seems like ConvertBack
would not give me a reference to IRecord
even if I was able to do two-way binding without a path.
Is there something trivial that I am missing? I'm not averse to developing a custom control but would like some tips/ideas on how to approach the problem.