2014-06-18 2 views
0

DataGrid에 바인딩하려고하는 레코드 구조가 있습니다. 기본적으로 내 열은 뷰 모델에 의해 동적으로 지정됩니다. 표 셀에 표시 (및 편집)해야하는 값은 각 레코드의 기능 (표의 각 행 뒤에있는 뷰 모델)을 사용하여 검색해야합니다.레코드 구조 (동적 열과 행)가있는 양방향 DataGrid 바인딩

나는 this question을 보았다. 레코드의 내 속성이 네임 스페이스 이름이므로 문자열로 표시 할 수 없으므로 불행히도 DynamicObject을 사용할 수 없습니다. 나는이 namespaced 이름을 바인딩 할 수있는 문자열로 바꾸 었는데 (불법 문자를 모두 밑줄로 바꾸었다.) 을 탐색 할 때마다 이 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 함수를 호출했습니다.) 그러나, 나는 양방향 바인딩을 할 수 있었다하더라도 IRecord에 대한 참조를 제공하지 않을 것 같습니다 경로없이.

실종 신고 된 것이 있습니까? 나는 사용자 정의 컨트롤을 개발하는 것에 반대하지 않지만 문제에 접근하는 방법에 대한 몇 가지 팁/아이디어를 원합니다.

+0

"성능이 충분하지 않을 것입니다." - 먼저 해봐. –

+0

@Henk : 나는 그것을 시도했지만 그것은 끔찍했습니다. "(즉, 스크롤 할 때마다 모든 셀의 기능을 호출합니다)" – sohum

답변

0

ICustomTypeDescriptorITypedList을 사용하여이를 해결했습니다. IEnumerable<IRecord>Records 대신 IList<IRecord>ITypedList을 구현 한 맞춤 컬렉션을 만들었습니다.

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에 난 그냥 true로 AutogenerateColumns를 설정하고 컨버터없이 Records 속성에 직접 바인딩 다음 ITypedList 구현을 위해, 내 IProperty 구현을 감싸 속성 기술자를 반환했습니다.