【问题标题】:Show Properties of a Navigation Property in DataGridView (Second Level Properties)在 DataGridView 中显示导航属性的属性(二级属性)
【发布时间】:2016-01-29 15:31:55
【问题描述】:

我正在尝试在 应用程序中的 DataGridView 上显示来自相关实体的多个属性。这对我来说似乎很普通,但我很难找到例子。这是一个订单输入操作。 OrderSheet 数据、订单的 ID 和取货日期,然后是网格中的行项目(以下模型中的 OrderSheetItems)。订单订单项具有基于 ProductId 的导航属性 Product。我可以使用 DataGridViewComboBoxColumn,ProductId 作为 ValueMember,另一个字段作为 DisplayMember。但我想在其他列中包含更多数据,尺寸、颜色、材质等。

这是加载数据的代码

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

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

蒂亚,迈克

【问题讨论】:

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


    【解决方案1】:

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

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

    选项 1 - 使用 DataGridViewComboBoxColumn

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

    在这种方法中,您可以使用DataGridViewComboBoxColumn 来显示navigationn 属性的任何字段。要在网格中显示导航属性的多个字段子属性,请使用多个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
    {
        get
        {
            if (this.Product != null)
                return this.Product.Name;
            else 
                return string.Empty;
        }
    }
    

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

    选项 3 - 调整查询以包含导航属性的属性

    用法:当您不需要编辑值时,这种方法会很有用。

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

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

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

    用法:当您不需要编辑值时,这种方法会很有用。

    在这种方法中,您可以使用DataGridViewCellFormatting 事件。您可以根据列索引简单地设置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 事件中,使用反射获取列的值。这是 Antonio Bello 关于这种方法的一篇好文章:

    选项 5 - 通过覆盖 ToString() 来显示对象的字符串表示形式

    用法:当您不需要编辑值时,这种方法会很有用。

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

    public override string ToString()
    {
        return this.Name;
    }
    

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

    用法:当您不需要编辑值时,这种方法会很有用。

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

    【讨论】:

    • 非常感谢您提供的所有选项。我已经完成了,我打算回来更新,但你打败了我。我选择了选项 2。我已经有一个用于 MetadataType 注释和其他一些东西的部分类,所以我只是放置了 getter 方法来深入了解 Product 的属性(除了计算的 Ext = Qty * Price)。最后我所要做的就是 orderSheetItem.Product = product;属性通过绑定源自动出现在其他列中。我仍然不会跳过关于 TypeDescriptor 的那篇文章。再次感谢。迈克
    • @JQSOFT 感谢您的反馈,我修复了损坏的链接以指向使用 web.archive.org 的帖子,当我有时间时,我可能会将主要代码片段添加到此帖子或其他帖子中我在stackoverflow中的相关帖子。
    【解决方案2】:

    使用 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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多