C#DataGrid AutoGenerateColumns用于包装器

时间:2017-10-26 09:10:11

标签: c# wpf datagrid

我正在尝试在WPF中实现某种对象选择器。到目前为止,我已经创建了一个带有 DataGrid 的Window,其中ItemsSource绑定到 ObservableCollection 。我还将 AutoGenerateColumns 设置为'true',因为要拾取的Item可以是任何类型的ob对象。 集合中的对象包含在 SelectionWrapper< T> ,其中包含一个IsSelected属性以便选择它们。

class SelectionWrapper<T> : INotifyPropertyChanged
{
    // Following Properties including PropertyChanged
    public bool IsSelected { [...] }
    public T Model { [...] }
}

我还在DataGrid.Columns中添加了一个CustomColumn,以便像这样绑定IsSelected属性

<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding SourceView}">
    <DataGrid.Columns>
        <DataGridCheckBoxColumn Header="Selected" Binding="{Binding IsSelected}" />
    </DataGrid.Columns>
</DataGrid>

我用这个解决方案得到的结果不是很令人满意,因为只有我定义的列'Selected'和两个GeneratedColumns'IsSelected'和'Model'。

有没有办法更改AutoGeneration的目标以显示模型的所有属性? 此外,还必须使AutoGeneratedColumns ReadOnly,因为没有人应该编辑显示的条目。

无法关闭AutoGenerateColumns并添加更多手动列,例如

<DataGridTextColumn Binding="{Binding Model.[SomeProperty]}"/>

因为Model可以是任何类型的Object。也许有办法将AutoGeneration的目标路由到Model Property?

先谢谢

修改

接受@ grek40的回答后 我想出了以下

首先,我创建了一个在SelectionProperty<T>中继承的SelectionProperty的通用类。在这里,我实现了最终看起来像的接口ICustomTypeDescriptor

public abstract class SelectionProperty : NotificationalViewModel, ICustomTypeDescriptor
{
    bool isSelected = false;
    public bool IsSelected
    {
        get { return this.isSelected; }
        set
        {
            if (this.isSelected != value)
            {
                this.isSelected = value;
                this.OnPropertyChanged("IsSelected");
            }
        }
    }

    object model = null;
    public object Model
    {
        get { return this.model; }
        set
        {
            if (this.model != value)
            {
                this.model = value;
                this.OnPropertyChanged("Model");
            }
        }
    }

    public SelectionProperty(object model)
    {
        this.Model = model;
    }
#region ICustomTypeDescriptor
[...]
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
    {
        return TypeDescriptor.GetProperties(this.Model.GetType());
    }

    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
    {
        if (pd.DisplayName == "IsSelected")
            return this;

        return this.Model;
    }
#endregion

然后我创建了一个专门的ObservableCollection

class SelectionPropertyCollection<T> : ObservableCollection<T>, ITypedList
    where T : SelectionProperty
{
    public SelectionPropertyCollection(IEnumerable<T> collection) : base(collection)
    {

    }

    public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
    {
        return TypeDescriptor.GetProperties(typeof(T).GenericTypeArguments[0]);
    }

    public string GetListName(PropertyDescriptor[] listAccessors)
    {
        return null;
    }
}

嗯,最后一件事是ViewModel。最重要的一行是

class ObjectPickerViewModel<ObjectType> : BaseViewModel
{
    public ICollectionView SourceView { get; set; }
    SelectionPropertyCollection<SelectionProperty<ObjectType>> source = null;
    public SelectionPropertyCollection<SelectionProperty<ObjectType>> Source
    {
        get { return this.source; }
        set
        {
            if (this.source != value)
            {
                this.source = value;
                this.OnPropertyChanged("Source");
            }
        }
    }
    // [...]
    this.Source = new SelectionPropertyCollection<SelectionProperty<ObjectType>>(source.Select(x => new SelectionProperty<ObjectType>(x)));
    this.SourceView = CollectionViewSource.GetDefaultView(this.Source);
}

这里的好处是,我仍然可以在XAML中添加更多列,但也拥有包装对象的所有公共属性!

2 个答案:

答案 0 :(得分:0)

  

有没有办法更改AutoGeneration的目标以显示所有模型属性?

简短回答:不。

只会创建您设置为T的{​​{1}}类型IEnumerable<T>的每个公共属性列。

您应该考虑将ItemsSource属性设置为AutoGenerateColumns并以编程方式创建列,而不是在XAML标记中对其进行硬编码。

答案 1 :(得分:0)

Binding DynamicObject to a DataGrid with automatic column generation?的过程之后,以下内容应该在某种程度上起作用,但我不确定我是否会在制作中使用类似的东西:

创建一个实现ITypedListIList的集合。来自GetItemProperties的{​​{1}}将被使用。期望列表类型实现ITypedList

ICustomTypeDescriptor

public class TypedList<T> : List<T>, ITypedList, IList where T : ICustomTypeDescriptor { public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) { if (this.Any()) { return this[0].GetProperties(); } return new PropertyDescriptorCollection(new PropertyDescriptor[0]); } public string GetListName(PropertyDescriptor[] listAccessors) { return null; } } 实施为SelectionWrapper<T>并实施DynamicObject(至少ICustomTypeDescriptor方法)

PropertyDescriptorCollection GetProperties()

public class SelectionWrapper<T> : DynamicObject, INotifyPropertyChanged, ICustomTypeDescriptor { private bool _IsSelected; public bool IsSelected { get { return _IsSelected; } set { SetProperty(ref _IsSelected, value); } } private T _Model; public T Model { get { return _Model; } set { SetProperty(ref _Model, value); } } public override bool TryGetMember(GetMemberBinder binder, out object result) { if (Model != null) { var prop = typeof(T).GetProperty(binder.Name); // indexer member will need parameters... not bothering with it if (prop != null && prop.CanRead && prop.GetMethod != null && prop.GetMethod.GetParameters().Length == 0) { result = prop.GetValue(Model); return true; } } return base.TryGetMember(binder, out result); } public override IEnumerable<string> GetDynamicMemberNames() { // not returning the Model property here return typeof(T).GetProperties().Select(x => x.Name).Concat(new[] { "IsSelected" }); } public PropertyDescriptorCollection GetProperties() { var props = GetDynamicMemberNames(); return new PropertyDescriptorCollection(props.Select(x => new DynamicPropertyDescriptor(x, GetType(), typeof(T))).ToArray()); } // some INotifyPropertyChanged implementation public event PropertyChangedEventHandler PropertyChanged; protected void RaisePropertyChangedEvent([CallerMemberName]string prop = null) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(prop)); } protected bool SetProperty<T2>(ref T2 store, T2 value, [CallerMemberName]string prop = null) { if (!object.Equals(store, value)) { store = value; RaisePropertyChangedEvent(prop); return true; } return false; } // ... A long list of interface method implementations that just throw NotImplementedException for the example } 破解了一种访问包装器和包装对象属性的方法。

DynamicPropertyDescriptor

现在,如果您将某些public class DynamicPropertyDescriptor : PropertyDescriptor { private Type ObjectType; private PropertyInfo Property; public DynamicPropertyDescriptor(string name, params Type[] objectType) : base(name, null) { ObjectType = objectType[0]; foreach (var t in objectType) { Property = t.GetProperty(name); if (Property != null) { break; } } } public override object GetValue(object component) { var prop = component.GetType().GetProperty(Name); if (prop != null) { return prop.GetValue(component); } DynamicObject obj = component as DynamicObject; if (obj != null) { var binder = new MyGetMemberBinder(Name); object value; obj.TryGetMember(binder, out value); return value; } return null; } public override void SetValue(object component, object value) { var prop = component.GetType().GetProperty(Name); if (prop != null) { prop.SetValue(component, value); } DynamicObject obj = component as DynamicObject; if (obj != null) { var binder = new MySetMemberBinder(Name); obj.TrySetMember(binder, value); } } public override Type PropertyType { get { return Property.PropertyType; } } public override bool IsReadOnly { get { return !Property.CanWrite; } } public override bool CanResetValue(object component) { return false; } public override Type ComponentType { get { return typeof(object); } } public override void ResetValue(object component) { } public override bool ShouldSerializeValue(object component) { return false; } } public class MyGetMemberBinder : GetMemberBinder { public MyGetMemberBinder(string name) : base(name, false) { } public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) { throw new NotImplementedException(); } } public class MySetMemberBinder : SetMemberBinder { public MySetMemberBinder(string name) : base(name, false) { } public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, DynamicMetaObject value, DynamicMetaObject errorSuggestion) { throw new NotImplementedException(); } } 绑定到datagrid itemssource,则应填充TypedList<SelectionWrapper<ItemViewModel>>的列和IsSelected的属性。

让我再说一遍 - 整个方法有点hacky,我在这里的实现远非稳定。

当我再考虑一下时,只要ItemViewModel用于定义列而某些DynamicObject可以访问,则可能不需要整个TypedList内容。包装器和模型的属性。