与记录结构(动态列和行)的双向DataGrid绑定

本文关键字:绑定 DataGrid 结构 记录 动态 | 更新日期: 2023-09-27 18:09:08

我有一个记录结构,我试图绑定到DataGrid。基本上,我的列是由视图模型动态指定的。应该在网格单元格中显示(和编辑)的值必须使用每个记录(网格每行后面的视图模型)上的函数来检索。

我看着这个问题,它让我完成了一半。不幸的是,我不能使用DynamicObject,因为记录上的属性是有名称空间的名称,因此不能用字符串表示。我尝试了一种变化,我把这个命名空间的名字转换成一个可绑定的字符串(用下划线替换所有非法字符),但似乎DynamicObject每次我导航DataGrid时都会调用TryGetMember(即每次我滚动时都会调用每个单元格的函数)。这样的表现是不够好的。

这是我迄今为止所拥有的用于显示数据(即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函数中写什么,因为我希望源保持不变(因为我绑定到IRecord并且已经调用了IRecord.SetValue函数来更新数据)。然而,即使我能够在没有路径的情况下进行双向绑定,ConvertBack似乎也不会给我IRecord的引用。

我是不是漏掉了什么琐碎的东西?我不反对开发一个自定义控件,但是我想要一些关于如何解决这个问题的技巧/想法。

与记录结构(动态列和行)的双向DataGrid绑定

我使用ICustomTypeDescriptorITypedList解决了这个问题。而不是RecordsIEnumerable<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属性,而不需要转换器。