我是WPF的新手(来自Winforms)。我正在使用.Net 4.5和WPF中随框架一起提供的默认DataGrid。列是动态创建的,因为我在编译时不知道。现在,基于数据,一些列将是只读的,一些列将是ComboBox类型。
注意:目前这一切都运转良好。我有一个只读的数据绑定网格。但是,不支持选择性可编辑列和选择性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);
}
}
答案 0 :(得分:2)
如果数据源属性类型派生自DataGridComboBoxColumn,则默认情况下WPF DataGrid会创建Enum,如果属性没有公共setter或属性为{{3},则默认设置DataGridColumn.IsReadyOnly } ReadOnlyAttribute = true。
如果您的数据源属性不满足上述默认条件,我现在将展示如何自定义DataGrid列生成。
首先,我将介绍两个用于指定属性是只读的属性(EditableAttribute),并且该属性应该可视化为具有预定义下拉项的ComboBox(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 ,这对所有数据绑定应用程序都是通用的:
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>
关键部分是ReadOnlyAttribute.IsReadOnly事件处理程序OnAutoGeneratingColumn
。
DataGrid生成DataGridColumn后会触发此事件,并且会为每个自动生成的列触发一次。它用于自定义自动生成的列或指定不同的列,具体取决于DataGrid.AutoGeneratingColumn。
这是 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支持动态反射,provided data source property在数据项上实现,ICustomTypeDescriptor在集合上实现。 此外,.NET 4.5支持ITypedList,但由于我没有安装.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();
}
}
}
<强> MainWindow.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;
}
}
}
答案 1 :(得分:1)
首先,WPF到WinForms的主要优点之一是能够使用模板声明用户界面。并且您应该尽可能避免在代码中声明UI组件。
据我了解,您希望根据对象类型/数据显示不同对象的集合。 实现此类逻辑的最佳方式 - 实现您自己的TemplateSelector
我建议你阅读下一篇文章:
P.S。 以供参考。在代码中声明DataTemplate的示例:
//create the data template
DataTemplate cardLayout = new DataTemplate();
cardLayout.DataType = typeof(CreditCardPayment);
//set up the stack panel
FrameworkElementFactory spFactory = new FrameworkElementFactory(typeof(StackPanel));
spFactory.Name = "myComboFactory";
spFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
//set up the card holder textblock
FrameworkElementFactory cardHolder = new FrameworkElementFactory(typeof(TextBlock));
cardHolder.SetBinding(TextBlock.TextProperty, new Binding("BillToName"));
cardHolder.SetValue(TextBlock.ToolTipProperty, "Card Holder Name");
spFactory.AppendChild(cardHolder);
//set up the card number textblock
FrameworkElementFactory cardNumber = new FrameworkElementFactory(typeof(TextBlock));
cardNumber.SetBinding(TextBlock.TextProperty, new Binding("SafeNumber"));
cardNumber.SetValue(TextBlock.ToolTipProperty, "Credit Card Number");
spFactory.AppendChild(cardNumber);
//set up the notes textblock
FrameworkElementFactory notes = new FrameworkElementFactory(typeof(TextBlock));
notes.SetBinding(TextBlock.TextProperty, new Binding("Notes"));
notes.SetValue(TextBlock.ToolTipProperty, "Notes");
spFactory.AppendChild(notes);
//set the visual tree of the data template
cardLayout.VisualTree = spFactory;
//set the item template to be our shiny new data template
drpCreditCardNumberWpf.ItemTemplate = cardLayout;
但正如我上面所说,你应该避免这种情况。
答案 2 :(得分:1)
这是正确答案 - http://www.paulstovell.com/dynamic-datagrid(动态参见模板创建逻辑。它很聪明)。
并且,MMVM将会像这样实现 - http://www.codeproject.com/Articles/36462/Binding-a-ListView-to-a-Data-Matrix(几乎我在问题中发布的内容)
答案 3 :(得分:1)
我离开互联网已有几天了,但我认为我已经找到了更简单的PropertyDescriptor架构的方法,它不需要实现ICustomTypeDescriptor。这是整个代码:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var records = new RecordCollection(new Property("Name"), new Property("Surname"));
for (int i = 0; i < 1000; ++i)
records.Add(new Record()
{
{ "Name", "John " + i },
{ "Surname", "Doe " + i }
});
this.dataGrid.ItemsSource = records;
}
private void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var property = e.PropertyDescriptor as Property;
if (property != null)
{
var binding = new Binding() { Path = new PropertyPath(property), Mode = property.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay };
var dataGridBoundColumn = e.Column as DataGridBoundColumn;
if (dataGridBoundColumn != null)
dataGridBoundColumn.Binding = binding;
else
{
var dataGridComboBoxColumn = e.Column as DataGridComboBoxColumn;
if (dataGridComboBoxColumn != null)
dataGridComboBoxColumn.SelectedItemBinding = binding;
}
}
}
}
public sealed class Record : INotifyPropertyChanged, IEnumerable
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly IDictionary<string, object> values = new SortedList<string, object>(StringComparer.Ordinal);
private void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, e);
}
public object GetValue(string name)
{
object value;
return this.values.TryGetValue(name, out value) ? value : null;
}
public void SetValue(string name, object value)
{
if (!object.Equals(this.GetValue(name), value))
{
this.values[name] = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
}
public void Add(string name, object value)
{
this.values[name] = value;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.values.GetEnumerator();
}
}
public sealed class Property : PropertyDescriptor
{
private readonly Type propertyType;
private readonly bool isReadOnly;
public Property(string name)
: this(name, typeof(string))
{
}
public Property(string name, Type propertyType)
: this(name, propertyType, false)
{
}
public Property(string name, Type propertyType, bool isReadOnly, params Attribute[] attributes)
: base(name, attributes)
{
this.propertyType = propertyType;
this.isReadOnly = isReadOnly;
}
public override Type ComponentType
{
get { return typeof(Record); }
}
public override Type PropertyType
{
get { return this.propertyType; }
}
public override bool IsReadOnly
{
get { return this.isReadOnly; }
}
public override object GetValue(object component)
{
var record = component as Record;
return record != null ? record.GetValue(this.Name) : null;
}
public override void SetValue(object component, object value)
{
var record = component as Record;
if (record != null)
record.SetValue(this.Name, value);
}
public override bool CanResetValue(object component)
{
throw new NotSupportedException();
}
public override void ResetValue(object component)
{
throw new NotSupportedException();
}
public override bool ShouldSerializeValue(object component)
{
throw new NotSupportedException();
}
}
public sealed class RecordCollection : ObservableCollection<Record>, ITypedList
{
private readonly PropertyDescriptorCollection properties;
public RecordCollection(params Property[] properties)
{
this.properties = new PropertyDescriptorCollection(properties);
}
PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
{
return this.properties;
}
string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
{
return string.Empty;
}
}
}
<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>
此代码中的关键是使用包含Property实例的BindingPath创建Binding,而不是字符串。这样可以简化PropertyDescriptor体系结构,因为不再需要ICustomTypeDescriptor。
您对此解决方案有何看法?