Miguel Sanchez
Miguel Sanchez

Reputation: 434

DataGridView not displaying properties containing Lists

I have an object like so

public class Person{
        public int ID { get; set; }                 
        public string Name{ get; set; }
        public string Surname{ get; set; }
        public List<int> AltIDs { get; set; }
        public List<string> AltNames{ get; set; }   
}

I am setting data into the DataGridView like

    dataGridView1.DataSource = null;
    dataGridView1.DataSource = persons; // persons is List<Person>

However I only see 3 columns in the DataGridView that is of ID, Name and Surname, the properties with List<int> and List<string> seem to be ignored. Is there a way to get theses properties showing up in the DataGridView? Probably like comma seperated values.

Upvotes: 1

Views: 940

Answers (3)

JohnG
JohnG

Reputation: 9469

From what I can tell, this appears as a fairly basic “Master/Slave” type UI. In the grid, there is a “Person” object with the ID, Name and Surname. Obviously from your question, the AltIDs and AltNames Lists are NOT displayed in the grid.

This is due to the fact that the grid has a problem trying to add a “list of multiple” values into a “single” cell. As suggested, it is possible for you to “combine” the values into a “single” string and use that. However, this is extra work on your part and may create more work if the cell is edited.

One issue in this example is that, the AltIDs list for each Person could and will have a different number of elements. One possible solution is to simply ADD theses as new rows where the Person info (ID, Name and Surname) are duplicated. This will work but IMHO not very user friendly. The combo box option will also work, however again it may be confusing to users since a combo box “usually” indicates that the users selects a “single” value from many.

It is also possible to “flatten” each person object and have a column for each AltID in the list. This will work but the possibility of large gaps in the grid are likely. Again, not very user friendly. This is all doubled by the fact that there is another list AltNames that we have to take into account.

Given this, it appears clear that YOU are going to have to do extra work UNLESS you use an advanced third-party grid OR add more grids to the picture… in this case three (3) total. One for the person, another for the IDs and a third for the Names. Proper arrangement of the grids may be a little work, however, with three grids, it will make all the issues described above… go away. In addition the coding will be much easier.

It would be such that the user “selects” a Person from the first grid, then the second grid list all the AltID values and the third grid lists all the AltName values. If the “same” data source is used for each grid then… when the user selects a different Person in the person grid, the AltID grid and AltName grid will “automatically” update/refresh with the proper values. This will also make CRUD operations on any grid/value much easier.

The only problem I seen in the current Person class is that the two Lists are of “primitive” types. Used this way, the lists won’t display properly, the grid wants a CLASS. Therefore, you need to make a wrapper class for the AltID and AltName lists. Then change the list values in the Person class. Something like…

public class AltID_C {
  public int AltID { get; set; }
}

public class AltName_C {
  public string AltName { get; set; }
}

public class Person {
  public int ID { get; set; }
  public string Name { get; set; }
  public string Surname { get; set; }
  public List<AltID_C> AltIDs { get; set; }
  public List<AltName_C> AltNames { get; set; }
}

With this, all that is needed is to set each grid to the “same” data source, then set the AltID grids DataMember to the “AltIDs” property and set the AltNames grids DataMember to the “AltNames` property. Something like…

List<Person> AllPersons;

public Form1() {
  InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e) {
  AllPersons = GetRandomData(25);
  dgvPerson.DataSource = AllPersons;
  dgvAltID.DataSource = AllPersons;
  dgvAltID.DataMember = "AltIDs";
  dgvAltNames.DataSource = AllPersons;
  dgvAltNames.DataMember = "AltNames";
} 

Additional code below to complete the example.

private List<Person> GetRandomData(int numberOfPersons) {
  List<Person> listOPeople = new List<Person>();
  Person curPerson;
  Random rand = new Random();
  for (int i = 0; i < numberOfPersons - 1; i++) {
    curPerson = new Person() {
      ID = rand.Next(1, 1000),
      Name = "Name_" + i + 1,
      Surname = "Sname_" + i + 1,
      AltIDs = GetRandomNumberOfInts(rand),
      AltNames = GetRandomNumberOfStrings(rand)
    };
    listOPeople.Add(curPerson);
  }
  return listOPeople;
}

private List<AltID_C> GetRandomNumberOfInts(Random rand) {
  List<AltID_C> listOInts = new List<AltID_C>();
  int numberOfInts = rand.Next(0, 10);
  for (int i = 0; i < numberOfInts - 1; i++) {
    listOInts.Add(new AltID_C { AltID = rand.Next(1, 10000) });
  }
  return listOInts;
}

private List<AltName_C> GetRandomNumberOfStrings(Random rand) {
  List<AltName_C> listOStrings = new List<AltName_C>();
  int numberOfStrings = rand.Next(0, 10);
  for (int i = 0; i < numberOfStrings - 1; i++) {
    listOStrings.Add(new AltName_C { AltName = "RandString: " + rand.Next(1, 1000) });
  }
  return listOStrings;
}

Hope that helps.

Upvotes: 1

Chetan
Chetan

Reputation: 6891

When DataGridView is bound to DataSource, it auto generates columns for it for the properties with primitive datatypes such as int, string, double etc.

For the datatypes which are collections, DataGridViewComboBoxColumn is used. This type of column is not autogenerated. You need to add such columns manually in the GridView columns collection.

For your use case following is the solution.

Add DataGridView to the form and add columns to it manually from the Form's designer view by click on Add Column. You will have to add 3 DataGridViewTextBoxColumn and 2 DataGridViewComboBoxColumn.

enter image description here

After adding columns, the columns would look as following.

enter image description here

Now while assigning DataSource to the DataGridView you need to write following code. Here IdColumn, NameColumn, and SurnameColumn are the names give to columns when they were created in above steps.

dataGridView1.AutoGenerateColumns = false;
IdColumn.DataPropertyName = "ID";
NameColumn.DataPropertyName = "Name";
SurnameColumn.DataPropertyName = "Surname";
dataGridView1.DataSource = persons;

With the above code you will see Id, Name and Surname columns populated for persons in the collection but the dropdown lists in last two columns are empty.

To populate the dropdown list columns you need to add event handler for CellClick event of the DataGridView. And write following code there. Here AltIdsColumn and AltNamesColumn are the names given to column when they created manually in earlier steps.

private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
    var altIdIndex = dataGridView1.Columns["AltIdsColumn"].Index;
    var altNameIndex = dataGridView1.Columns["AltNamesColumn"].Index;
    if (altIdIndex == e.ColumnIndex || altNameIndex == e.ColumnIndex)
    {
        var altIdsCell = (DataGridViewComboBoxCell)dataGridView1.Rows[e.RowIndex].Cells[altIdIndex];
        var altNamesCell = (DataGridViewComboBoxCell)dataGridView1.Rows[e.RowIndex].Cells[altNameIndex];

        if (altIdsCell.DataSource == null || altNamesCell.DataSource == null)
        {
            var person = dataGridView1.Rows[e.RowIndex].DataBoundItem as Person;
            if (person != null)
            {
                altIdsCell.DataSource = person.AltIDs;
                altNamesCell.DataSource = person.AltNames;
            }
        }
    }
}

With this code, the dropdown list of AldIDs and AltNames columns will be populated when you click on those dropdown lists on individual rows.

enter image description here

I hope this will help you resolve your issue.

Upvotes: 1

mehdi farhadi
mehdi farhadi

Reputation: 1532

most probably the persons is null you just change your code like this code

first Solution :

your model :

public class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public List<int> AltIDs { get; set; }
    public List<string> AltNames { get; set; }
}

PersonViewModel :

    public class PersonViewModel
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public int AltID { get; set; }
    public string AltName { get; set; }
}

formLoad :

private void Form1_Load(object sender, EventArgs e)
    {
        List<Person> people = new List<Person>() 
        {new Person(){ ID=1,Name="a",Surname="a",AltIDs=new List<int>(){1,2,3,4 },AltNames=new List<string>(){"a","b","c" } },
        new Person(){ ID=2,Name="b",Surname="b",AltIDs=new List<int>(){10,20,30,40 },AltNames=new List<string>(){"a","b","c" }},
        new Person(){ ID=3,Name="c",Surname="c",AltIDs=new List<int>(){100,200,300,400 },AltNames=new List<string>(){"a","b","c" }},
        new Person(){ ID=4,Name="d",Surname="d",AltIDs=new List<int>(){1000,2000,3000,4000 },AltNames=new List<string>(){"a","b","c" }},
        new Person(){ ID=3,Name="e",Surname="e",AltIDs=new List<int>(){10000,20000,30000,40000 },AltNames=new List<string>(){"a","b","c" }}
        };
        List<PersonViewModel> pwm = new List<PersonViewModel>();
        foreach (var person in people)
        {
           foreach(var id in person.AltIDs)
            {
                foreach (var name in person.AltNames)
                    pwm.Add(new PersonViewModel() { ID = person.ID, Name = person.Name, Surname = person.Surname, AltID = id, AltName = name });
            }
        }
        dgv.DataSource = pwm;

    }

Result : enter image description here

Second Solution :

your model :

public class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public List<int> AltIDs { get; set; }
    public List<string> AltNames { get; set; }
}

PersonViewModel :

    public class PersonViewModel
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public string AltID { get; set; }
    public string AltName { get; set; }
}

FormLoad :

 private void Form1_Load(object sender, EventArgs e)
    {
        List<Person> people = new List<Person>() 
        {new Person(){ ID=1,Name="a",Surname="a",AltIDs=new List<int>(){1,2,3,4 },AltNames=new List<string>(){"a","b","c" } },
        new Person(){ ID=2,Name="b",Surname="b",AltIDs=new List<int>(){10,20,30,40 },AltNames=new List<string>(){"a","b","c" }},
        new Person(){ ID=3,Name="c",Surname="c",AltIDs=new List<int>(){100,200,300,400 },AltNames=new List<string>(){"a","b","c" }},
        new Person(){ ID=4,Name="d",Surname="d",AltIDs=new List<int>(){1000,2000,3000,4000 },AltNames=new List<string>(){"a","b","c" }},
        new Person(){ ID=3,Name="e",Surname="e",AltIDs=new List<int>(){10000,20000,30000,40000 },AltNames=new List<string>(){"a","b","c" }}
        };
        List<PersonViewModel> pwm = new List<PersonViewModel>();
        StringBuilder sbIds;
        StringBuilder sbNames;
        foreach (var person in people)
        {
            sbIds = new StringBuilder();
            sbNames = new StringBuilder();
            person.AltIDs.ForEach(c=> sbIds.Append(c.ToString()).Append(","));
            person.AltNames.ForEach(c=> sbNames.Append(c).Append(","));
            pwm.Add(new PersonViewModel() { ID = person.ID, Name = person.Name, Surname = person.Surname, AltID = sbIds.ToString().TrimEnd(','), AltName = sbNames.ToString().TrimEnd(',') });                
        }
        dgv.DataSource = pwm;            
    }

Result : enter image description here

Upvotes: 3

Related Questions