2010-06-30 2 views
8

У меня есть коллекция объектов, хранящихся в CollectionViewSource и привязанных к DataGrid. Я хочу отобразить «подробный вид» объекта, выбранного в настоящее время в DataGrid. Я могу получить текущий объект, используя CollectionViewSource.View.CurrentItem.WPF: динамическое связывание списка с (некоторыми из) свойств объекта

MyClass 
{ 
    [IsImportant] 
    AProperty{} 

    AnotherProperty{} 

    [IsImportant] 
    YetAnotherProperty{} 
} 

То, что я хотел бы сделать, это дисплея этикетка (с именем свойства) и управление (для редактирования) в ListBox, для каждого из этих свойств отмечен IsImportant атрибута. Связывание должно работать между внесенными изменениями, DataGrid и объектом поддержки. Отображаемый элемент управления должен меняться в зависимости от типа объекта, который может быть либо boolean, string, либо IEnumerable<string> (я написал IValueConverter для преобразования между перечислимой и строкой, разделенной символом новой строки).

Кто-нибудь знает о способе выполнения этого? Я могу себе отобразить значения каждого свойства через следующее, но редактирование их не обновят объект подкладочного:

listBox.ItemsSource = from p in typeof(MyClass).GetProperties() 
         where p.IsDefined(typeof(IsImportant), false) 
         select p.GetValue(_collectionViewSource.View.CurrentItem, null); 

Чтобы уточнить, я хотел бы, чтобы это произошло «автомагический», без ручного указания имен свойств в XAML. Если я могу динамически добавлять XAML во время выполнения, на основе которого свойства помечены атрибутами, это также было бы хорошо.

ответ

12

Вы хотите контроль, который имеет метку с именем свойства и управления для редактирования значения свойства, поэтому начнем с создания класса, который оборачивает свойство конкретного объекта действовать в качестве DataContext для этого элемента управления:

public class PropertyValue 
{ 
    private PropertyInfo propertyInfo; 
    private object baseObject; 

    public PropertyValue(PropertyInfo propertyInfo, object baseObject) 
    { 
     this.propertyInfo = propertyInfo; 
     this.baseObject = baseObject; 
    } 

    public string Name { get { return propertyInfo.Name; } } 

    public Type PropertyType { get { return propertyInfo.PropertyType; } } 

    public object Value 
    { 
     get { return propertyInfo.GetValue(baseObject, null); } 
     set { propertyInfo.SetValue(baseObject, value, null); } 
    } 
} 

Вы хотите связать ItemsSource из в ListBox на объект для того, чтобы заполнить его с этими элементами управления, поэтому создать IValueConverter, который будет преобразовывать объект в список объектов PropertyValue для своих важных свойств:

public class PropertyValueConverter 
    : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     return 
      from p in value.GetType().GetProperties() 
      where p.IsDefined(typeof(IsImportant), false) 
      select new PropertyValue(p, value); 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     return Binding.DoNothing; 
    } 
} 

Итоговый трюк что вы хотите, чтобы элемент управления редактирования изменялся в зависимости от типа свойства. Вы можете сделать это с помощью ContentControl и установить ContentTemplate в один из различных шаблонов редактора на основе типа свойства. В этом примере используется CheckBox, если свойство является логическим и TextBox иначе:

<DataTemplate x:Key="CheckBoxTemplate"> 
    <CheckBox IsChecked="{Binding Value}"/> 
</DataTemplate> 
<DataTemplate x:Key="TextBoxTemplate"> 
    <TextBox Text="{Binding Value}"/> 
</DataTemplate> 
<Style x:Key="EditControlStyle" TargetType="ContentControl"> 
    <Setter Property="ContentTemplate" Value="{StaticResource TextBoxTemplate}"/> 
    <Style.Triggers> 
     <DataTrigger Binding="{Binding PropertyType}" Value="{x:Type sys:Boolean}"> 
      <Setter Property="ContentTemplate" Value="{StaticResource CheckBoxTemplate}"/> 
     </DataTrigger> 
    </Style.Triggers> 
</Style> 
<DataTemplate DataType="{x:Type local:PropertyValue}"> 
    <StackPanel Orientation="Horizontal"> 
     <Label Content="{Binding Name}"/> 
     <ContentControl Style="{StaticResource EditControlStyle}" Content="{Binding}"/> 
    </StackPanel> 
</DataTemplate> 

Затем, вы можете просто создать свой ListBox как:

<ItemsControl ItemsSource="{Binding Converter={StaticResource PropertyValueConverter}}"/> 
+0

Это выглядит фантастическим; Я буду реализовывать его, как только у меня появится шанс, и дайте знать, как это происходит. Благодаря! –

+0

Это работает в основном отлично. Я просто добавил нулевую проверку в PropertyValueConverter, так как он получал нулевой объект, когда привязка была впервые установлена. Единственная проблема заключается в том, что в настоящее время, если я изменяю значение этого элемента управления, оно не распространяется на datagrid. Предположительно, объект подложки изменяется, поскольку измененное значение все еще отображается элементом управления items, но сетка не уведомляется. Это связано с Binding.DoNothing? –

+1

@ Daniel: вашему объекту необходимо будет реализовать INotifyPropertyChanged, если вы хотите, чтобы обновления, сделанные одним элементом управления, отражались в других элементах управления, привязанных к одному и тому же свойству. Вам также потребуется реализовать INotifyPropertyChanged в PropertyValue. Свойство PropertyValueConverter для самой привязки ItemsSource, которая является однонаправленной, поэтому ConvertBack не будет вызываться, а Binding.DoNothing не имеет значения. – Quartermeister

Смежные вопросы