从对象集合自动创建 DataGridViewComboBoxCell 属性
Automatically create DataGridViewComboBoxCell from object's collection property
使用 WinForms,我正在尝试编写一种方法来检查绑定到 DataGridView 中的行的数据项是否包含 IList
作为 属性,然后自动转换 DataGridViewTextBoxCell
到 DataGridViewComboBoxCell
绑定该列表作为数据源。 objective 将有一个下拉菜单,其中每一行的值都不同,具体取决于找到的对象的列表 属性 中的元素。因此,例如,在第一行中,下拉列表可以有 3 个类型为 ObjA
的对象作为选项,第二行可以有 5 个类型为 ObjC
的对象,依此类推。
这是我的:
private void dgv_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
foreach (DataGridViewRow row in dgv.Rows)
{
object obj = row.DataBoundItem;
if (obj != null)
{
IEnumerable listProperties = obj.GetType().GetProperties().Where(p => p.GetValue(obj) is IList);
foreach (PropertyInfo list in listProperties)
{
DataGridViewComboBoxCell cell = new DataGridViewComboBoxCell();
IList source = (IList)list.GetValue(obj, null);
cell.DataSource = source;
cell.ValueMember = "id";
cell.DisplayMember = "name";
cell.ValueType = source.GetType().GetProperty("Item").PropertyType;
cell.Value = source[0].GetType().GetProperty("id").GetValue(source[0]);
(row.Cells[list.Name]) = cell;
}
}
}
}
这是将初始数据绑定到 DataGridView
:
的代码
IBindingList source = new BindingList<Models.InputValue>(datasource);
inputsDgv.AutoGenerateColumns = true;
inputsDgv.AllowUserToAddRows = false;
inputsDgv.AllowUserToDeleteRows = false;
inputsDgv.DataSource = source;
问题: 我在加载 DataGridView 时遇到 "DataGridViewComboBoxCell value is not valid" 错误。我注意到在 运行 行 (row.Cells[list.Name]) = cell;
之后,cell.ValueType
从 System.Collections.Generic.IList1[[roco.Models.ISnapshots]]
变为 System.Int32
。我认为这一定是问题所在。
有人知道我该如何解决这个错误吗?
谢谢!
P.S.: row.DataBoundItem
是 InputValue
类型,列表 属性 是 ProjectionSnapshot
个对象的集合
public class InputValue : IValues
{
private int _id;
public int id { get { return _id; } }
private IList<ISnapshots> _snapshots;
public IList<ISnapshots> snapshots
{
get { return _snapshots; }
set { _snapshots = value; }
}
}
public class ProjectionSnapshot : ISnapshots
{
private int _id;
public int id { get { return _id; } }
private string _name;
public string name
{
get { return _name; }
set
{
if (value.Length > 255)
Console.WriteLine("Error! SKU snapshot name must be less than 256 characters!");
else
_name = value;
}
}
}
public interface ISnapshots
{
int id { get; }
string name { get; set; }
}
正如@Loathing 所建议的,DataGridViewColumns 与相应的 Cell class 紧密耦合。不能在文本列中使用组合单元格。
您可以使用 GridView,用于按列排列数据并向 ListView 添加布局和设计支持。 GridView 用作 ListView 的补充控件,以提供样式和布局。 GridView 没有自己的控件相关属性,例如背景色和前景色、字体属性、大小和位置。容器 ListView 用于提供所有与控件相关的属性。 Read More about GridView in WPF
如果你想根据所选行在下拉列表中有不同的值,那么你将需要使用 CellBeginEdit
事件并更改组合框编辑控件的 DataSource
.
示例见此答案:
TL;DR:跳到 解决方案 部分。
将 DataGridViewTextBoxCell
更改为 DataGridViewComboBoxCell
是可能的,但如果组合框值的类型与为该列设置的 ValueType
不同,则会导致问题。这是因为,正如@Loathing 和@Mohit Shrivastava 所提到的,DataGridViewColumns
与相应的 Cell class.
紧密耦合
阅读@Loathing 的代码示例后,我尝试在更改 DataGridViewTextBoxCell
之前将 Column 的 ValueType
设置为 typeof(Object)
。这不起作用,因为当您将对象绑定到 DataGridView
并使用 AutoGenerateColumns
时,有一种机制会自动设置列的 ValueType
以反映 [=54 的类型=] 在绑定对象中。如果您生成自己的列并将列的 DataPropertyName
设置为对象 属性 名称,情况也是如此。
解决方案:
1) 生成您自己的列,将对象属性映射到 DataGridViewTextBoxCell
但不映射到 DataGridViewComboBoxCell
:
P.S.: 我不再检查 IList,而是检查任何 IEnumerable(有关详细信息,请参阅此答案:)
public void loadGrid<T>(IList<T> datasource)
{
generateDataGridViewColumns<T>(datasource);
IBindingList source = new BindingList<T>(datasource);
inputsDgv.AutoGenerateColumns = false;
inputsDgv.AllowUserToAddRows = false;
inputsDgv.AllowUserToDeleteRows = false;
inputsDgv.DataSource = source;
}
private void generateDataGridViewColumns<T>(IList<T> datasource)
{
dgv.Columns.Clear();
if (datasource != null)
{
foreach (PropertyInfo property in typeof(T).GetProperties())
{
DataGridViewColumn col;
var displayNameObj = property.GetCustomAttributes(typeof(DisplayNameAttribute), true).Cast<DisplayNameAttribute>().FirstOrDefault();
string displayName = (displayNameObj == null) ? property.Name : displayNameObj.DisplayName;
if (property.PropertyType.GetInterface(typeof(IEnumerable<>).FullName) != null && property.PropertyType != typeof(string))
{
col = new DataGridViewComboBoxColumn();
(col as DataGridViewComboBoxColumn).AutoComplete = false;
(col as DataGridViewComboBoxColumn).ValueType = typeof(Object);
}
else
{
col = new DataGridViewTextBoxColumn() { DataPropertyName = property.Name };
}
col.Name = property.Name;
col.HeaderText = displayName;
ReadOnlyAttribute attrib = Attribute.GetCustomAttribute(property, typeof(ReadOnlyAttribute)) as ReadOnlyAttribute;
col.ReadOnly = (!property.CanWrite || (attrib != null && attrib.IsReadOnly));
inputsDgv.Columns.Add(col);
}
}
}
2) 使用 DataBindingComplete 事件填充组合框:
private void dgv_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
foreach (DataGridViewRow row in dgv.Rows)
{
object obj = row.DataBoundItem;
if (obj != null)
{
IEnumerable listProperties = obj.GetType().GetProperties().Where(p => p.GetValue(obj) is IList);
foreach (PropertyInfo list in listProperties)
{
IList source = (IList)list.GetValue(obj, null);
DataGridViewComboBoxCell cell = (row.Cells[list.Name] as DataGridViewComboBoxCell);
cell.DataSource = source;
cell.ValueType = source.GetType().GetProperty("Item").PropertyType;
ValueMember valueMember = (ValueMember)obj.GetType().GetProperty(list.Name).GetCustomAttribute(typeof(ValueMember));
DisplayMember displayMember = (DisplayMember)obj.GetType().GetProperty(list.Name).GetCustomAttribute(typeof(DisplayMember));
if(valueMember != null && displayMember != null)
{
cell.ValueMember = valueMember.Value;
cell.DisplayMember = displayMember.Value;
}
cell.Value = source[0].GetType().GetProperty("id").GetValue(source[0]);
}
}
}
}
3) 创建 ValueMember 和 DisplayMember 属性 classes:
[System.AttributeUsage(System.AttributeTargets.Property)]
public class ValueMember : System.Attribute
{
public string Value { get; private set; }
public ValueMember(string valueMember)
{
this.Value = valueMember;
}
}
[System.AttributeUsage(System.AttributeTargets.Property)]
public class DisplayMember : System.Attribute
{
public string Value { get; private set; }
public DisplayMember(string displayMember)
{
this.Value = displayMember;
}
}
4) 使用属性:
public class InputValue
{
public string id{ get; set; }
public string name{ get; set; }
[DisplayName("Values")]
[ValueMember("id")]
[DisplayMember("name")]
public IList<IValue> values{ get; set; }
}
使用 WinForms,我正在尝试编写一种方法来检查绑定到 DataGridView 中的行的数据项是否包含 IList
作为 属性,然后自动转换 DataGridViewTextBoxCell
到 DataGridViewComboBoxCell
绑定该列表作为数据源。 objective 将有一个下拉菜单,其中每一行的值都不同,具体取决于找到的对象的列表 属性 中的元素。因此,例如,在第一行中,下拉列表可以有 3 个类型为 ObjA
的对象作为选项,第二行可以有 5 个类型为 ObjC
的对象,依此类推。
这是我的:
private void dgv_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
foreach (DataGridViewRow row in dgv.Rows)
{
object obj = row.DataBoundItem;
if (obj != null)
{
IEnumerable listProperties = obj.GetType().GetProperties().Where(p => p.GetValue(obj) is IList);
foreach (PropertyInfo list in listProperties)
{
DataGridViewComboBoxCell cell = new DataGridViewComboBoxCell();
IList source = (IList)list.GetValue(obj, null);
cell.DataSource = source;
cell.ValueMember = "id";
cell.DisplayMember = "name";
cell.ValueType = source.GetType().GetProperty("Item").PropertyType;
cell.Value = source[0].GetType().GetProperty("id").GetValue(source[0]);
(row.Cells[list.Name]) = cell;
}
}
}
}
这是将初始数据绑定到 DataGridView
:
IBindingList source = new BindingList<Models.InputValue>(datasource);
inputsDgv.AutoGenerateColumns = true;
inputsDgv.AllowUserToAddRows = false;
inputsDgv.AllowUserToDeleteRows = false;
inputsDgv.DataSource = source;
问题: 我在加载 DataGridView 时遇到 "DataGridViewComboBoxCell value is not valid" 错误。我注意到在 运行 行 (row.Cells[list.Name]) = cell;
之后,cell.ValueType
从 System.Collections.Generic.IList1[[roco.Models.ISnapshots]]
变为 System.Int32
。我认为这一定是问题所在。
有人知道我该如何解决这个错误吗?
谢谢!
P.S.: row.DataBoundItem
是 InputValue
类型,列表 属性 是 ProjectionSnapshot
个对象的集合
public class InputValue : IValues
{
private int _id;
public int id { get { return _id; } }
private IList<ISnapshots> _snapshots;
public IList<ISnapshots> snapshots
{
get { return _snapshots; }
set { _snapshots = value; }
}
}
public class ProjectionSnapshot : ISnapshots
{
private int _id;
public int id { get { return _id; } }
private string _name;
public string name
{
get { return _name; }
set
{
if (value.Length > 255)
Console.WriteLine("Error! SKU snapshot name must be less than 256 characters!");
else
_name = value;
}
}
}
public interface ISnapshots
{
int id { get; }
string name { get; set; }
}
正如@Loathing 所建议的,DataGridViewColumns 与相应的 Cell class 紧密耦合。不能在文本列中使用组合单元格。
您可以使用 GridView,用于按列排列数据并向 ListView 添加布局和设计支持。 GridView 用作 ListView 的补充控件,以提供样式和布局。 GridView 没有自己的控件相关属性,例如背景色和前景色、字体属性、大小和位置。容器 ListView 用于提供所有与控件相关的属性。 Read More about GridView in WPF
如果你想根据所选行在下拉列表中有不同的值,那么你将需要使用 CellBeginEdit
事件并更改组合框编辑控件的 DataSource
.
示例见此答案:
TL;DR:跳到 解决方案 部分。
将 DataGridViewTextBoxCell
更改为 DataGridViewComboBoxCell
是可能的,但如果组合框值的类型与为该列设置的 ValueType
不同,则会导致问题。这是因为,正如@Loathing 和@Mohit Shrivastava 所提到的,DataGridViewColumns
与相应的 Cell class.
阅读@Loathing 的代码示例后,我尝试在更改 DataGridViewTextBoxCell
之前将 Column 的 ValueType
设置为 typeof(Object)
。这不起作用,因为当您将对象绑定到 DataGridView
并使用 AutoGenerateColumns
时,有一种机制会自动设置列的 ValueType
以反映 [=54 的类型=] 在绑定对象中。如果您生成自己的列并将列的 DataPropertyName
设置为对象 属性 名称,情况也是如此。
解决方案:
1) 生成您自己的列,将对象属性映射到 DataGridViewTextBoxCell
但不映射到 DataGridViewComboBoxCell
:
P.S.: 我不再检查 IList,而是检查任何 IEnumerable(有关详细信息,请参阅此答案:)
public void loadGrid<T>(IList<T> datasource)
{
generateDataGridViewColumns<T>(datasource);
IBindingList source = new BindingList<T>(datasource);
inputsDgv.AutoGenerateColumns = false;
inputsDgv.AllowUserToAddRows = false;
inputsDgv.AllowUserToDeleteRows = false;
inputsDgv.DataSource = source;
}
private void generateDataGridViewColumns<T>(IList<T> datasource)
{
dgv.Columns.Clear();
if (datasource != null)
{
foreach (PropertyInfo property in typeof(T).GetProperties())
{
DataGridViewColumn col;
var displayNameObj = property.GetCustomAttributes(typeof(DisplayNameAttribute), true).Cast<DisplayNameAttribute>().FirstOrDefault();
string displayName = (displayNameObj == null) ? property.Name : displayNameObj.DisplayName;
if (property.PropertyType.GetInterface(typeof(IEnumerable<>).FullName) != null && property.PropertyType != typeof(string))
{
col = new DataGridViewComboBoxColumn();
(col as DataGridViewComboBoxColumn).AutoComplete = false;
(col as DataGridViewComboBoxColumn).ValueType = typeof(Object);
}
else
{
col = new DataGridViewTextBoxColumn() { DataPropertyName = property.Name };
}
col.Name = property.Name;
col.HeaderText = displayName;
ReadOnlyAttribute attrib = Attribute.GetCustomAttribute(property, typeof(ReadOnlyAttribute)) as ReadOnlyAttribute;
col.ReadOnly = (!property.CanWrite || (attrib != null && attrib.IsReadOnly));
inputsDgv.Columns.Add(col);
}
}
}
2) 使用 DataBindingComplete 事件填充组合框:
private void dgv_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
foreach (DataGridViewRow row in dgv.Rows)
{
object obj = row.DataBoundItem;
if (obj != null)
{
IEnumerable listProperties = obj.GetType().GetProperties().Where(p => p.GetValue(obj) is IList);
foreach (PropertyInfo list in listProperties)
{
IList source = (IList)list.GetValue(obj, null);
DataGridViewComboBoxCell cell = (row.Cells[list.Name] as DataGridViewComboBoxCell);
cell.DataSource = source;
cell.ValueType = source.GetType().GetProperty("Item").PropertyType;
ValueMember valueMember = (ValueMember)obj.GetType().GetProperty(list.Name).GetCustomAttribute(typeof(ValueMember));
DisplayMember displayMember = (DisplayMember)obj.GetType().GetProperty(list.Name).GetCustomAttribute(typeof(DisplayMember));
if(valueMember != null && displayMember != null)
{
cell.ValueMember = valueMember.Value;
cell.DisplayMember = displayMember.Value;
}
cell.Value = source[0].GetType().GetProperty("id").GetValue(source[0]);
}
}
}
}
3) 创建 ValueMember 和 DisplayMember 属性 classes:
[System.AttributeUsage(System.AttributeTargets.Property)]
public class ValueMember : System.Attribute
{
public string Value { get; private set; }
public ValueMember(string valueMember)
{
this.Value = valueMember;
}
}
[System.AttributeUsage(System.AttributeTargets.Property)]
public class DisplayMember : System.Attribute
{
public string Value { get; private set; }
public DisplayMember(string displayMember)
{
this.Value = displayMember;
}
}
4) 使用属性:
public class InputValue
{
public string id{ get; set; }
public string name{ get; set; }
[DisplayName("Values")]
[ValueMember("id")]
[DisplayMember("name")]
public IList<IValue> values{ get; set; }
}