我有一个记录结构,我试图绑定到DataGrid
。基本上,我的列是由视图模型动态指定的。必须使用每条记录上的函数(网格每一行后面的视图模型)检索网格单元格中应显示(和编辑)的值。
我看了this question,它让我走了一半路。不幸的是,我不能使用DynamicObject
,因为记录中的属性是命名空间名称,因此不能用字符串表示。我尝试了一个变体,我将这个命名空间名称转换为可绑定字符串(用下划线替换所有非法字符)但似乎DynamicObject
每次导航TryGetMember
时都会调用DataGrid
(即每次滚动时它会调用每个单元格的函数)。这种表现不够好。
这是我到目前为止用于显示数据的方法(即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();
}
}
上述代码在显示值时有效。但是,一旦我尝试编辑值,我就会得到一个例外:
**Two-way binding requires Path or XPath**
这是有道理的,因为即使在上面的转换器中,我也不确定我会在ConvertBack
函数中写什么,因为我希望源保持不变(因为我绑定到{{1}并且已经调用了IRecord
函数来更新数据。)然而,即使我能够做到双向,IRecord.SetValue
似乎也不会给我ConvertBack
的引用没有路径的绑定。
我缺少一些微不足道的东西吗?我并不反对开发自定义控件,但想了解如何处理问题的一些提示/想法。
答案 0 :(得分:0)
我使用ICustomTypeDescriptor
和ITypedList
解决了这个问题。我将其设为Records
以及IEnumerable<IRecord>
的自定义集合,而不是IList<IRecord>
ITypedList
。对于ITypedList
实现,我返回了包含IProperty
实现的属性描述符:
public interface IDataSet
{
TypedRecordsCollection Records { get; }
IEnumerable<IProperty> PropertiesToDisplay { get; }
}
public class TypedRecordsCollection : ObservableCollection<RecordViewModel>, ITypedList
{
private PropertyDescriptorCollection _properties;
public TypedRecordsCollection(IEnumerable<IRecord> records)
{
_properties = new PropertyDescriptorCollection(
PropertiesToDisplay
.Select(prop => new CustomPropertyDescriptor(prop))
.ToArray());
records.ForEach(rec => Items.Add(new RecordViewModel(rec, _properties)));
}
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
return _properties;
}
}
public interface IRecordViewModel : ICustomTypeDescriptor
{
object GetCellValue(IProperty property);
void SetCellValue(IProperty property, object value);
}
public class RecordViewModel : CustomTypeDescriptor, IRecord
{
private IRecord _record;
private PropertyDescriptorCollection _properties;
public RecordViewModel(IRecord record, PropertyDescriptorCollection properties)
{
_record = record;
_properties = properties;
}
public object GetCellValue(IProperty property)
{
return _record.GetValue(property);
}
public void SetCellValue(IProperty property, object value)
{
_record.SetValue(property, value);
}
public override PropertyDescriptorCollection GetProperties()
{
return _properties;
}
}
public class CustomPropertyDescriptor : PropertyDescriptor
{
private IProperty _property;
public CustomPropertyDescriptor(IProperty property)
: base(GetCleanName(property.Name), new Attribute[0])
{
_property = property;
}
public override object GetValue(object component)
{
var recordViewModel = component as IRecordViewModel;
if (recordViewModel != null)
{
return recordViewModel.GetCellValue(_property);
}
return null;
}
public override void SetValue(object component, object value)
{
var recordViewModel = component as IRecordViewModel;
if (recordViewModel != null)
{
recordViewModel.SetCellValue(_property, value);
}
}
private static string GetCleanName(XName name)
{
// need to return a XAML bindable string.. aka no "{}" or ".", etc.
}
}
现在在我的XAML中,我可以将AutogenerateColumns
设置为true,并直接绑定到Records
属性,而无需转换器。