c# - WPF:Datagrid - 动态应用 DataGridTemplateColumn.CellTemplate

标签 c# wpf

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

http://msdn.microsoft.com/en-us/library/system.windows.controls.datagridtemplatecolumn.celltemplate(v=vs.95).aspx

最佳答案

WPF DataGrid 创建 DataGridComboBoxColumn默认情况下,如果数据源属性类型派生自 Enum并设置 DataGridColumn.IsReadyOnly默认情况下,如果属性没有 public setter 或者如果属性有 ReadOnlyAttributeReadOnlyAttribute.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/

相关文章:

c# - 方法作用域变量的事件会发生什么?

wpf - 这个 DataGridComboBoxColumn 绑定(bind)语法有什么问题?

WPF:绑定(bind)更改时如何触发EventTrigger(或动画)?

wpf - 如果绑定(bind)源为空,如何为图像设置默认源?

c# - 对于链式异步/事件调用是否有有用的设计模式?

c# - 设计决策: MVVM and WPF with two Views?

wpf - 阻止 WPF 文本框失去焦点

c# wpf 数据绑定(bind)没有发生。

javascript - 切换窗口句柄并按 Enter

c# - C# 和 C++ 中的可选参数