I have this code that I woud like to populate to a datagridview in windows form c#
public class Person
{
public string Nom { get; set; }
public string Prenom { get; set; }
}
public class Phone
{
public int PhoneId { get; set; }
public string PhoneNumber { get; set; }
}
public class Email
{
public int EmailId { get; set; }
public string EmailAddress { get; set; }
}
public class Contacts
{
public int ContactId { get; set; }
public Person Contact { get; set; }
public List<Phone> Telephone { get; set; }
public List<Email> Emails { get; set; }
}
I have this code that works fine but I cannot get it to work to show the emails as well
var result = c.SelectMany(x => x.Telephone, (x, y) =>
new { x.ContactId, x.Contact.Nom, x.Contact.Prenom, y.PhoneNumber }).ToList();
dataGridView1.DataSource = result;
The issue with using a List<myClass>
as a DataSource
to the grid is that the grid will not display properties of myClass
that are collections. Like the Telephone
and Emails
lists. It will display a Class, however, it will default and use the classes ToString
method and display that into a "single" column.
In your example, using the Contacts
Class as a List<Contacts>
as a DataSource
to the grid, I am sure you are aware that neither the Person
object is displayed properly since it is a Class nor is the Phone and Email objects displayed because they are collections of objects.
In this example, the Person
object may display as a column in the grid, however, it will display something like… “SolutionName.Person.” If Person
had an overridden ToString
method then it would display what that method returned. However, even with an overridden ToString
method, it will NOT “split” the Person
object into two columns… one for Nom
and the other Prenom
… your code would need to do this.
In the case of the phone numbers and emails… no column will be generated for those properties because they are “collection” properties and the grid does not know how to put “multiple” items into a single cell. This is why they are ignored.
In addition, as I commented, it seems like a poor UI idea to “repeat” the Nom
and Prenom
data for x number of rows. IMO, one row for each Person
and if that person has multiple phone numbers and emails, then add ALL the phone numbers and emails such that they will be in a SINGLE cell. This will remove the redundant data duplication.
So, given this… how can we “flatten” the data so we can get both Person
properties to display in two different columns and also have ALL the phone numbers in a single cell and ALL the email addresses in a single cell?
One possible solution follows.
First, I suggest you simply “add” these properties to the Contacts
class. In the Contact
class we will add the properties to the class that simply return the Person
s Nom
and Prenom
properties.
This will solve the issue of displaying the Nom
and Prenom
properties as two different columns in the grid. However, we still have the “collection” problem with the PhoneNumbers
and the EmailAddresses.
In this case, one solution is to add a string
property to the Contacts
class and have it return a single string with ALL the phone numbers and another string
property that returns all the Emails
as a single string.
Once this is done, then the grid WILL display those properties since it is single string and not a “collection” of strings.
So, some changes are needed in the Contacts
class. Below is an example of what is described above. Note, I CHANGED the class name to Contact
instead of Contacts
and I CHANGED the Person
property name to ThePerson
instead of Contact.
So, you need to adjust your code accordingly.
This updated Contact
class may look something like…
public class Contact {
public int ContactId { get; set; }
public Person ThePerson { get; set; }
public List<Phone> Telephone { get; set; }
public List<Email> Emails { get; set; }
public string PhoneNumbers {
get {
if (Telephone == null) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < Telephone.Count; i++) {
sb.Append(Telephone[i].PhoneNumber.ToString());
if (i < Telephone.Count - 1) {
sb.AppendLine();
}
}
return sb.ToString();
}
}
public string EmailAddresses {
get {
if (Emails == null) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < Emails.Count; i++) {
sb.Append(Emails[i].EmailAddress.ToString());
if (i < Emails.Count - 1) {
sb.AppendLine();
}
}
return sb.ToString();
}
}
public string Nom {
get {
if (ThePerson == null) {
return "";
}
return ThePerson.Nom;
}
}
public string Prenom {
get {
if (ThePerson == null) {
return "";
}
return ThePerson.Prenom;
}
}
}
As described previously, we added two properties called Nom
and Prenom
and they simply return those properties from the ThePerson
object. This will make a column for both properties.
Next, we have the added PhoneNumbers
and EmailAddresses
properties. In this case, we need to loop through all the phone numbers and add each phone number to a single string. In this case, each phone number will be on a single line, so we will need to adjust the grids row heights to accommodate however many lines there are. Otherwise, some phone numbers may be cut off in the cells display. This same idea is used for the Email addresses.
Next, we need to manually add the columns to the grid. Then tell the grid to not auto generate the columns since we obviously have already added them. The key property in each column will be its DataPropertyName
. This property tells the grid “which” property to display in the column given a DataSource
. In this case, we want to set the first columns DataPropertyName
to ContactID
, the second column to Nom
, third to Prenom
, fourth to PhoneNumbers
and finally the last to EmaillAddresses
. To help a method is created to return a column with the given properties… Something like…
private DataGridViewColumn GetDGVCol(string colName, string dataPropertyName, string headerText) {
DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
col.Name = colName;
col.DataPropertyName = dataPropertyName;
col.HeaderText = headerText;
return col;
}
To use the above helper in this example, I created an additional method that adds the columns to the grid as described.
private void AddDGV_Columns() {
dataGridView1.Columns.Add(GetDGVCol("ContactID", "ContactID", "ContactID"));
dataGridView1.Columns.Add(GetDGVCol("Nom", "Nom", "Nom"));
dataGridView1.Columns.Add(GetDGVCol("Prenom", "Prenom", "Prenom"));
dataGridView1.Columns.Add(GetDGVCol("PhoneNumbers", "PhoneNumbers", "PhoneNumbers"));
dataGridView1.Columns.Add(GetDGVCol("EmailAddresses", "EmailAddresses", "EmailAddresses"));
dataGridView1.DefaultCellStyle.WrapMode = DataGridViewTriState.True;
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
}
Above we mate all the columns to a property in the Contacts
class, then set the cells wrap mode to accommodate multiple lines in the PhoneNumers
and EmailAddresses
properties. In addition to auto sizing the column width to show all the data.
To make a complete example, the code below should demonstrate what is described above. NOTE, I have changed some class names and property names and you will need to adjust your code to fit mine or change my code to fit yours.
List<Contact> Contacts;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
Contacts = GetContacts();
AddDGV_Columns();
dataGridView1.AutoGenerateColumns = false;
dataGridView1.DataSource = Contacts;
}
private void AddDGV_Columns() {
dataGridView1.Columns.Add(GetDGVCol("ContactID", "ContactID", "ContactID"));
dataGridView1.Columns.Add(GetDGVCol("Nom", "Nom", "Nom"));
dataGridView1.Columns.Add(GetDGVCol("Prenom", "Prenom", "Prenom"));
dataGridView1.Columns.Add(GetDGVCol("PhoneNumbers", "PhoneNumbers", "PhoneNumbers"));
dataGridView1.Columns.Add(GetDGVCol("EmailAddresses", "EmailAddresses", "EmailAddresses"));
dataGridView1.DefaultCellStyle.WrapMode = DataGridViewTriState.True;
dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
}
private DataGridViewColumn GetDGVCol(string colName, string dataPropertyName, string headerText) {
DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
col.Name = colName;
col.DataPropertyName = dataPropertyName;
col.HeaderText = headerText;
return col;
}
private List<Contact> GetContacts() {
List<Contact> Contacts = new List<Contact>();
List<Phone> PhoneNumbers = new List<Phone>();
Phone pn = new Phone { PhoneId = 1, PhoneNumber = "111-11-11" };
PhoneNumbers.Add(pn);
pn = new Phone { PhoneId = 2, PhoneNumber = "222-22-22" };
PhoneNumbers.Add(pn);
pn = new Phone { PhoneId = 3, PhoneNumber = "222-22-22" };
PhoneNumbers.Add(pn);
Person = new Person { Nom = "Jeff", Prenom = "Prosper" };
List<Email> emails = new List<Email>();
Email em = new Email { EmailId = 1, EmailAddress = "email1@someISP.com" };
emails.Add(em);
em = new Email { EmailId = 2, EmailAddress = "email2@someISP.com" };
emails.Add(em);
Contact newC = new Contact { ContactId = 1, Telephone = PhoneNumbers, Emails = emails, ThePerson = person };
Contacts.Add(newC);
person = new Person { Nom = "Jimmy", Prenom = "Gean" };
PhoneNumbers = new List<Phone>();
pn = new Phone { PhoneId = 1, PhoneNumber = "333-33-33" };
PhoneNumbers.Add(pn);
emails = new List<Email>();
em = new Email { EmailId = 1, EmailAddress = "emailXX1@someISP.com" };
emails.Add(em);
em = new Email { EmailId = 2, EmailAddress = "emailXX2@someISP.com" };
emails.Add(em);
newC = new Contact { ContactId = 2, Telephone = PhoneNumbers, Emails = emails, ThePerson = person };
Contacts.Add(newC);
return Contacts;
}
Edit as per Enigmativity's comment.
As @Enigmativity notes a more succinct approach could be used like below. You would use these properties below as opposed to the above properties. Thanks, Enigmativity 😉
public string PhoneNumbers => Telephone == null ? "" : String.Join(Environment.NewLine, Telephone.Select(x => x.PhoneNumber));
public string EmailAddresses => Emails == null ? "" : String.Join(Environment.NewLine, Emails.Select(x => x.EmailAddress));
public string Nom => ThePerson == null ? "" : ThePerson.Nom;
public string Prenom => ThePerson == null ? "" : ThePerson.Prenom;
I hope this makes sense and helps.