【发布时间】:2017-10-26 09:10:10
【问题描述】:
我正在尝试在 WPF 中实现某种对象选择器。到目前为止,我已经创建了一个带有 DataGrid 的窗口,其中 ItemsSource 绑定到一个 ObservableCollection。我还将 AutoGenerateColumns 设置为“true”,因为要选择的 Item 可以是任何类型的 ob 对象。 集合中的对象被包装在一个 SelectionWrapper 中,其中包含一个 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'。
有没有办法改变自动生成的目标以显示模型的所有属性? 此外,有必要将 AutoGeneratedColumns 设置为只读,因为没有人应该编辑显示的条目。
无法关闭 AutoGenerateColumns 并添加更多手动列,例如
<DataGridTextColumn Binding="{Binding Model.[SomeProperty]}"/>
因为模型可以是任何类型的对象。也许有办法将 AutoGeneration 的目标路由到模型属性?
提前致谢
编辑
接受@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 中添加更多列,而且还具有包装对象的所有公共属性!
【问题讨论】:
-
请参阅Binding DynamicObject to a DataGrid with automatic column generation 了解可能的方法...会写一个答案,但它既不简单也不完整;)
-
我认为这是错误的方式(也许)。问题是,SelectionWrapper 中的属性“模型”可以是任何类型,即元组、日期时间等。如果我离开包装器并直接绑定到 T 集合,所有属性都会按预期自动构建,但我错过了“IsSelected”列。