0

I have a DGV that is bound to a List.

Work is a class that contains a number of properties which includes a status property which is of type int. I would like to have that Status value in a DataGridViewComboBoxCell that maps to the appropriate display value.

So the mapping would look like

1 = Completed   
2 = In Progress   
3 = Errored   
4 = On Hold

I have tried to bind the DataGridViewComboBoxCell to a List which holds the int and string values of the statuses.

I have been unable to work out how to get the work.status to display Statuses.DisplayName in the DataGridViewComboBoxCell.

public class Work
{
    public int id {get; set;}
    public string name {get; set;}
    public int status {get; set;}
    public datetime created {get; set;}
    public datetime modified {get; set;}
}

public class Statuses
{
    public int id {get; set;}
    public string DisplayValue {get; set;}

}

If this can be done, please let me know..

Blair
  • 93
  • 1
  • 13
  • Without changing the current structure, you could add a new `DataGridViewComboBoxColumn`, set its DataSource to a `List` (DisplayMemeber = "DisplayValue", Valuemember = "id"), set its `DisplayIndex = 3` and hide the Column corresponding to the `status` property. In the `CellFormatting` event, set the `.Value` of the ComboBox column to the `.Value` of the `status` property, in the `CellEndEdit`, when `e.ColumnIndex == [Your ComboBox column]`, the opposite. Note that, without any further adjustment, the value of the `status` property will be updated only when the current cell changes. – Jimi May 02 '19 at 01:41
  • Well, if the description is not quite understandable and you need an example, let me know :) – Jimi May 02 '19 at 02:41
  • Just like Jimmy said, but the DataGridViewComboBoxColumn have a property called DataPropertyName, setting that to "status" should do the trick with less hassle. – M. Ruiz May 02 '19 at 04:56
  • @M.Ruiz I seriously had tried that but was getting error messages when i tried it. So i obviously had something wrong in the code. Tried it again and worked first pop. – Blair May 02 '19 at 19:54
  • @Jimi I was about start going down the road you suggested and then retried the suggestion in the other comment that worked. Thank you – Blair May 02 '19 at 19:59
  • I suggested that method for a reason. If you just bind the `DataPropertyName` of the ComboBox coulmn to a field and you change, in code, the `status` value, the ComboBox will not update. The same (almost) if you change the ComboBox value: the value of the `status` Field/Column will be updated only when you change the focus (to another Cell or another control). – Jimi May 02 '19 at 20:20

1 Answers1

0

One possible solution as suggested in the comments, is to “create” the DataGridViewComboBoxColumn the way you describe, then add it to the grid. Do this before you set the grids data source. Mapping this combo box column to the “WorkStatus” property of the Work list is done using the combo columns DataPropertyName. Setting the columns DataPropertyName to “WorkStatus” should do the trick.

One important problem you may run into is if AFTER setting up the combo box column with the 1 – Complete, 2 – In Progress… etc. The int values 1-4 are the values in the “Status” field of the list of Work items… HOPEFULLY! This may always be the case, however if the value of one of the Status fields is NOT 1, 2, 3 or 4… then the grid is going to throw a DataError when this value is mapped to the combo box column. In most cases, you won’t know this until the data is read and boom crash.

The point is that if you are going to set up a combo box column and then “map” a column from the data source to this column… you better be damn sure it doesn’t have any invalid “status” values. Otherwise a crash (data error) is guaranteed. It would be prudent to wire up the DataError anyway.

Given this, it appears necessary to at least “check” to make sure something like this doesn’t happen when the data is bound to the grid. One simple solution is to loop through all the data (before binding it to the grid) and “check the “status” values of each Work item. What is unknown is what to do if a value greater than 4 or less than 1 is found in the data. You can’t just let it go as explained above. This solution is to simply “add” the value to the combo box with an “unknown” string value. This will at least “guarantee” that you will avoid the data error from invalid values.

Putting this together may go something like this…

Below is the Work class as posted however I did change some variable names. This is the class that is bound to the grid.

public class Work {
    public int WorkID { get; set; }
    public string WorkName { get; set; }
    public int WorkStatus { get; set; }
    public DateTime DateCreated { get; set; }
    public DateTime DateModified { get; set; }
}

Next, is the Status class that is used to define the Status objects. This is used for the combo box column. A constructor is added along with the overridden Equals methods. This will facilitate “checking” to see if an existing “Status” value is already in the list using the list’s Contains property.

public class Status : IEquatable<Status> {

  public int StatusID { get; set; }
  public string StatusString { get; set; }

  public Status(int statusID, string statusString) {
    StatusID = statusID;
    StatusString = statusString;
  }

  public override bool Equals(object obj) {
    if (obj.GetType() != GetType()) return false;
    return Equals(obj as Status);
  }

  public bool Equals(Status that) {
    return that != null && this.StatusID == that.StatusID;
  }

  public override int GetHashCode() {
    var hashCode = -1280899892;
    hashCode = hashCode * -1521134295 + StatusID.GetHashCode();
    return hashCode;
  }
}

Setting up the ListOfStatus will require looping through the list of Work objects and “checking” each of the status values making sure the values are between 1 and 4 inclusive. A method that returns this list could come in handy and may look something like below… First the “default” Status values are created and added to the list (1, 2, 3, 4), then, a loop through the Work items to add any values that are not already in the status list.

private List<Status> GetStatusList(List<Work> workItems) {
  List<Status> listOfStatus = new List<Status>();
  // add the default values for the combo boxes
  Status curStatus = new Status(1, "Complete");
  listOfStatus.Add(curStatus);
  curStatus = new Status(2, "In Progress");
  listOfStatus.Add(curStatus);
  curStatus = new Status(3, "Errored");
  listOfStatus.Add(curStatus);
  curStatus = new Status(4, "On Hold");
  listOfStatus.Add(curStatus);
  // check to make sure the data (workItems) does NOT have any values in the Status field...
  //       that are NOT one of the values above... (specifically 1, 2, 3 or 4)
  //    if the value is NOT one of the values above... we will simply add it to the list
  Status unknownStatus;
  foreach (Work work in workItems) {
    if (work.WorkStatus < 1 || work.WorkStatus > 4) {
      unknownStatus = new Status(work.WorkStatus, "Unknown_" + work.WorkStatus);
      if (!listOfStatus.Contains(unknownStatus)) {
        listOfStatus.Add(unknownStatus);
      }
    }
  }
  return listOfStatus;
}

Now that we have a good list of Status objects, we can proceed to creating the combo box column and use this list as a data source for the combo column. Given a list of Work objects is all we need to create this column and a method that returns this DataGridViewComboBoxColumn may look something like below…. First, we get the status list from the above method, then set the columns properties making sure to set the columns DataPropertyName to match the Work objects WorkStatus property. If this does not match, the column will not map properly. The added “status” column will end up as the first column in the grid so you may want to change its ordinal value.

private DataGridViewComboBoxColumn GetComboColumn(List<Work> listOfWork) {
  List<Status> ListOfStatus = GetStatusList(listOfWork);
  DataGridViewComboBoxColumn comboCol = new DataGridViewComboBoxColumn();
  comboCol.Name = "Status";
  comboCol.DataPropertyName = "WorkStatus";
  comboCol.DisplayMember = "StatusString";
  comboCol.ValueMember = "StatusID";
  comboCol.DataSource = ListOfStatus;
  return comboCol;
}

An example of using the above methods is below.

private void Form1_Load(object sender, EventArgs e) {
  List<Work> ListOfWork = new List<Work>();
  FillListOfWork(ListOfWork);
  dataGridView1.Columns.Add(GetComboColumn(ListOfWork));
  dataGridView1.DataSource = ListOfWork;
}

private void FillListOfWork(List<Work> ListOfWork) {
  Work newWork;
  Random rand = new Random();
  for (int i = 0; i < 100; i++) {
    newWork = new Work();
    newWork.WorkID = i;
    newWork.WorkName = "Work Name " + i;
    newWork.WorkStatus = rand.Next(1, 5);
    newWork.DateCreated = DateTime.Now;
    newWork.DateModified = DateTime.Now;
    ListOfWork.Add(newWork);
  }
}

private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e) {
  MessageBox.Show("DataError: " + e.Exception.Message);
}

I hope this helps.

JohnG
  • 9,259
  • 2
  • 20
  • 29