在 DataGridView 中显示导航属性的属性(二级属性) [英] Show Properties of a Navigation Property in DataGridView (Second Level Properties)
问题描述
我正在尝试在
这是加载数据的代码
试试{_context.OrderSheets.Include(o => o.OrderSheetItems.Select(i => i.Product)).Load();orderSheetBindingSource.DataSource = _context.OrderSheets.Local.ToBindingList();}捕获(异常前)...
ProductId 位于单独的列中,仅用于试验,稍后将用作组合框.那么有没有办法将其他列绑定到 OrderSheetItem 的 Product 导航属性中的数据,或者我是否必须处理产品 id 上的 CellValueChanged 以物理设置其他列中的数据?如果有一种方法可以绑定列,那么是通过 OnLoad 中的代码还是网格视图列设计器中的某个位置?
TIA,迈克
您可以使用以下任一选项:
- 使用
DataGridViewComboBoxColumn
- 为子实体部分类添加相应的属性
- 使用
Linq
调整查询以包含导航属性的属性 - 使用
CellFormatting
事件获取子属性有界列的值 - 通过覆盖
ToString()
来显示对象的字符串表示 - 使用自定义
TypeDescriptor
来启用到子属性的数据绑定.
选项 1 - 使用 DataGridViewComboBoxColumn
用法:这种方法在您想要保持控件可编辑的情况下特别有用.
在这种方法中,您可以使用 DataGridViewComboBoxColumn
来显示导航属性的任何字段.要在网格中显示导航属性的多个字段子属性,请使用多个 DataGridViewComboBoxColumn
绑定到具有不同 DisplayMember
在这种方法中,除了 ProductId
列之外,还向网格添加更多 DataGridViewComboBoxColumn
,然后为所有其他组合列执行这些设置:
- 将它们的
DataPropertyName
设置为ProductId
- 将它们的
DataSource
属性设置为与用于主ProductId
列的数据源完全相同,例如productBindingSource
- 将它们的
ValueMember
设置为您为产品 ID 列设置的相同值成员,它是您的产品表的关键列.(ProductId
) - 将每个的
DisplayMember
设置为您要显示的列,例如,将其中一个设置为名称.一个是价格,一个是尺寸,......这样您就可以显示相关的实体字段. - 将它们的
ReadOnly
属性设置为true
.它使单元格只读. - 如果你想让列成为只读 将它们的
DisplayStyle
属性设置为Nothing
.它删除了下拉样式.
如果您想保持 ProductId
可编辑,请将其 DisplayStyle
保持为 DropDownButton
.这样,当您使用组合框更改 ProductId
列的值时,当您离开该行并移动到下一行时,您将看到该行的其他单元格,显示所选产品的其他属性.此外,由于其他组合框列是只读的并且没有组合框样式,因此用户无法更改它们的值,并且它们的作用就像只读文本框列一样,显示相关实体的其他属性.
选项 2 - 为子实体部分类添加相应的属性
用法: 当您不需要编辑值时,这种方法会很有用.
在这种方法中,您可以在子实体部分类中定义属性,返回父实体对应属性的值.例如对于产品名称,在订单项部分类中定义此属性:
public string ProductName{得到{如果 (this.Product != null)返回 this.Product.Name;别的返回字符串.空;}}
然后你可以在选择订单项时简单地包含产品并将网格列绑定到订单项的相应属性.
选项 3 - 调整查询以包含导航属性的属性
用法: 当您不需要编辑值时,这种方法会很有用.
您可以调整查询以包含导航属性的属性.您可以简单地使用匿名对象或查看模式,例如:
var list = db.OrderDetails.Include("Products").Where(x=>x.OrderId==1).Select(x=> new OrderDetailVM() {Id = x.Id,ProductId = x.ProductId,产品名称 = x.产品.名称,价格 = x.产品.价格}).ToList();
选项 4 - 使用 CellFormatting 事件获取子属性有界列的值
用法: 当您不需要编辑值时,这种方法会很有用.
在这种方法中,您可以使用 DataGridView
的 CellFormatting
事件.您可以根据列索引简单地设置 e.Value
.例如:
void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e){//我假设您想在索引 3 的列中显示产品名称if(e.RowIndex>=0 && e.ColumnIndex==3){var orderLineItem= (OrderLineItem)(this.dataGridView1.Rows[e.RowIndex].DataBoundItem);if (order!= null && orderLineItem.Product != null)e.Value = orderLineItem.Product.Name);}}
您可以使用不同的条件来处理不同的列并显示不同的子属性.
您还可以使用反射使其更具动态性和可重用性.您可以使用反射提取导航属性的子属性的值.为此,您应该创建列并将 DataPropertyName
设置为 Product.Name
等子属性,然后在 CellFormatting
事件中,使用反射,获取柱子.这是 Antonio Bello 撰写的一篇关于这种方法的好文章:
选项 5 - 通过覆盖 ToString()
用法: 当您不需要编辑值时,这种方法会很有用.
如果你只想显示单列导航属性,你可以简单地覆盖导航属性类的ToString()
方法并返回合适的值.这样,当在网格中显示该类型的属性时,您将看到一个友好的文本.例如在Product
的部分类中,你可以这样写:
公共覆盖字符串 ToString(){返回 this.Name;}
选项 6 - 使用自定义 TypeDescriptor 启用到子属性的数据绑定
用法: 当您不需要编辑值时,这种方法会很有用.
在这种方法中,您可以创建一个自定义 TypeDescriptor,使您能够对二级属性执行数据绑定.下面是 Linda Liu 关于这种方法的一篇好文章:
I'm trying to display several properties from a related entity on a DataGridView
in a winforms app. It seems pretty ordinary to me but I'm having trouble finding examples. It's an order entry operation. OrderSheet data, the ID and the pickup date for the order, then the line items (OrderSheetItems in the model below) in the grid. The order lineitems have a navigation property, Product, based on the ProductId. I can use a DataGridViewComboBoxColumn with ProductId as ValueMember and another field as DisplayMember. But I want to include more data in other columns, size, color, material, etc.
Here's the code for loading the data
try
{
_context.OrderSheets.Include(o => o.OrderSheetItems.Select(i => i.Product)).Load();
orderSheetBindingSource.DataSource = _context.OrderSheets.Local.ToBindingList();
}
catch (Exception ex)...
The ProductId is in a separate column just for experimenting, that will be the combobox later. So is there a way to bind the other columns to the data in Product navigation property of the OrderSheetItem or do I have to handle CellValueChanged on the product id to physically set the data in the other columns? If there's a way to bind the columns then would that be via code in OnLoad or somewhere in the grid view columns designer?
TIA, Mike
You can use either of these options:
- Use
DataGridViewComboBoxColumn
- Add corresponding properties to child entity partial class
- Shape the query to include properties of navigation property using
Linq
- Use
CellFormatting
event to get value for sub property bounded columns - Show string representation of object by overriding
ToString()
- Use a custom
TypeDescriptor
to enable data binding to sub properties.
Option 1 - Use DataGridViewComboBoxColumn
Usage: This approach would be useful specially in cases which you want to keep the control editable.
In this approach you can use DataGridViewComboBoxColumn
to show any field of navigationn property. To show multiple field sub properties of navigation property in grid, use multiple DataGridViewComboBoxColumn
bound to same navigation property with different DisplayMember
In this approach, additional to your ProductId
column, add more DataGridViewComboBoxColumn
to the grid and then perform these settings for all additional combo columns:
- Set
DataPropertyName
of them toProductId
- Set the
DataSource
property of them, to exactly the same data source you used for mainProductId
column, for exampleproductBindingSource
- Set
ValueMember
of them to the same value member you set for product id column, it's the key column of your product table.(ProductId
) - Set
DisplayMember
for each of them to a column that you want to show, for example, set one of them to Name. one to Price, one to Size, ... . This way you can show related entity fields. - Set
ReadOnly
property of them totrue
. It makes the cell read only. - If you want to make columns readonly Set
DisplayStyle
property of them toNothing
. It removes dropdown style.
If you want to keep ProductId
editable, keep the DisplayStyle
of it to DropDownButton
. This way when you change the value of ProductId
column using combobox, when you leave the row and moved to next row, you will see other cells of row, shows other properties of the selected product. Also since the other combobox columns are read only and have no combobox style, the user can not change the value of them and they act only like a read only text box column that show other properties from related entity.
Option 2 - Add corresponding properties to child entity partial class
Usage: This approach would be useful when you don't need to edit values.
In this approach, You can define properties in child entity partial class return value of corresponding property of parent entity. For example for product name, define this property in order item partial class:
public string ProductName
{
get
{
if (this.Product != null)
return this.Product.Name;
else
return string.Empty;
}
}
Then you can simply include products when selecting order items and bind the grid column to corresponding properties of order item.
Option 3 - Shape the query to include properties of navigation property
Usage: This approach would be useful when you don't need to edit values.
You can shape the query to include properties of navigation property. You can use an anonymous object or a View Mode simply, for example:
var list = db.OrderDetails.Include("Products").Where(x=>x.OrderId==1)
.Select(x=> new OrderDetailVM() {
Id = x.Id,
ProductId = x.ProductId,
ProductName = x.Product.Name,
Price = x.Product.Price
}).ToList();
Option 4 - Use CellFormatting event to get value for sub property bounded columns
Usage: This approach would be useful when you don't need to edit values.
In this approach you can use CellFormatting
event of DataGridView
. You can simply set e.Value
based on column index. For example:
void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
//I Suppose you want to show product name in column at index 3
if(e.RowIndex>=0 && e.ColumnIndex==3)
{
var orderLineItem= (OrderLineItem)(this.dataGridView1.Rows[e.RowIndex]
.DataBoundItem);
if (order!= null && orderLineItem.Product != null)
e.Value = orderLineItem.Product.Name);
}
}
You can use different criteria to handle different columns and show different sub properties.
Also you can make it more dynamic and reusable using reflection. You can extract the value of sub property of navigation property using reflection. To do so you should create column and set DataPropertyName
to sub properties like Product.Name
then in CellFormatting
event, using reflection, get the value for column. Here is a good article by Antonio Bello about this approach:
Option 5 - Show string representation of object by overriding ToString()
Usage: This approach would be useful when you don't need to edit values.
If you want to show only a single column of navigation property, you can simply override ToString()
method of navigation property class and return suitable value. This way, when showing a property of that type in grid, you will see a friendly text. For example in partial class of Product
, you can write:
public override string ToString()
{
return this.Name;
}
Option 6 - Use a custom TypeDescriptor to enable data binding to sub properties
Usage: This approach would be useful when you don't need to edit values.
In this approach you can create a custom TypeDescriptor that enables you to perform data binding to second-level properties. Here is a good article by Linda Liu about this approach:
这篇关于在 DataGridView 中显示导航属性的属性(二级属性)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!