我对 WPF(来自 Winforms)还很陌生。我正在使用 .Net 4.5 和 WPF 框架附带的默认 DataGrid。这些列是动态创建的,因为我在编译时不知道。现在,基于数据,一些列将是只读的,一些列将是 ComboBox 类型。
- 如何申请 this logic dynamically同时动态创建列,如下所示。这是我到目前为止写的代码。每当数据发生变化时,都会根据数据动态生成列。
- 此外,如何根据数据动态生成“不同类型”的列(ComboBox、TextBox 等)。 WPF 中的 MVVM-ish 方式有点限制我,因为我对模板化了解不多。我相信一旦我通过了它应该很容易。
注意:目前一切正常。我有一个只读数据绑定(bind)网格。但是,不支持选择性可编辑列和选择性 ComboBox 列。
public class DatagridExtension {
public static readonly DependencyProperty RefDataSourceProperty =
DependencyProperty.RegisterAttached(
"RefDataSource",
typeof(RefDataRecord),
typeof(DatagridExtension),
new PropertyMetadata( default(RefDataRecord), OnRefDataSourceChanged)
);
private static void OnRefDataSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as DataGrid;
var dataSource = e.NewValue as RefDataRecord;
grid.ItemsSource = dataSource;
grid.Columns.Clear();
int count = 0;
foreach (var col in dataSource.Columns)
{
grid.Columns.Add(
new DataGridTextColumn
{
Header = col.Name,
Binding = new Binding(string.Format("[{0}]", count))
}
);
count++;
}
}
public static RefDataRecord GetRefDataSource(DependencyObject dependencyObject)
{
return (RefDataRecord) dependencyObject.GetValue(RefDataSourceProperty);
}
public static void SetRefDataSource(DependencyObject dependencyObject, RefDataRecord value)
{
dependencyObject.SetValue(RefDataSourceProperty, value);
}
}
最佳答案
WPF DataGrid 创建 DataGridComboBoxColumn默认情况下,如果数据源属性类型派生自 Enum并设置 DataGridColumn.IsReadyOnly默认情况下,如果属性没有 public setter 或者如果属性有 ReadOnlyAttribute与 ReadOnlyAttribute.IsReadOnly = 真。
如果您的数据源属性不满足上述默认条件,我现在将展示如何自定义 DataGrid 列生成。
首先,我将介绍两个属性,用于指定属性是只读的(EditableAttribute)和该属性应该可视化为带有预定义下拉项的组合框(NameValueAttribute)。
这是EditableAttribute.cs:
using System;
namespace WpfApplication
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class EditableAttribute : Attribute
{
public bool AllowEdit { get; set; }
}
}
这是NameValueAttribute.cs:
using System;
namespace WpfApplication
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public sealed class NameValueAttribute : Attribute
{
public string Name { get; set; }
public object Value { get; set; }
}
}
接下来,我们需要一些用于演示的示例类。
这里是 Person.cs 类,它将代表 DataGrid 中的单个项目(行):
using System.ComponentModel;
namespace WpfApplication
{
public class Person : ObservableObject
{
private string name;
private string surname;
private char gender;
public string Name
{
get { return this.name; }
set { this.SetValue(ref this.name, value, "Name"); }
}
[Editable(AllowEdit = false)]
public string Surname
{
get { return this.surname; }
set { this.SetValue(ref this.surname, value, "Surname"); }
}
[NameValue(Name = "Male", Value = 'M')]
[NameValue(Name = "Female", Value = 'F')]
public char Gender
{
get { return this.gender; }
set { this.SetValue(ref this.gender, value, "Gender"); }
}
}
}
请注意 Surname 属性如何应用 EditableAttribute 以及 Gender 属性如何应用 NameValueAttributes。
这里是代表 DataGrid 数据源的 People.cs 类:
using System.Collections.ObjectModel;
namespace WpfApplication
{
public class People : ObservableCollection<Person>
{
public People()
{
for (int i = 0; i < 100; ++i)
this.Items.Add(new Person()
{
Name = "Name " + i,
Surname = "Surname " + i,
Gender = i % 2 == 0 ? 'M' : 'F'
});
}
}
}
Person 的基类是 ObservableObject.cs,它对所有数据绑定(bind)应用程序都是通用的:
using System.Collections.Generic;
using System.ComponentModel;
namespace WpfApplication
{
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void SetValue<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
}
}
现在,这是托管 DataGrid 控件的 MainWindow.xaml 的 XAML:
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication">
<Window.Resources>
<local:People x:Key="itemsSource"/>
</Window.Resources>
<DataGrid ItemsSource="{StaticResource itemsSource}" AutoGeneratingColumn="OnAutoGeneratingColumn"/>
</Window>
关键部分是 DataGrid.AutoGeneratingColumn事件处理程序 OnAutoGeneratingColumn
。
此事件在 DataGrid 生成 DataGridColumn 后触发,并为每个自动生成的列触发一次。它用于自定义自动生成的列或指定不同的列,具体取决于 provided data source property .
这是 MainWindow.xaml.cs 代码隐藏,其中 OnAutoGeneratingColumn
事件处理程序正是这样做的。如果数据源属性具有 AllowEdit = false 的 EditableAttribute,它通过将生成的列设置为只读来自定义生成的列,如果数据源属性具有 NameValueAttributes,它会使用 DataGridComboBoxColumn 覆盖自动生成的列:
using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var propertyDescriptor = (PropertyDescriptor)e.PropertyDescriptor;
var dataBoundColumn = (DataGridBoundColumn)e.Column;
var comboBoxColumn = GenerateComboBoxColumn(propertyDescriptor, dataBoundColumn);
if (comboBoxColumn != null)
e.Column = comboBoxColumn;
if (IsReadOnlyProperty(propertyDescriptor))
e.Column.IsReadOnly = true;
}
private static DataGridComboBoxColumn GenerateComboBoxColumn(PropertyDescriptor propertyDescriptor, DataGridBoundColumn dataBoundColumn)
{
var nameValueAttributes = Attribute.GetCustomAttributes(propertyDescriptor.ComponentType.GetProperty(propertyDescriptor.Name)).OfType<NameValueAttribute>().ToArray();
if (nameValueAttributes.Length > 0)
return new DataGridComboBoxColumn()
{
ItemsSource = nameValueAttributes,
DisplayMemberPath = "Name",
SelectedValuePath = "Value",
SelectedValueBinding = dataBoundColumn.Binding
};
else
return null;
}
private static bool IsReadOnlyProperty(PropertyDescriptor propertyDescriptor)
{
var editableAttribute = propertyDescriptor.Attributes.OfType<EditableAttribute>().FirstOrDefault();
return editableAttribute != null ? !editableAttribute.AllowEdit : false;
}
}
}
动态案例更新:
WPF 通过 ICustomTypeDescriptor 支持动态反射在数据项和 ITypedList 上实现在集合上实现。 此外,.NET 4.5 支持 ICustomTypeProvider ,但由于我没有安装 .NET 4.5,所以我还没有测试它。
NameValueAttribute.cs 与之前相同。
下面是工作示例中 ICustomTypeDescriptor 和 ITypedList 的非常简单的实现:
DataProperty.cs
using System;
using System.ComponentModel;
namespace WpfApplication
{
public class DataProperty : PropertyDescriptor
{
private readonly Type propertyType;
private readonly bool isReadOnly;
private readonly Attribute[] attributes;
public DataProperty(string propertyName, Type propertyType, bool isReadOnly, params Attribute[] attributes)
: base(propertyName, null)
{
this.propertyType = propertyType;
this.isReadOnly = isReadOnly;
this.attributes = attributes;
}
protected override Attribute[] AttributeArray
{
get { return this.attributes; }
set { throw new NotImplementedException(); }
}
public override Type ComponentType
{
get { return typeof(DataRecord); }
}
public override Type PropertyType
{
get { return this.propertyType; }
}
public override bool IsReadOnly
{
get { return this.isReadOnly; }
}
public override object GetValue(object component)
{
return ((DataRecord)component)[this.Name];
}
public override void SetValue(object component, object value)
{
if (!this.isReadOnly)
((DataRecord)component)[this.Name] = value;
}
#region Not implemented PropertyDescriptor Members
public override bool CanResetValue(object component)
{
throw new NotImplementedException();
}
public override void ResetValue(object component)
{
throw new NotImplementedException();
}
public override bool ShouldSerializeValue(object component)
{
throw new NotImplementedException();
}
#endregion
}
}
DataRecord.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace WpfApplication
{
public class DataRecord : INotifyPropertyChanged, ICustomTypeDescriptor
{
public event PropertyChangedEventHandler PropertyChanged;
internal ITypedList container;
private readonly IDictionary<string, object> values = new SortedList<string, object>();
public object this[string propertyName]
{
get
{
object value;
this.values.TryGetValue(propertyName, out value);
return value;
}
set
{
if (!object.Equals(this[propertyName], value))
{
this.values[propertyName] = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, e);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return this.container.GetItemProperties(null);
}
#region Not implemented ICustomTypeDescriptor Members
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
throw new NotImplementedException();
}
string ICustomTypeDescriptor.GetClassName()
{
throw new NotImplementedException();
}
string ICustomTypeDescriptor.GetComponentName()
{
throw new NotImplementedException();
}
TypeConverter ICustomTypeDescriptor.GetConverter()
{
throw new NotImplementedException();
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
throw new NotImplementedException();
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
throw new NotImplementedException();
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
throw new NotImplementedException();
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
throw new NotImplementedException();
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
throw new NotImplementedException();
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
throw new NotImplementedException();
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
throw new NotImplementedException();
}
#endregion
}
}
DataRecordCollection.cs:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfApplication
{
public class DataRecordCollection<T> : ObservableCollection<T>, ITypedList where T : DataRecord
{
private readonly PropertyDescriptorCollection properties;
public DataRecordCollection(params DataProperty[] properties)
{
this.properties = new PropertyDescriptorCollection(properties);
}
protected override void InsertItem(int index, T item)
{
item.container = this;
base.InsertItem(index, item);
}
PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
{
return this.properties;
}
string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
{
throw new NotImplementedException();
}
}
}
主窗口.xaml:
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication">
<DataGrid x:Name="dataGrid" AutoGeneratingColumn="OnAutoGeneratingColumn"/>
</Window>
MainWindow.xaml.cs:
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var records = new DataRecordCollection<DataRecord>(
new DataProperty("Name", typeof(string), false),
new DataProperty("Surname", typeof(string), true),
new DataProperty("Gender", typeof(char), false, new NameValueAttribute() { Name = "Male", Value = 'M' }, new NameValueAttribute() { Name = "Female", Value = 'F' }));
for (int i = 0; i < 100; ++i)
{
var record = new DataRecord();
record["Name"] = "Name " + i;
record["Surname"] = "Surname " + i;
record["Gender"] = i % 2 == 0 ? 'M' : 'F';
records.Add(record);
}
this.dataGrid.ItemsSource = records;
}
private void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
e.Column.Header = ((PropertyDescriptor)e.PropertyDescriptor).DisplayName;
var propertyDescriptor = (PropertyDescriptor)e.PropertyDescriptor;
var dataBoundColumn = (DataGridBoundColumn)e.Column;
var comboBoxColumn = GenerateComboBoxColumn(propertyDescriptor, dataBoundColumn);
if (comboBoxColumn != null)
e.Column = comboBoxColumn;
}
private static DataGridComboBoxColumn GenerateComboBoxColumn(PropertyDescriptor propertyDescriptor, DataGridBoundColumn dataBoundColumn)
{
var nameValueAttributes = propertyDescriptor.Attributes.OfType<NameValueAttribute>().ToArray();
if (nameValueAttributes.Length > 0)
return new DataGridComboBoxColumn()
{
ItemsSource = nameValueAttributes,
DisplayMemberPath = "Name",
SelectedValuePath = "Value",
SelectedValueBinding = dataBoundColumn.Binding
};
else
return null;
}
}
}
关于c# - WPF:Datagrid - 动态应用 DataGridTemplateColumn.CellTemplate,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13880616/