在DataGridView中显示导航属性的属性(第二级属性)

时间:2016-01-29 15:31:56

标签: c# .net winforms entity-framework datagridview

我试图在应用中的DataGridView上显示来自相关实体的多个媒体资源。这对我来说似乎很平常,但我找不到例子。它是一个订单输入操作。 OrderSheet数据,订单的ID和取件日期,然后是网格中的行项目(下面的模型中的OrderSheetItems)。订单lineitems具有基于ProductId的导航属性Product。我可以将DataGridViewComboBoxColumn与ProductId一起用作ValueMember,将另一个字段用作DisplayMember。但我想在其他列,大小,颜色,材料等中包含更多数据。

OrderSheet and OrderSheetItems with related Product

Order entry

这里是加载数据的代码

try
{
    _context.OrderSheets.Include(o => o.OrderSheetItems.Select(i => i.Product)).Load();
    orderSheetBindingSource.DataSource = _context.OrderSheets.Local.ToBindingList();
}
catch (Exception ex)...

ProductId位于一个单独的专栏中,仅用于实验,稍后将是组合框。 那么有没有办法将其他列绑定到OrderSheetItem的Product导航属性中的数据,或者我是否必须在产品ID上处理CellValueChanged以在其他列中物理设置数据?如果有一种绑定列的方法,那么是通过OnLoad中的代码还是网格视图列设计器中的某个位置?

TIA,迈克

2 个答案:

答案 0 :(得分:7)

您可以使用以下任一选项:

  1. 使用DataGridViewComboBoxColumn
  2. 将相应的属性添加到子实体分部类
  3. 使用Linq
  4. 对查询进行整形以包含导航属性的属性
  5. 使用CellFormatting事件获取子属性有界列的值
  6. 通过覆盖ToString()
  7. 显示对象的字符串表示形式
  8. 使用自定义TypeDescriptor启用与子属性的数据绑定。
  9. 选项1 - 使用DataGridViewComboBoxColumn

    用法:此方法在您希望保持控件可编辑的情况下特别有用。

    在这种方法中,您可以使用DataGridViewComboBoxColumn来显示navigationn属性的任何字段。要在网格中显示导航属性的多个字段子属性,请使用绑定到具有不同DataGridViewComboBoxColumn的相同导航属性的多个DisplayMember

    在此方法中,除ProductId列之外,向网格添加更多DataGridViewComboBoxColumn,然后为所有其他组合列执行以下设置:

    • DataPropertyName设置为ProductId
    • 将它们的DataSource属性设置为您用于主ProductId列的完全相同的数据源,例如productBindingSource
    • ValueMember设置为您为产品ID列设置的相同值成员,它是产品表的关键列。(ProductId
    • 将每个DisplayMember设置为您要显示的列,例如,将其中一个设置为“名称”。一个是Price,一个是Size,....这样您就可以显示相关的实体字段。
    • ReadOnly属性设置为true。它使单元格只读。
    • 如果您希望将列只读为DisplayStyle,请将其Nothing属性设置为ProductId。它删除了下拉样式。

    如果您想保持DisplayStyle可编辑,请将其DropDownButton保持为ProductId。这样,当您使用组合框更改public string ProductName { get { if (this.Product != null) return this.Product.Name; else return string.Empty; } } 列的值时,当您离开行并移动到下一行时,您将看到行的其他单元格,显示所选产品的其他属性。此外,由于其他组合框列是只读的并且没有组合框样式,因此用户无法更改它们的值,它们仅作为显示来自相关实体的其他属性的只读文本框列。

    选项2 - 将相应的属性添加到子实体分部类

    用法:当您不需要编辑值时,此方法非常有用。

    在这种方法中,您可以在父实体的相应属性的子实体部分类返回值中定义属性。例如,对于产品名称,请在订单商品分部类中定义此属性:

    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();       
    

    然后,您可以在选择订单商品时简单地包含产品,并将网格列绑定到订单商品的相应属性。

    选项3 - 将查询整形为包含导航属性的属性

    用法:当您不需要编辑值时,此方法非常有用。

    您可以将查询整形为包含导航属性的属性。您可以简单地使用匿名对象或查看模式,例如:

    CellFormatting

    选项4 - 使用CellFormatting事件获取子属性有界列的值

    用法:当您不需要编辑值时,此方法非常有用。

    在此方法中,您可以使用DataGridView e.Value事件。您只需根据列索引设置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); } } 即可。例如:

    DataPropertyName

    您可以使用不同的标准来处理不同的列并显示不同的子属性。

    此外,您可以使用反射使其更具动态性和可重用性。您可以使用反射提取导航属性的子属性的值。为此,您应创建列并将Product.Name设置为CellFormatting等子属性,然后在ToString()事件中,使用反射,获取列的值。以下是Antonio Bello关于这种方法的好文章:

    选项5 - 通过覆盖ToString()

    显示对象的字符串表示形式

    用法:当您不需要编辑值时,此方法非常有用。

    如果只想显示一列导航属性,则可以简单地覆盖导航属性类的Product方法并返回合适的值。这样,当在网格中显示该类型的属性时,您将看到一个友好的文本。例如,在public override string ToString() { return this.Name; } 的部分类中,您可以写:

    {{1}}

    选项6 - 使用自定义TypeDescriptor启用数据绑定到子属性

    用法:当您不需要编辑值时,此方法非常有用。

    在此方法中,您可以创建自定义TypeDescriptor,使您可以执行数据绑定到第二级属性。这是Linda Liu关于这种方法的好文章:

答案 1 :(得分:0)

使用 CellFormatting 和 CellParsing 在 DataGridView 中显示和编辑嵌套属性

特点:

  • 支持任何级别的嵌套。
  • 支持可编辑和只读

工作原理:

  • 每列都有“.”在 DataPropertyName 中将被视为嵌套属性。
  • CellFormatting 事件将被处理以使用递归函数获取嵌套属性的值。
  • CellParsing 事件将被处理以使用递归函数设置嵌套属性的值。

方法如下:

public object GetPropertyValue(object source, string name)
{
    if (name.Contains("."))
    {
        var nameParts = name.Split(new[] { '.' }, 2);
        return GetPropertyValue(GetPropertyValue(source, nameParts[0]), nameParts[1]);
    }
    else
    {
        var property = TypeDescriptor.GetProperties(source)[name];
        return property.GetValue(source);
    }
}
public void SetPropertyValue(object source, string name, object value)
{
    if (name.Contains("."))
    {
        var nameParts = name.Split(new[] { '.' }, 2);
        SetPropertyValue(GetPropertyValue(source, nameParts[0]), nameParts[1], value);
    }
    else
    {
        var property = TypeDescriptor.GetProperties(source)[name];
        property.SetValue(source, value);
    }
}

这里是事件处理程序:

private void CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
    if (e.ColumnIndex < 0 || e.RowIndex < 0) return;
    var dg = (DataGridView)sender;
    var propertyName = dg.Columns[e.ColumnIndex].DataPropertyName;
    if (propertyName.Contains("."))
    {
        var dataObject = dg.Rows[e.RowIndex].DataBoundItem;
        e.Value = GetPropertyValue(dataObject, propertyName);
    }
}

private void CellParsing(object sender, DataGridViewCellParsingEventArgs e)
{
    var dg = (DataGridView)sender;
    var propertyName = dg.Columns[e.ColumnIndex].DataPropertyName;
    if (propertyName.Contains("."))
    {
        var dataObject = dg.Rows[e.RowIndex].DataBoundItem;
        SetPropertyValue(dataObject, propertyName, e.Value);
    }
}

这是一个例子:

var categories = new List<Category>() {
    new Category{ Id= 1, Name = "C1"},
    new Category{ Id= 2, Name = "C2"}
};
var products = new List<Product>() {
    new Product(){ Id = 1, Name ="P1", Category = categories[0]},
    new Product(){ Id = 2, Name ="P2", Category = categories[0]},
    new Product(){ Id = 3, Name ="P3", Category = categories[1]},
};
var dg = new DataGridView();
dg.AutoGenerateColumns = false;
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
    HeaderText = "Id",
    DataPropertyName = "Id"
});
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
    HeaderText = "Name",
    DataPropertyName = "Name"
});
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
    HeaderText = "CategoryId",
    DataPropertyName = "Category.Id"
});
dg.Columns.Add(new DataGridViewTextBoxColumn()
{
    HeaderText = "CategoryName",
    DataPropertyName = "Category.Name"
});
dg.Dock = DockStyle.Fill;
dg.DataSource = products;
this.Controls.Add(dg);
dg.CellFormatting += CellFormatting;
dg.CellParsing += CellParsing;

enter image description here