Search code examples
c#winformsdatagridviewbindingsourcebindinglist

How can I have two editable and updatable DataGridViews bounded to a list and sub-list?


I have a List<Input> named listInput that contains property of type a List<class> named friends:

List<Input> listInput=new List<Input>();
BindingList<Input> blInput;
BindingSource bsInput;

public class Input
{
    public string Name { get; set; }
    public List<Friend> friends{ get; set; }
}

public class Friend
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

I have two DataGridView controls, named dgInputand dgFriends.
dgInput is bound to a BindingList like this:

blInput = new BindingList<Input>(listInput);
bsInput = new BindingSource(blInput, null);
dgInput.DataSource = bsInput;

dgInput is editable and updatable at run-time after user changes any cell on dgInput.

The question is: how can I bind dgFriends to sublist friends, so that it will automatically update when the dgInput curretn Row changes?
It's also important that dgFriens can be updated when an User changes any cell on dgFriends (the properties of listInput must preserve the changes).


Solution

  • Here's a simple example:

    • The first DataGridView uses the BindingSource you defined. The DataSource of this BindingSource is set to the List<Input> collection. This DGV can only show the Name property from the List<Input>, since you cannot present Rows containing both single values and collections of values.
      The DataMember of the BindingSource must be empty (or null): if you set Name as DataMember, you'll get an array of Char as a result instead of a string.

    • A second BindingSource is created: its DataSource is set to the existing BindingSource. In this case, the DataMember is set explicitly to the Friends property, which represents a List<class> objects (a single object that represents a collection that will provide the data to the Rows of the second DGV).

    This generates an active binding (a relationship) between the two BindingSource objects: when the first BindingSource.Current object changes, the second BindingSource will follow, showing its Current object (the List<Friend> linked to the current Name property).

    All properties of the two classes are editable.

    Note:
    I've implemented the INotifyPropertyChanged interface in the Input class to notify changes of the Name property: it's not strictly required (or, it' not required at all) in this context, but you may want to have it there, you'll quite probably need it later.

    List<Input> listInput = new List<Input>();
    BindingSource bsInput = null;
    
    public SomeForm()
    {
        InitializeComponent();
    
        bsInput = new BindingSource(listInput, "");
        var bsFriends = new BindingSource(bsInput, "Friends");
        dataGridView1.DataSource = bsInput;
        dataGridView2.DataSource = bsFriends;
    }
    
    public class Input : INotifyPropertyChanged {
        public event PropertyChangedEventHandler PropertyChanged;
        private string m_Name = string.Empty;
    
        public string Name {
            get => m_Name;
            set { m_Name = value;
                NotifyPropertyChanged(nameof(this.Name));
            }
        }
        public List<Friend> Friends { get; set; } = new List<Friend>();
    
        private void NotifyPropertyChanged(string propertyName) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    
    public class Friend {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    

    This is how it works:

    DataGridView Double BindingSource

    Sample object initialization, for testing:

    listInput.AddRange(new[] {
        new Input() {
            Name = "Name One", Friends = new List<Friend>() {
                new Friend () { FirstName = "First", LastName = "Friend of One"},
                new Friend () { FirstName = "Second", LastName = "Friend of One"},
                new Friend () { FirstName = "Third", LastName = "Friend of One"},
            }
        },
        new Input() {
            Name = "Name Two", Friends = new List<Friend>() {
                new Friend () { FirstName = "First", LastName = "Friend of Two"},
                new Friend () { FirstName = "Second", LastName = "Friend of Two"},
                new Friend () { FirstName = "Third", LastName = "Friend of Two"},
            }
        },
        new Input() {
            Name = "Name Three", Friends = new List<Friend>() {
                new Friend () { FirstName = "First", LastName = "Friend of Three"},
                new Friend () { FirstName = "Second", LastName = "Friend of Three"},
                new Friend () { FirstName = "Third", LastName = "Friend of Three"},
            }
        }
    });