包装器内部动态对象的C#DataGrid AutoGenerateColumns [英] C# DataGrid AutoGenerateColumns for dynamic Object inside Wrapper

查看:76
本文介绍了包装器内部动态对象的C#DataGrid AutoGenerateColumns的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  class SelectionWrapper< T> :INotifyPropertyChanged 
{
//以下属性包括PropertyChanged
public bool IsSelected {[...]}
public T模型{[...]}
}

我还向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。



是否可以更改自动生成的目标以显示所有模型属性?
另外,有必要使AutoGeneratedColumns为ReadOnly,因为没有人可以编辑显示的条目。



没有选择关闭AutoGenerateColumns并添加更多手册的选项列

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

因为模型可以是任何对象。也许有一种方法可以将自动生成的目标路由到模型属性?



预先感谢



编辑



接受@ 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);
}
}
}

对象模型= null;
公共对象Model
{
get {return this.model; }
set
{
if(this.model!= value)
{
this.model = value;
this.OnPropertyChanged( Model);
}
}
}

public SelectionProperty(对象模型)
{
this.Model = model;
}
#region ICustomTypeDescriptor
[...]
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
返回TypeDescriptor.GetProperties(this.Model.GetType ());
}

对象ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
if(pd.DisplayName == IsSelected)
返回此值;

返回this.Model;
}
#endregion

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

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

}

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

公共字符串GetListName(PropertyDescriptor [] listAccessors)
{
返回null;
}
}

最后,最后一个是ViewModel。最重要的行是

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

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

解决方案

按照将DynamicObject绑定到具有自动列生成功能的DataGrid吗?,以下内容在某种程度上应该可以工作,但是我不确定我是否会在生产中使用类似的东西:



创建一个实现 ITypedList IList 的集合。将使用 ITypedList 中的 GetItemProperties 。期望列表类型实现 ICustomTypeDescriptor

 公共类TypedList< T> ; :List< T>,ITypedList,IList 
,其中T:ICustomTypeDescriptor
{
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor [] listAccessors)
{
if(this.Any( ))
{
返回this [0] .GetProperties();
}
返回new PropertyDescriptorCollection(new PropertyDescriptor [0]);
}

公共字符串GetListName(PropertyDescriptor [] listAccessors)
{
返回null;
}
}

实施 SelectionWrapper< T> 作为 DynamicObject 并实现 ICustomTypeDescriptor (至少 PropertyDescriptorCollection GetProperties ()方法)

 公共类SelectionWrapper< T> :DynamicObject,INotifyPropertyChanged,ICustomTypeDescriptor 
{
私人布尔值_IsSelected;
public bool IsSelected
{
get {return _IsSelected; }
set {SetProperty(ref _IsSelected,value); }
}


私人T _Model;
public T Model
{
get {return _Model; }
set {SetProperty(ref _Model,value); }
}

公共替代布尔值TryGetMember(GetMemberBinder活页夹,出对象结果)
{
if(Model!= null)
{
var prop = typeof(T).GetProperty(binder.Name);
//索引器成员将需要参数...如果(prop!= null&& prop.CanRead&& prop.GetMethod!= null&& prop .GetMethod.GetParameters()。Length == 0)
{
结果= prop.GetValue(Model);
返回true;
}
}
返回基准。TryGetMember(binder,out result);
}

公共替代IEnumerable< string> GetDynamicMemberNames()
{
//这里不返回Model属性
返回typeof(T).GetProperties()。Select(x => x.Name).Concat(new [] { IsSelected});
}

public PropertyDescriptorCollection GetProperties()
{
var props = GetDynamicMemberNames();
返回new PropertyDescriptorCollection(props.Select(x => new DynamicPropertyDescriptor(x,GetType(),typeof(T)))。ToArray());
}

//一些INotifyPropertyChanged实现

公共事件PropertyChangedEventHandler PropertyChanged;
受保护的void RaisePropertyChangedEvent([CallerMemberName] string prop = null)
{
var handler = PropertyChanged;
if(handler!= null)handler(this,new PropertyChangedEventArgs(prop));
}

受保护的布尔值SetProperty< T2>(参考T2商店,T2值,[CallerMemberName]字符串属性= null)
{
if(!object.Equals ((商店,价值))
{
商店=价值;
RaisePropertyChangedEvent(prop);
返回true;
}
返回false;
}

// ...一长串的接口方法实现,只是为示例
}抛出NotImplementedException
}

DynamicPropertyDescriptor 破解了一种访问包装器和包装对象属性的方法。

 公共类DynamicPropertyDescriptor:PropertyDescriptor 
{
private Type ObjectType;
private PropertyInfo财产;
public DynamicPropertyDescriptor(字符串名称,params Type [] objectType):base(名称,null)
{
ObjectType = objectType [0];
foreach(objectType中的var t)
{
属性= t.GetProperty(name);
if(Property!= null)
{
休息;
}
}
}

公共重写对象GetValue(对象组件)
{
var prop = component.GetType()。GetProperty (名称);
if(prop!= null)
{
return prop.GetValue(component);
}
DynamicObject obj =组件为DynamicObject;
if(obj!= null)
{
varinder = new MyGetMemberBinder(Name);
对象值;
obj.TryGetMember(binder,out value);
的返回值;
}
返回null;
}

公共重写void SetValue(对象组件,对象值)
{
var prop = component.GetType()。GetProperty(Name);
if(prop!= null)
{
prop.SetValue(component,value);
}
DynamicObject obj =组件为DynamicObject;
if(obj!= null)
{
varinder = new MySetMemberBinder(Name);
obj.TrySetMember(binder,value);
}
}

公共替代类型PropertyType
{
get {return Property.PropertyType; }
}

公共替代布尔值IsReadOnly
{
get {return!Property.CanWrite; }
}

公共替代布尔值CanResetValue(object component)
{
return false;
}

公共重写类型ComponentType
{
get {return typeof(object); }
}

公共替代无效ResetValue(对象组件)
{
}

公共替代布尔值ShouldSerializeValue(对象组件)
{
返回false;
}
}

公共类MyGetMemberBinder:GetMemberBinder
{
public MyGetMemberBinder(string name)
:base(name,false)
{

}
公共重写DynamicMetaObject FallbackGetMember(DynamicMetaObject target,DynamicMetaObject errorSuggestion)
{
throw new NotImplementedException();
}
}
public class MySetMemberBinder:SetMemberBinder
{
public MySetMemberBinder(string name)
:base(name,false)
{

}
公共重写DynamicMetaObject FallbackSetMember(DynamicMetaObject目标,DynamicMetaObject值,DynamicMetaObject errorSuggestion)
{
throw new NotImplementedException();
}
}

现在,如果您绑定一些将TypedList< SelectionWrapper< ItemViewModel>>> 添加到datagrid项目源,它应填充 IsSelected 的列和 ItemViewModel 。



让我再说一遍-整个方法有点棘手,我的实现还很不稳定。 p>

我想得更多,只要<< c> c的动态对象可能就没有真正的必要了。 code> TypedList 用于定义列,某些 DynamicPropertyDescriptor 用于访问包装器和模型中的属性。


I'm trying to implement some kind of Object Picker in WPF. So far I've created the Window with a DataGrid which ItemsSource is bound to an ObservableCollection. I also set AutoGenerateColumns to 'true' due to the fact that the Item to be picked can be any kind ob object. The Objects inside the Collection are wrapped in a SelectionWrapper< T> which contains an IsSelected Property in order to select them.

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

I also added a CustomColumn to the DataGrid.Columns in order to bind the IsSelected Property like so

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

The result I get with this solution is not very satisfying because there is just my defined Column 'Selected' and two GeneratedColumns 'IsSelected' and 'Model'.

Is there a way to change the target for the AutoGeneration to display all Properties of Model instead? Also it is necessary to make the AutoGeneratedColumns ReadOnly because no one should edit the displayed Entries.

It is no option to turn off AutoGenerateColumns and add some more manual Columns like

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

because the Model can be of any kind of Object. Maybe there is a way to route the target for AutoGeneration to the Model Property?

Thanks in Advance

Edit

After accepting the Answer of @grek40 I came up with the following

First I created a general class of SelectionProperty which is inherited in SelectionProperty<T>. Here I implement the Interface ICustomTypeDescriptor which finally looked like:

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

Then I created a specialized 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;
    }
}

Well and the last thing is the ViewModel. The most significant lines are

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);
}

The good thing here is, that I can still add more Columns in the XAML but also have all public Properties of the Wrapped Object!

解决方案

Following the course of Binding DynamicObject to a DataGrid with automatic column generation?, the following should work to some extent but I'm not quite sure if I would ever use something like it in production:

Create a collection that implements ITypedList and IList. GetItemProperties from ITypedList will be used. Expect the list type to implement 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;
    }
}

Implement SelectionWrapper<T> as DynamicObject and implement ICustomTypeDescriptor (at least the PropertyDescriptorCollection GetProperties() method)

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
}

The DynamicPropertyDescriptor hacks a way to access the properties of the wrapper and the wrapped object.

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();
    }
}

Now if you bind some TypedList<SelectionWrapper<ItemViewModel>> to your datagrid itemssource, it should populate the columns for IsSelected and for the properties of ItemViewModel.

Let me say it again - the whole approach is a bit hacky and my implementation here is far from being stable.

As I think about it some more, there is probably no real need for the whole DynamicObject stuff as long as the TypedList is used to define the columns and some DynamicPropertyDescriptor to access the properties from wrapper and model.

这篇关于包装器内部动态对象的C#DataGrid AutoGenerateColumns的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆