Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Show Properties of a Navigation Property in DataGridView (Second Level Properties)

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.

OrderSheet and OrderSheetItems with related Product

Order entry

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

like image 844
Mike Avatar asked Jan 29 '16 15:01

Mike


1 Answers

You can use either of these options:

  1. Use DataGridViewComboBoxColumn
  2. Add corresponding properties to child entity partial class
  3. Shape the query to include properties of navigation property using Linq
  4. Use CellFormatting event to get value for sub property bounded columns
  5. Show string representation of object by overriding ToString()
  6. 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 to ProductId
  • Set the DataSource property of them, to exactly the same data source you used for main ProductId column, for example productBindingSource
  • 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 to true. It makes the cell read only.
  • If you want to make columns readonly Set DisplayStyle property of them to Nothing. 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:

  • DataGridView: How to Bind Nested Objects

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:

  • How to bind a DataGridView column to a second-level property of a data source
like image 159
Reza Aghaei Avatar answered Sep 30 '22 05:09

Reza Aghaei