0
votes

For my current problem I want to create a DataGridView and use a list of objects as the datasource.

The problem I'm having is that the objects itself contains two lists, which are supposed to fill combobox columns. The DataGridView should contain three columns, each corresponding to my sample object below. The first column is a simple text column, while the other two are combo box columns.

Currently I'm receiving the error:

System.ArgumentException: The value DataGridViewComboBoxCell is invalid.

I've been looking for other solutions on SO, but can't seem to get it right.

    public class SampleObject
    {
        public SampleObject(string name, IList<TimeSpan> startTimes, IList<Activity> completedActivities)
        {
            this.Name = name;
            this.StartTimes = startTimes;
            this.CompletedActivities = completedActivities;
        }

        public string Name { get; }

        public IList<TimeSpan> StartTimes { get; }

        public IList<Activity> CompletedActivities { get; }
    }

Activity object:

    public class Activity
    {
        public Activity(string activityName)
        {
            ActivityName = activityName;
        }

        public string ActivityName { get; }

        public override string ToString()
        {
            return ActivityName;
        }
    }

And the code for adding the columns to my grid:

private void FillGrid()
    {
        sampleGrid.AutoGenerateColumns = false;

        var columnName = new DataGridViewTextBoxColumn
        {
            DataPropertyName = nameof(SampleObject.Name),
            HeaderText = "Name",
            Width = 160,
            ReadOnly = true
        };
        sampleGrid.Columns.Add(columnName);

        var columnStartTimes = new DataGridViewComboBoxColumn()
        {
            ValueType = typeof(TimeSpan),
            HeaderText = "StartTimes",
            Width = 120,
            ReadOnly = false
        };
        sampleGrid.Columns.Add(columnStartTimes);

        var columnCompletedActivities = new DataGridViewComboBoxColumn
        {
            ValueType = typeof(Activity),
            HeaderText = "Completed activities",
            Width = 120,
            ReadOnly = false

        };
        sampleGrid.Columns.Add(columnCompletedActivities);
    }

And populating the grid:

List<SampleObject> myObjects = GetSampleObjectsBasedOnValue(value);

sampleGrid.DataSource = myObjects;

FillComboBoxesInDGV(myObjects);

Method for filling the comboboxes:

        private void FillComboBoxesInDGV(IList<SampleObject> sampleObjects)
        {
            for (int i = 0; i < sampleObjects.Count; i++)
            {
                DataGridViewRow row = sampleGrid.Rows[i];

                var firstBox = row.Cells[1] as DataGridViewComboBoxCell;
                firstBox.DataSource = sampleObjects[i].StartTimes;

                var secondBox = row.Cells[2] as DataGridViewComboBoxCell;
                secondBox.DataSource = sampleObjects[i].CompletedActivities;
            }
        }
1
There appears to be some confusion as to “what” the combo boxes should contain. Example; you have a list of objects. When you “bind” the list of objects to the grid and set each column’s DataPropertyName to one of the properties of the object, this works for primitive types. However, if the property in the object is another object or “collection” (like another list) then the grid is NOT going to display this list in a combo box even if you set the DataPropertyName. This makes sense and there are ways to fix it to make it work. However, in your description… - JohnG
Each combo box MAY have different values. Therefore, on each row in the grid, each combo box “could” contain different values. Because of this, Making a “single” DataGridViewComboBoxColumn WONT WORK in this case. Unless you add ALL the :”start times” from ALL the rows into the combo box column, you are almost guaranteed to get the error you are getting which is saying that one of the “start times” in the data is NOT one of the items in the combo boxes list of items. - JohnG
Can you clarify if you want ALL the combo boxes to contain the same items OR each combo box may have different items as they will require two different approaches. - JohnG
@JohnG thanks for the reply. Yes every start times list in each row can be different. - Sintae
Since you are using the List<SampleObject> as a DataSource to the grid, then AFTER the grid is bound to the list, you will need to loop through the GRID rows, grab the appropriate SampleObject for that row and set each DataGridComboBoxCell’s DataSource to the appropriate “StartTimes” list or “CompleteActivites” list. You should also “remove” setting the columns DataPropertyName except for the “Name” column. Also, setting the “whole” grid to “ReadOnly” sampleGrid.ReadOnly = true; is going to make the combo boxes unchangeable. - JohnG

1 Answers

0
votes

I still not sure why you do not keep the original IList<string> CompletedActivities instead of introducing the Activity class? Using the list in this manner worked in my tests.

However, since you did use the class Activity, I would assumed it would work as expected. However, I ran into the same issue you described. After some hair pulling (and I don’t have much), I found some help from the SO post… DataGridViewComboBoxCell Binding - “value is not valid” … this was not obvious to me and I am guessing you also, thinking that overriding the Activity class’s ToString method would be enough. This is obviously not the case.

It appears that setting the DataGridViewComboBoxColumns’s ValueMember property to the name of the property in the Activity class should eliminate the error.

var columnCompletedActivities = new DataGridViewComboBoxColumn {
    HeaderText = "Completed activities",
    ValueMember = "ActivityName",
    Width = 120,
};
sampleGrid.Columns.Add(columnCompletedActivities);

I hope this works for you.