【问题标题】:DataAnnotation Attribute in Windows Forms - How to use UIHint Attribute with WinFormsWindows 窗体中的 DataAnnotation 属性 - 如何在 WinForms 中使用 UIHint 属性
【发布时间】:2021-03-09 20:23:19
【问题描述】:

我有一个DataGrid 和一个List<Foo> 作为数据源。我希望数据网格使用我的自定义控件作为Foo.Value 属性的编辑器。我相信这是UIHintAttribute 的目的,但它没有效果。我知道我可以明确地创建列,并分配ValueColumn.ColumnEdit = new FooValueEditor();,但我试图避开大量 UI 代码,并依赖网格从公共属性中推断列。 p>

    class Foo
    {
        public string Name { get; set; }

        [UIHint("FooValueEditor", "WinForms")]
        public int Value { get; set; }
    }

    public class FooValueEditor : System.Windows.Forms.TextBox
    {
        public FooValueEditor() : base()
        {
            ...
        }
    }

我尝试为我的自定义编辑器提供完整的命名空间。我发现很多在 Asp.NET 中使用的属性示例,但属性构造函数采用 presentationLayer 参数:

可以设置为“HTML”、“Silverlight”、“WPF”或“WinForms”。

我希望它支持 WinForms。难道我做错了什么?这不可能吗?

编辑:

关于评论“这里的 DataGrid 是什么”。我正在使用一组third partycontrols,我错误地假设它继承自DataGrid。它支持同一命名空间中的validation attributes(这是他们的 Aps.Net 文档,但对于 WinForms 至少部分正确),所以我希望 UIHint 可能会得到支持。看起来我应该直接向第三方提供商开票,但与此同时,如果我选择,下面的答案将帮助我自己实现这一点。

【问题讨论】:

  • 这里的 DataGrid 是什么?一个数据网格视图?您可以构建一个自定义 DataGridViewColumn,使用自定义/用户控件进行数据输入/编辑:How to: Host Controls in Windows Forms DataGridView Cells -- 标记您的问题以指定您正在使用的正确平台和对象。
  • 您需要编写代码使框架使用UIHint 属性。要了解一些想法,请查看此帖子:DataAnnotations attributes for DataGridView in Windows Forms。对于自定义编辑器(内置列类型除外),您需要创建自定义 datagridview 列类型,如 Jimi 共享的文档中的示例。
  • 嗯,我分享的答案只对 Windows 窗体 DataGrdiView 有效,既不适用于第 3 方控件,也不适用于 WPF 或 WinForms DataGrid。对于第 3 方控件,您很可能需要参考他们的文档。编辑问题添加在标签中添加正确的控件也是一个好主意。
  • devexprss 网格的链接也指向一个 ASP.NET WebForm 控件。相当混乱的问题。
  • 我已根据您的反馈进行了进一步编辑。

标签: c# winforms gridview attributes devexpress


【解决方案1】:

Windows 窗体中没有对 Data Annotation 属性(包括 UIHint)的内置支持,但考虑到这些属性的工作方式,您可以扩展框架和控件以在 Windows 窗体中使用这些属性。

在这篇文章中,我将扩展我在另一篇文章中分享的关于 DataAnnotations attributes for DataGridView in Windows Forms 的答案,以便您可以拥有以下功能:

  • 列的可见性:由[Browsable] 属性控制。您还可以依赖 [Display] 属性的 AutoGenerateField 属性。
  • 列的标题文本:由[Display] 属性的Name 控制。
  • 列顺序:由[Display] 属性的Order 控制。
  • 列提示:由[DisplayFormat]属性控制。
  • 列类型:由[UIHint] 属性控制。

所以在模型上设置数据注释属性后,如果你像这样this.dataGridView1.Bind(list, true);设置datagridveiw你会看到:

以下是示例的构建块:

  • 有一个 UIHintMappings 类负责将 UI 提示映射到不同的 DataGridViewColumn 类型。每个 UI 提示都将映射到 Func(一种工厂方法),该方法创建所需 DataGridViewColumn 的实例。例如Text 将映射到()=>new DataGridViewTextBoxColumn()。您可以根据自己的要求添加或删除映射。

  • 有一个Bind<T> 扩展方法负责通过应用数据注释属性使用列表生成DataGridview 的列。您可以在此处更改列创建的逻辑;例如添加对新属性的支持。

  • 对于每个非标准列类型,您需要按照此处的说明/示例创建自己的列类型:How to: Host Controls in Windows Forms DataGridView Cells,然后添加映射。

示例

  1. 创建一个 Windows 窗体应用程序。

  2. 在 Form1 上放置一个 DataGridView 的实例(并将其设置为停靠在父容器中)

  3. 在项目中添加Person.cs文件,并将以下代码粘贴到文件中:

    using System;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    
    public class Person
    {
        [Display(Name = "Id")]
        [Browsable(false)]
        public int? Id { get; set; }
    
        [Display(Name = "First Name", Description = "First name.", Order = 1)]
        [UIHint("TextBox")]
        public string FirstName { get; set; }
    
        [Display(Name = "Last Name", Description = "Last name", Order = 2)]
        [UIHint("TextBox")]
        public string LastName { get; set; }
    
        [Display(Name = "Birth Date", Description = "Date of birth.", Order = 4)]
        [DisplayFormat(DataFormatString = "yyyy-MM-dd")]
        [UIHint("Calendar")]
        public DateTime BirthDate { get; set; }
    
        [Display(Name = "Homepage", Description = "Url of homepage.", Order = 5)]
        [UIHint("Link")]
        public string Url { get; set; }
    
        [Display(Name = "Member", Description = "Is member?", Order = 3)]
        [UIHint("CheckBox")]
        public bool IsMember { get; set; }
    }
    
  4. 创建一个名为 DataGridViewCalendarColumn.cs 的代码文件并将以下代码粘贴到文件中(这是基于MS Docs example,只是为了尊重更改格式而稍作改动):

    using System;
    using System.Windows.Forms;
    
    public class DataGridViewCalendarColumn : DataGridViewColumn
    {
        public DataGridViewCalendarColumn() : base(new DataGridViewCalendarCell())
        {
        }
        public override DataGridViewCell CellTemplate
        {
            get
            {
                return base.CellTemplate;
            }
            set
            {
                // Ensure that the cell used for the template is a CalendarCell.
                if (value != null && 
                !value.GetType().IsAssignableFrom(typeof(DataGridViewCalendarCell)))
                {
                    throw new InvalidCastException("Must be a CalendarCell");
                }
                base.CellTemplate = value;
            }
        }
    }
    
    public class DataGridViewCalendarCell : DataGridViewTextBoxCell
    {
        public DataGridViewCalendarCell()
            : base()
        {
            // Use the short date format.
            // this.Style.Format = "d";
        }
        public override void InitializeEditingControl(int rowIndex, object
            initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
        {
            // Set the value of the editing control to the current cell value.
            base.InitializeEditingControl(rowIndex, initialFormattedValue,
                dataGridViewCellStyle);
            DataGridViewCalendarEditingControl ctl =
                DataGridView.EditingControl as DataGridViewCalendarEditingControl;
            // Use the default row value when Value property is null.
            if (this.Value == null)
            {
                ctl.Value = (DateTime)this.DefaultNewRowValue;
            }
            else
            {
                ctl.Value = (DateTime)this.Value;
            }
        }
    
        public override Type EditType
        {
            get
            {
                // Return the type of the editing control that CalendarCell uses.
                return typeof(DataGridViewCalendarEditingControl);
            }
        }
    
        public override Type ValueType
        {
            get
            {
                // Return the type of the value that CalendarCell contains.
    
                return typeof(DateTime);
            }
        }
    
        public override object DefaultNewRowValue
        {
            get
            {
                // Use the current date and time as the default value.
                return DateTime.Now;
            }
        }
    }
    
    class DataGridViewCalendarEditingControl : DateTimePicker, 
        IDataGridViewEditingControl
    {
        DataGridView dataGridView;
        private bool valueChanged = false;
        int rowIndex;
    
        public DataGridViewCalendarEditingControl()
        {
            //this.Format = DateTimePickerFormat.Short;
        }
    
        // Implements the IDataGridViewEditingControl.EditingControlFormattedValue
        // property.
        public object EditingControlFormattedValue
        {
            get
            {
                return this.Value.ToShortDateString();
            }
            set
            {
                if (value is String)
                {
                    try
                    {
                        // This will throw an exception of the string is
                        // null, empty, or not in the format of a date.
                        this.Value = DateTime.Parse((String)value);
                    }
                    catch
                    {
                        // In the case of an exception, just use the
                        // default value so we're not left with a null
                        // value.
                        this.Value = DateTime.Now;
                    }
                }
            }
        }
    
        // Implements the
        // IDataGridViewEditingControl.GetEditingControlFormattedValue method.
        public object GetEditingControlFormattedValue(
            DataGridViewDataErrorContexts context)
        {
            return EditingControlFormattedValue;
        }
    
        // Implements the
        // IDataGridViewEditingControl.ApplyCellStyleToEditingControl method.
        public void ApplyCellStyleToEditingControl(
            DataGridViewCellStyle dataGridViewCellStyle)
        {
            this.Font = dataGridViewCellStyle.Font;
            this.CalendarForeColor = dataGridViewCellStyle.ForeColor;
            this.CalendarMonthBackground = dataGridViewCellStyle.BackColor;
            if (!string.IsNullOrEmpty(dataGridViewCellStyle.Format))
            {
                this.Format = DateTimePickerFormat.Custom;
                this.CustomFormat = dataGridViewCellStyle.Format;
            }
            else
            {
                this.Format = DateTimePickerFormat.Short;
            }
        }
    
        // Implements the IDataGridViewEditingControl.EditingControlRowIndex
        // property.
        public int EditingControlRowIndex
        {
            get
            {
                return rowIndex;
            }
            set
            {
                rowIndex = value;
            }
        }
    
        // Implements the IDataGridViewEditingControl.EditingControlWantsInputKey
        // method.
        public bool EditingControlWantsInputKey(
            Keys key, bool dataGridViewWantsInputKey)
        {
            // Let the DateTimePicker handle the keys listed.
            switch (key & Keys.KeyCode)
            {
                case Keys.Left:
                case Keys.Up:
                case Keys.Down:
                case Keys.Right:
                case Keys.Home:
                case Keys.End:
                case Keys.PageDown:
                case Keys.PageUp:
                    return true;
                default:
                    return !dataGridViewWantsInputKey;
            }
        }
    
        // Implements the IDataGridViewEditingControl.PrepareEditingControlForEdit
        // method.
        public void PrepareEditingControlForEdit(bool selectAll)
        {
            // No preparation needs to be done.
        }
    
        // Implements the IDataGridViewEditingControl
        // .RepositionEditingControlOnValueChange property.
        public bool RepositionEditingControlOnValueChange
        {
            get
            {
                return false;
            }
        }
    
        // Implements the IDataGridViewEditingControl
        // .EditingControlDataGridView property.
        public DataGridView EditingControlDataGridView
        {
            get
            {
                return dataGridView;
            }
            set
            {
                dataGridView = value;
            }
        }
    
        // Implements the IDataGridViewEditingControl
        // .EditingControlValueChanged property.
        public bool EditingControlValueChanged
        {
            get
            {
                return valueChanged;
            }
            set
            {
                valueChanged = value;
            }
        }
    
        // Implements the IDataGridViewEditingControl
        // .EditingPanelCursor property.
        public Cursor EditingPanelCursor
        {
            get
            {
                return base.Cursor;
            }
        }
    
        protected override void OnValueChanged(EventArgs eventargs)
        {
            // Notify the DataGridView that the contents of the cell
            // have changed.
            valueChanged = true;
            this.EditingControlDataGridView.NotifyCurrentCellDirty(true);
            base.OnValueChanged(eventargs);
        }
    }
    
  5. 在项目中添加UIHintMappings.cs文件,并将以下代码粘贴到文件中:

    using System;
    using System.Collections.Generic;
    using System.Windows.Forms;
    
    public class UIHintMappings
    {
        public static Dictionary<string, Func<DataGridViewColumn>> DataGridViewColumns
        {
            get;
        }
        static UIHintMappings()
        {
            DataGridViewColumns = new Dictionary<string, Func<DataGridViewColumn>>();
            DataGridViewColumns.Add("TextBox", 
                () => new DataGridViewTextBoxColumn());
            DataGridViewColumns.Add("CheckBox", 
                () => new DataGridViewCheckBoxColumn(false));
            DataGridViewColumns.Add("TreeStateCheckBox", 
                () => new DataGridViewCheckBoxColumn(true));
            DataGridViewColumns.Add("Link", 
                () => new DataGridViewLinkColumn());
            DataGridViewColumns.Add("Calendar", 
                () => new DataGridViewCalendarColumn());
        }
    }
    
  6. DataGridViewExtensions.cs文件添加到项目中,并将以下代码粘贴到文件中:

    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Windows.Forms;
    
    public static class DataGridViewExtensions
    {
        public static void Bind<T>(this DataGridView grid, IList<T> data,
            bool autoGenerateColumns = true)
        {
            if (autoGenerateColumns)
            {
                var properties = TypeDescriptor.GetProperties(typeof(T));
                var metedata = properties.Cast<PropertyDescriptor>().Select(p => new
                {
                    Name = p.Name,
                    HeaderText = p.Attributes.OfType<DisplayAttribute>()
                        .FirstOrDefault()?.Name ?? p.DisplayName,
                    ToolTipText = p.Attributes.OfType<DisplayAttribute>()
                        .FirstOrDefault()?.GetDescription() ?? p.Description,
                    Order = p.Attributes.OfType<DisplayAttribute>()
                        .FirstOrDefault()?.GetOrder() ?? int.MaxValue,
                    Visible = p.IsBrowsable,
                    ReadOnly = p.IsReadOnly,
                    Format = p.Attributes.OfType<DisplayFormatAttribute>()
                        .FirstOrDefault()?.DataFormatString,
                    Type = p.PropertyType,
                    UIHint = p.Attributes.OfType<UIHintAttribute>()
                        .FirstOrDefault()?.UIHint
                });
                var columns = metedata.OrderBy(m => m.Order).Select(m =>
                {
                    DataGridViewColumn c;
                    if(!string.IsNullOrEmpty( m.UIHint) && 
                    UIHintMappings.DataGridViewColumns.ContainsKey(m.UIHint))
                    {
                        c = UIHintMappings.DataGridViewColumns[m.UIHint].Invoke();
                    }
                    else
                    {
                        c = new DataGridViewTextBoxColumn();
                    }
                    c.DataPropertyName = m.Name;
                    c.Name = m.Name;
                    c.HeaderText = m.HeaderText;
                    c.ToolTipText = m.ToolTipText;
                    c.DefaultCellStyle.Format = m.Format;
                    c.ReadOnly = m.ReadOnly;
                    c.Visible = m.Visible;
                    return c;
                });
                grid.Columns.Clear();
                grid.Columns.AddRange(columns.ToArray());
            }
            grid.DataSource = data;
        }
    }
    
  7. 在设计模式下双击Form1并使用以下代码处理Load事件:

    private void Form1_Load(object sender, EventArgs e)
    {
        var list = new List<Person>()
        {
            new Person()
            {
                Id= 1, FirstName= "Mario", LastName= "Speedwagon",
                BirthDate = DateTime.Now.AddYears(-30).AddMonths(2).AddDays(5),
                IsMember = true, Url ="https://Mario.example.com"
            },
            new Person()
            {
                Id= 1, FirstName= "Petey", LastName= "Cruiser",
                BirthDate = DateTime.Now.AddYears(-20).AddMonths(5).AddDays(1),
                IsMember = false, Url ="https://Petey.example.com"
            },
            new Person()
            {
                Id= 1, FirstName= "Anna", LastName= "Sthesia",
                BirthDate = DateTime.Now.AddYears(-40).AddMonths(3).AddDays(8),
                IsMember = true, Url ="https://Anna.example.com"
            },
        };
    
        this.dataGridView1.Bind(list, true);
    }
    

运行项目,然后就可以了!您可以看到属性如何帮助生成列。

未来读者的改进点

  1. 您也可以添加对数据注释验证的支持。为此,您可以使用Validator 类实现IDataErrorInfo 接口,就像我在DataAnnotations Validation attributes for Windows Forms 中所做的那样。

  2. 您可以使用DataGridViewComboBoxColumn(如this post)轻松添加对枚举列的支持。

  3. 您可以通过以下方式改进映射:如果没有为 UIHint 定义映射,则作为备用方法查看列的类型,例如使用 DataGridViewCheckBoxColumn 表示 bool 属性。

【讨论】:

    猜你喜欢
    • 2013-02-28
    • 1970-01-01
    • 1970-01-01
    • 2012-06-13
    • 2014-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-31
    相关资源
    最近更新 更多