【问题标题】:Using ComboBox to show second-level properties in DataGridView使用 ComboBox 在 DataGridView 中显示二级属性
【发布时间】:2021-01-13 23:29:41
【问题描述】:

据我了解,ComboBox 列在 DataGridView 中的绑定比标准列更具动态性,并且这种灵活性可用于从二阶属性中使用 DisplayMembers。这种方法是hereMr. Aghaei第一次提到的。

但是,我没有做对。我的应用程序仍然抛出“名称”不存在的异常。

 public void CreateEmployeeTable()
        {

            DataGridViewComboBoxColumn jobTitleColumn = new DataGridViewComboBoxColumn();
            jobTitleColumn.HeaderText = "Job Title";
            jobTitleColumn.DataPropertyName = "JobTitle";
            jobTitleColumn.DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing;
            jobTitleColumn.DataPropertyName = "ID";
            jobTitleColumn.DataSource = globalEmployeeList;
            jobTitleColumn.ValueMember = "ID";
            jobTitleColumn.DisplayMember = "Name";
            jobTitleColumn.ReadOnly = true;

            employeeGridView.AutoGenerateColumns = false;
            employeeGridView.ColumnCount = 2;
            employeeGridView.Columns[0].HeaderText = "Employee ID";
            employeeGridView.Columns[0].DisplayIndex = 0;
            employeeGridView.Columns[0].DataPropertyName = "ID";
            employeeGridView.Columns[1].HeaderText = "Name";
            employeeGridView.Columns[1].DataPropertyName = "ListView";
            employeeGridView.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
            employeeGridView.Columns.Add(jobTitleColumn);
            
            employeeGridView.DataSource = globalEmployeeList;                                                                               
        }

这是类定义:

     
    public class EmployeeModel
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstName { get; set; }
        public string Nickname { get; set; }
        public DepartmentModel Department { get; set; }
        public TitleModel JobTitle { get; set; }
        public DateTime HireDate { get; set; }
        public List<EmailModel> EmailList { get; set; } = new List<EmailModel>();
        public List<PhoneModel> PhoneList { get; set; } = new List<PhoneModel>();
        public List<RestrictionModel> RestrictionsList { get; set; } = new List<RestrictionModel>();
        public List<CitationModel> CitationsList { get; set; } = new List<CitationModel>();
        public List<CertificationModel> CertificationList { get; set; } = new List<CertificationModel>();

        public string ListView
        {
            get
            {
                return $"{LastName}, {FirstName}";
            }
        }

        public string ToEmailString()
        {
            IEnumerable<string> employeeEmailStrings = EmailList.Select(emmod => emmod.ToString());
            string employeeEmailString = string.Join($"{Environment.NewLine}", employeeEmailStrings);

            IEnumerable<string> certificationStrings = CertificationList.Select(clistmod => clistmod.ToString());
            string certificationString = string.Join($"{Environment.NewLine}", certificationStrings);

            IEnumerable<string> phoneStrings = PhoneList.Select(plistmod => plistmod.ToString());
            string phoneString = string.Join($"{Environment.NewLine}", phoneStrings);

            return $"{FirstName}, {LastName}: {Environment.NewLine} -{JobTitle.Name}- {Environment.NewLine} {employeeEmailString} {Environment.NewLine} {certificationString} {Environment.NewLine} {phoneString}";
        }



        public class EmailModel
        {
            public int ID { get; set; }
            public string Address { get; set; }
            public string Type { get; set; }

            public override string ToString()
            {
                return $"{Address} ({Type})";
            }
        }

        public class PhoneModel
        {
            public int ID { get; set; }
            public string Number { get; set; }
            public string Type { get; set; }
            public override string ToString()
            {
                return $"{Number} ({Type})";
            }

        }
    }

以及 TitleModel 的定义:

    public class TitleModel
    {
        public string Name { get; set; }
        public int ID { get; set; }  

    }
}

【问题讨论】:

    标签: c# .net winforms datagridview


    【解决方案1】:

    为了支持帖子Show Properties of a Navigation Property in DataGridView (Second Level Properties),我已经在同一个帖子或this one 中分享了一些示例,它允许显示二级属性并允许对其进行编辑。

    这里我再分享几个例子,每个例子都写成一个最小的完整的可验证的例子,你可以复制粘贴到一个空的表格中,它们就可以工作了。

    这些是例子:

    • 使用 ToString()
    • 使用单元格格式
    • 为导航对象使用组合框列
    • 为外键列使用组合框列

    示例 - 使用 ToString()

    何时:您不想更改员工的JobTitle

    如何:通过覆盖JobTitleToString方法

    class JobTitle
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public override string ToString()
        {
            return Name;
        }
    }
    class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public JobTitle JobTitle { get; set; }
    }
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        var jobTitles = new List<JobTitle>() {
            new JobTitle {Id= 1, Name="Manager" },
            new JobTitle {Id= 2, Name="Employee" },
        };
        var employees = new List<Employee>() {
            new Employee{ Id = 1, Name ="John", JobTitle = jobTitles[0] },
            new Employee{ Id = 2, Name ="Jane", JobTitle = jobTitles[1] },
            new Employee{ Id = 3, Name ="Jack", JobTitle = jobTitles[1] },
        };
        var dg = new DataGridView();
        dg.Dock = DockStyle.Fill;
        dg.DataSource = employees;
        this.Controls.Add(dg);
    }
    

    示例 - 使用单元格格式化

    何时:您不想更改员工的JobTitle

    如何:通过处理DataGridViewCellFormatting事件并将事件参数的Value设置为JobTitle的字符串表示形式

    class JobTitle
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public JobTitle JobTitle { get; set; }
    }
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        var jobTitles = new List<JobTitle>() {
            new JobTitle {Id= 1, Name="Manager" },
            new JobTitle {Id= 2, Name="Employee" },
        };
        var employees = new List<Employee>() {
            new Employee{ Id = 1, Name ="John", JobTitle = jobTitles[0] },
            new Employee{ Id = 2, Name ="Jane", JobTitle = jobTitles[1] },
            new Employee{ Id = 3, Name ="Jack", JobTitle = jobTitles[1] },
        };
        var dg = new DataGridView();
        dg.Dock = DockStyle.Fill;
        dg.DataSource = employees;
        dg.CellFormatting += (obj, args) =>
        {
            if (args.RowIndex >= 0 &&
                dg.Columns[args.ColumnIndex].DataPropertyName == "JobTitle")
                args.Value = ((Employee)dg.Rows[args.RowIndex].DataBoundItem).JobTitle.Name;
        };
        this.Controls.Add(dg);
    }
    

    示例 - 将 ComboBox 列用于外键列

    何时:您希望能够更改 EmployeeJobTitle,并且您的模型中有外键列。

    如何:为该属性使用DataGridViewComboBoxColumn,拥有一个包含所有职位的数据源,并将DisplayMemberValueMember 设置为适当的属性。

    class JobTitle
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int JobTitleId { get; set; }
    }
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        var jobTitles = new List<JobTitle>() {
            new JobTitle {Id= 1, Name="Manager" },
            new JobTitle {Id= 2, Name="Employee" },
        };
        var employees = new List<Employee>() {
            new Employee{ Id = 1, Name ="John", JobTitleId = 1 },
            new Employee{ Id = 2, Name ="Jane", JobTitleId = 2 },
            new Employee{ Id = 2, Name ="Jack", JobTitleId = 2 },
        };
        var dg = new DataGridView();
        dg.Dock = DockStyle.Fill;
        dg.DataSource = employees;
        dg.Columns.Add(new DataGridViewTextBoxColumn() 
        { 
            DataPropertyName = "Id", HeaderText = "Id" 
        });
        dg.Columns.Add(new DataGridViewTextBoxColumn() 
        {
            DataPropertyName = "Name", HeaderText = "Name" 
        });
        dg.Columns.Add(new DataGridViewComboBoxColumn()
        {
            DataPropertyName = "JobTitleId",
            HeaderText = "JobTitleId",
            DataSource = jobTitles,
            ValueMember = "Id",
            DisplayMember = "Name",
        });
        this.Controls.Add(dg);
    }
    

    示例 - 将 ComboBox 列用于导航对象

    何时:您希望能够更改 EmployeeJobTitle 并且您的模型中没有外键列,而是希望使用导航对象你的模型。

    如何: 为该属性使用 DataGridViewComboBoxColumn,具有包含所有职位的数据源,而不将 DisplayMemberValueMember 设置为适当的属性。然后处理CellFormatting设置单元格的显示值,处理CellParsingComboBox获取值并放入单元格中。

    class JobTitle
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public override string ToString()
        {
            return Name;
        }
    }
    class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public JobTitle JobTitle { get; set; }
    }
    
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        var jobTitles = new List<JobTitle>() {
            new JobTitle {Id= 1, Name="Manager" },
            new JobTitle {Id= 1, Name="Employee" },
        };
        var employees = new List<Employee>() {
            new Employee{ Id = 1, Name ="John", JobTitle = jobTitles[0] },
            new Employee{ Id = 2, Name ="Jane", JobTitle = jobTitles[1] },
            new Employee{ Id = 2, Name ="Jack", JobTitle = jobTitles[1] },
        };
        var dg = new DataGridView();
        dg.Dock = DockStyle.Fill;
        dg.DataSource = employees;
        dg.Columns.Add(new DataGridViewTextBoxColumn() 
        { 
            DataPropertyName = "Id", HeaderText = "Id" 
        });
        dg.Columns.Add(new DataGridViewTextBoxColumn() 
        {
            DataPropertyName = "Name", HeaderText = "Name" 
        });
        dg.Columns.Add(new DataGridViewComboBoxColumn()
        {
            DataPropertyName = "JobTitle",
            HeaderText = "JobTitle",
            DataSource = jobTitles,
        });
        dg.CellFormatting += (obj, args) =>
        {
            if (args.RowIndex >= 0 &&
                dg.Columns[args.ColumnIndex].DataPropertyName == "JobTitle")
            {
                args.Value = 
            ((Employee)dg.Rows[args.RowIndex].DataBoundItem).JobTitle.ToString();
            }
        };
        dg.CellParsing += (obj, args) =>
        {
            if (args.RowIndex >= 0 &&
                dg.Columns[args.ColumnIndex].DataPropertyName == "JobTitle")
            {
                args.Value = ((ComboBox)dg.EditingControl).SelectedItem;
                args.ParsingApplied = true;
            }
        };
        this.Controls.Add(dg);
    }
    

    【讨论】:

    • 这真的很棒。我现在意识到使用导航属性的解决方案并不打算使用外键。此外,我遇到的问题之一是将类对象分配为属性,而您的方法将外键设置为属性,并且 C# 能够像 sql 那样映射这种关系。这个对吗?假设我应该停止将类分配为另一个类的属性而是使用外键,或者两者都有时间吗?
    • 在模型中包含外键是一种选择。您可以选择包含外键(以及导航属性),也可以选择仅保留导航属性。我个人更喜欢在模型中保留外键列;它使数据绑定和编辑等 UI 场景更容易。在上面的例子中,我已经在最后两个例子中介绍了这两种情况(有/没有外键)。
    • 感谢您的帮助和耐心等待。我只在 C# 中学习了大约一个月,我仍在学习它的语法和库。通过查看您的代码,我学到了很多关于语言的知识,超出了我的操作范围,例如使用 base 关键字。我对dg.CellParsing += (obj, args) =&gt; 结构特别感兴趣,并将继续阅读该语法。
    • 不用担心@JoshuaWhite。 myControl.Click += (obj, args) =&gt; {/*something*/} 基本上是使用指定的 lambda 事件处理程序来告诉句柄 ClickmyControl。类似于myControl.Click += myControl_Click,后来有void myControl_Click(object sender, EventArgs e) {/*something*/}的功能
    • 顺便说一句,我注意到您没有接受或赞成您收到的问题答案。我没有查看答案,但如果您不熟悉 Accept 和 Upvote,请查看 tour。这根本不是强制性的,但建议这样做,因为它可以使帖子对未来的读者更有用,并为作者提供一些声誉分数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-20
    • 1970-01-01
    • 1970-01-01
    • 2017-03-18
    • 2015-08-21
    • 1970-01-01
    相关资源
    最近更新 更多