我正在尝试在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中添加更多列,但也拥有包装对象的所有公共属性!
答案 0 :(得分:0)
有没有办法更改AutoGeneration的目标以显示所有模型属性?
简短回答:不。
只会创建您设置为T
的{{1}}类型IEnumerable<T>
的每个公共属性列。
您应该考虑将ItemsSource
属性设置为AutoGenerateColumns
并以编程方式创建列,而不是在XAML标记中对其进行硬编码。
答案 1 :(得分:0)
在Binding DynamicObject to a DataGrid with automatic column generation?的过程之后,以下内容应该在某种程度上起作用,但我不确定我是否会在制作中使用类似的东西:
创建一个实现ITypedList
和IList
的集合。来自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
内容。包装器和模型的属性。