4

i have a DataGridView and a list of objects that i would like to show.

The Objects are these:

public class Entity
{
    public int ID { get; set; }
}    

public class Travel: Entity
{
    public Service Service { get; set; }
    public City Source { get; set; }
    public City Destiny { get; set; }
    public decimal Price { get; set; }
}

public class Service: Entity
{
    public string Name { get; set; }
}

public class City: Entity
{
    public string Name { get; set; } // Max 50 chars
}

In my form i bind the list of of Travel Objects like this:

List<Travel> travels = logic.GetAllTravels();
DgvRecorridos.DataSource = travels;

And i get the following:

enter image description here

I would like to get the Name of the Service, Source City and Destiny City instead.

Thanks in advance.

John Woo
  • 258,903
  • 69
  • 498
  • 492
maxiruani
  • 169
  • 1
  • 2
  • 11

4 Answers4

6

Instead of doing the following codes below:

List<Travel> travels = logic.GetAllTravels();  
DgvRecorridos.DataSource = travels;  

Do this:

List<Travel> travels = logic.GetAllTravels();  
BindingSource bs = new BindingSource();  
bs.DataSource = travels;  
DgvRecorridos.AutoGenerateColumn = false;  
DgvRecorridos.DataSource = bs;  

Then, add the columns manually:

DataGridViewColumn col1 = new DataGridViewTextBoxColumn();  
col1.DataPropertyName = "Service.Name";  
col1.HeaderText = "Service Name";  
dataGridView1.Columns.Add(col1);  

DataGridViewColumn col2 = new DataGridViewTextBoxColumn();  
col2.DataPropertyName = "City.Name";  
col2.HeaderText = "City Name";  
dataGridView1.Columns.Add(col2);  

DataGridViewColumn col3 = new DataGridViewTextBoxColumn();  
col3.DataPropertyName = "City.Name";  
col3.HeaderText = "Destiny Name";  
dataGridView1.Columns.Add(col3);  

DataGridViewColumn col4 = new DataGridViewTextBoxColumn();  
col4.DataPropertyName = "Price";  
col4.HeaderText = "Price";  
dataGridView1.Columns.Add(col4);  

Then, add a cell formatting event handler for the DataGridView:

private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)  
{  
    if (dataGridView1.Rows[e.RowIndex].DataBoundItem != null &&   
        dataGridView1.Columns[e.ColumnIndex].DataPropertyName.Contains("."))  
    {  
        e.Value = BindProperty(dataGridView1.Rows[e.RowIndex].DataBoundItem,
            dataGridView1.Columns[e.ColumnIndex].DataPropertyName);  
    }  
}  

private string BindProperty(object property, string propertyName)  
{  
    string retValue = "";  

    if (propertyName.Contains("."))  
    {  
        PropertyInfo[] arrayProperties;  
        string leftPropertyName;  

        leftPropertyName = propertyName.Substring(0, propertyName.IndexOf("."));  
        arrayProperties = property.GetType().GetProperties();  

        foreach (PropertyInfo propertyInfo in arrayProperties)  
        {  
            if (propertyInfo.Name == leftPropertyName)  
            {  
                retValue = BindProperty(propertyInfo.GetValue(property, null),   
                propertyName.Substring(propertyName.IndexOf(".") + 1));  
                break;  
            }  
        }  
    }  
    else  
    {  
        Type propertyType;  
        PropertyInfo propertyInfo;  

        propertyType = property.GetType();  
        propertyInfo = propertyType.GetProperty(propertyName);  
        retValue = propertyInfo.GetValue(property, null).ToString();  
    }  

    return retValue;  
}  

For a complete guide of the cell formatting, browse here on Antonio Bello's blog, it's where I got the idea. ^_^ I also asked here on SO the same question two days ago, and got the same answers like you, and I know that it's not what you want to do too. Hope it helps you.

devpro101
  • 338
  • 1
  • 3
  • 13
4
List<Travel> travels = logic.GetAllTravels();
var _bind = from a in travels
            select new
            {
                Servicename = a.Service.Name,
                SourceName = a.Source.Name,
                DestinyName = a.Destiny.Name,
                Price = a.Price
            };
DgvRecorridos.DataSource = _bind;

or

List<Travel> travels = logic.GetAllTravels();
var _bind = travels.Select(a => new 
            { 
                Servicename = a.Service.Name,
                SourceName = a.Source.Name,
                DestinyName = a.Destiny.Name,
                Price = a.Price
            });
DgvRecorridos.DataSource = _bind;
John Woo
  • 258,903
  • 69
  • 498
  • 492
  • Thanks for the answer. How can I change the header text? – ehh Jun 29 '16 at 10:06
  • @ehh are you referring to the header of the datagrid? If yes you can change it in the properties of the datagridview. – John Woo Jun 29 '16 at 10:20
  • Yes thank you. I was wondering if it can be done as part of the anonymous method. Instead of having Servicename to have "Service Name" (or different syntax) – ehh Jun 29 '16 at 10:26
  • Thanks, just what i was looking for. I had to put .ToList(); at the end of the Linq query for it to display on the datagridview. – Mana Nov 21 '18 at 09:00
0

Your design is so strange. I have another approach is to override ToString() method of your classes (Service and City) like this:

public class Service: Entity
{
  public string Name { get; set; }
  public override string ToString(){
     return Name;
  }
}

public class City: Entity
{
  public string Name { get; set; } // Max 50 chars
  public override string ToString(){
     return Name;
  }
}

And that works OK. Again, your design is a little strange ^_^

King King
  • 61,710
  • 16
  • 105
  • 130
0

All that code in the CellFormatting and BindProperty methods seems excessive. I mean, something's got to basically do that, but I think it's already been done. I implement INotifyPropertyChanged in the object I want to bind to a grid row, and I put those objects into a BindingList. The BindingList is directly used as the datasource for the grid.

This approach means a little more typing in the basic entity class you're mapping to the grid row but I think it saves a lot more elsewhere. To implement INotifyPropertyChanged in your class:

public class Entity: INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public Entity()
        {
            Selected = false;
        }

        private bool _selected;
        public bool Selected
        {
            get
            {
                return _selected;
            }
            set
            {
                if (_selected != value)
                {
                    _selected = value;
                    OnPropertyChanged(nameof(Selected));
                }
            }
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

Then put a column in your grid and give it a DataPropertyName of "Selected" to match the name of the property in Entity that should appear in that column. Obviously add whatever properties you want to your grid, matching the properties of the entity. The key is to be sure to implement the property setter with the call to PropertyChanged.

This gets you two-way binding between the grid and your list of objects.

My personal opinion: even this is way too much code. I find myself constantly writing these kinds of things that do the obvious: take a property by name and map it to something else that knows that name (like the grid column in this example). It's just beyond my comprehension why these things don't just automatically hook up. A list and grid should have enough sense to figure this basic arrangement on their own. zero lines of code. Ok, one line of code. Grid.Datasource = List. So this is how I do it. I'd love to know a less lines of code way to do this.