How can I add a DisplayMember and ValueMember to a single ComboBox Item that has an assigned DataSource?

2.3k views Asked by At

I am successfully adding Display/Value pairs to a combobox like this:

List<Student> BRStudents =
        studentsList.Where(h => h.EnrolledInAYttFM)
            .Where(i =>     
    i.RecommendedNextTalkTypeID.Equals(BIBLE_READING_TALK_TYPE))
            .OrderBy(j => j.WeekOfLastAssignment)
            .ToList();
    comboBoxBR.DataSource = BRStudents;
    comboBoxBR.DisplayMember = "FullName";
    comboBoxBR.ValueMember = "StudentID";

...but I then (in some instances) want to add another item to comboBoxBR, one which is not present in the BRStudents list. If I try to do this:

AssignmentHistory ah = AYttFMConstsAndUtils.AssignmentHistList
    .FirstOrDefault(i => i.WeekOfAssignment == currentWeek && i.TalkType == 1);
string fullName = AYttFMConstsAndUtils.GetStudentFullNameForID(ah.StudentID_FK);
comboBoxBR.Items.Add(fullName);

...I get, "Items collection cannot be modified when the DataSource property is set."

Really, I want to do something like this:

comboBoxBR.Items.Add(fullName, ah.StudentID_FK);

Is there a way to combine the two results (the list of Student and the single AssignmentHistory) into a Dictionary or some such collection, and then assign that as the DataSource for comboBoxBR?

For full disclosure, here are the definitions of Student and AssignmentHistory:

public class Student
{
    public int StudentID { get; set; }
    public int FamilyID { get; set; }
    public bool EnrolledInAYttFM { get; set; }
    public DateTime DateEnrolledOrHiatusAYttFM { get; set; }
    public bool GivesBibleReading { get; set; }
    public bool PresentsICRVBS { get; set; }
    public bool IsHouseholder { get; set; }
    public bool IsMale { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string EmailAddr { get; set; }
    public DateTime WeekOfLastAssignment { get; set; }
    public int RecommendedNextTalkTypeID { get; set; }
    public int NextCounselPoint { get; set; }
    public string FullName => $"{FirstName} {LastName}";
}

public class AssignmentHistory
{
    public DateTime WeekOfAssignment { get; set; }
    public int TalkType { get; set; } 
    public int StudentID_FK { get; set; }
    public int AssistantID_FK { get; set; } 
    public int CounselPoint { get; set; }
    public bool HasBeenEmailed { get; set; }
    public bool SlipHasBeenPrinted { get; set; }        
}
1

There are 1 answers

0
Steve On BEST ANSWER

Instead of adding elements to the Items collection of the combo (not possible if you have set the DataSource) the first thing that comes to mind is adding elements to the List<Students> itself used as DataSource, but this doesn't reach your objective because there is no mechanism in place to inform the ComboBox that the DataSource has a new element. You can't see the added element unless you rebind the List to the combobox. To do this you first need to unbind the previous DataSource and then rebind the list again.

// Assuming a lot about your student class, hope it's clear the intention
BRStudents.Add(new Student() { newName = "ANewStudent", ID = 1} );
comboBoxBR.DataSource = null;
comboBoxBR.DataSource = BRStudents;
comboBoxBR.DisplayMember = "FullName";
comboBoxBR.ValueMember = "StudentID";

However, there is a better solution and it is the class BindingList(T)
This class is capable to refresh the object at which you bind an instance of it.

So when you bind the combo for the first time you have to write

BindingList<Student> bl = new BindingList<Student>(BRStudents);
comboBoxBR.DataSource = bl;
comboBoxBR.DisplayMember = "FullName";
comboBoxBR.ValueMember = "StudentID";

and when you want to add a new element to the list you write
(move bl at the global scope if needed)

bl.Add(new Student() { newName = "ANewStudent", ID = 1} );

The documentation states that you should call ResetBindings to force a refresh of the control but this doesn't seems always required...