2

I am currently trying to implement a relatively simple data management app.

I have a class Member and a BindingList<Member> membersList, as well as a ListBoxand some TextBoxes.

The ListBox is bound to membersList.

Now I, ideally, want to bind the TextBoxes to ListBox.SelectedItem, so that whatever element the user has selected in the ListBox, when they edit a TextBox the element in membersListis updated.

I tried just binding the TextBoxes to ListBox.SelectedItem, but this made the Binding to the actual element that ListBox.SelectedItem is referencing at the moment of the binding creation, not whichever item is selected in the ListBox.

firstNameTextBox.DataBindings.Add(new Binding("Text",
membersList.SelectedItem, "firstName", false,
DataSourceUpdateMode.OnPropertyChanged));

I actually solved this already by just clearing and recreating the Bindings for the TextBoxes in the membersList_SelectedIndexChanged(object sender, EventArgs e) event handler, but this feels very "hacky" and I suspect there is a more standard solution.

Another idea I had was to just make the Bindings to a Member temporaryMember that is set to ListBox.SelectedItem inside the membersList_SelectedIndexChanged(object sender, EventArgs e) event handler, but then I have to manually write the changes through to the corresponding item in membersList which also makes me feel like this isn't the optimal solution.

Is there a way to make the Binding dynamic, in the sense that, upon creation, I indicate to it that the DataSource is changing?

Or a standard way the change the Bindings DataSource without deleting it and creating a new one? (Or is this actually best practice?)

(Another thing to mention: I am new to Bindings in C# and while searching for solutions, I found out that there apparently are two different classes, one in the System.Windows.Data namespace and another in the System.Windows.Forms namespace. I think I am using the class from the latter. Maybe I should use the other one?)

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • *I tried just binding the TextBoxes to ListBox.SelectedItem, but this made the Binding to the actual element that ListBox.SelectedItem is referencing at the moment of the binding creation, not whichever item is selected in the ListBox.* What does that mean? The item which is selected in `ListBox`, is `SelectedItem`. – Reza Aghaei Jul 27 '19 at 12:48
  • `ListBox` have built-in support for two-way databindings. Use a `BindungList` as data source which T implements `InotifyPropertyChanged`. Take a look at [this post](https://stackoverflow.com/a/33625054/3110834) for more information. – Reza Aghaei Jul 27 '19 at 13:01
  • @RezaAghaei Yes, but the DataSource of the Binding is not changed, when `ListBox.SelectedItem` changes. – Silver_Bear Jul 27 '19 at 13:02
  • Can you verify the Binding? membersList.SelectedItem, "firstName" seems to bind to property firstName within source membersList.SelectedItem, and it looks wrong... – Fab Jul 27 '19 at 13:05
  • @Fab What do you mean? Yes, the TextBox is supposed to be bound to a property of the `ListBox.SelectedItem`? – Silver_Bear Jul 27 '19 at 13:16
  • 1
    As Reza Aghaei said and also setting the `BindingList` as the DataSource of a `BindingSource`. The TextBoxes just need a `Binding` set to the `BindingSource`, When an item is selected if the ListBox, the TextBoxes will update automatically. Also, when a member of the `BindingList` is changed/modified. The ListBox instead won't update the current ListItem if an Item of the `BindingList` is changed, but you just need to call `[BindingSource].ResetCurrentItem();` if this happens. – Jimi Jul 27 '19 at 14:40
  • @Jimi Thank you, I didn't know about the possibility to use an extra `BindingSource`. This seems to have done the trick! (If you want to write an answer, I'll mark it as correct) – Silver_Bear Jul 27 '19 at 17:14
  • Having a `BindingList` you really don't need `BindingSource`, unless for design-time support and for using in `BindingNavigator`. Take a look at [this example](https://stackoverflow.com/a/53294445/3110834) to see how you can setup a list box or a combo box as index in the form and bind other controls to selected item of the listbox/combo box. – Reza Aghaei Jul 27 '19 at 17:47

1 Answers1

3

As described in the comments, associating a BindingList (or a DataTable) with a BindingSource can have some interesting benefits.

All bound controls are updated automatically when one of the elements of the BindingList is modified or a new element is added to the list.

You can use the MovePrevious(), MoveNext(), MoveFirst(), MoveLast() methods to navigate the elements in the BindingList (other useful methods and events are available, see the Docs about the BindingSource functionality).

Here, a BindingList<T> (where T is the Member class shown below) is set as the DataSource of a BindingSource. Both are Fields of a Form class, this can be modified as needed.
The BindingSource is then used as the DataSource of a ListBox.

The Text property of two TextBox controls is then bound, using the BindingSource, to one of the properties of the Member class. This way, the Text property is set to the current Item of the BindingList. All controls are synchronized:

txtMemberName.DataBindings.Add(new Binding("Text", membersSource, 
    "FirstName", false, DataSourceUpdateMode.OnPropertyChanged));
txtMemberLastName.DataBindings.Add(new Binding("Text", membersSource, 
    "LastName", false, DataSourceUpdateMode.OnPropertyChanged));

This is how it works, in practice:

BindingSource and BindingList

Note that the current Item of the ListBox is updated in real time when the Text of a TextBox is modified.

BindingList<Member> members = null;
BindingSource membersSource = null;

public partial class frmMembers : Form
{
    public frmMembers() {
        InitializeComponent();
        InitializeDataBinding();
    }

    private void InitializeDataBinding()
    {
        members = new BindingList<Member>();
        membersSource = new BindingSource(members, null);

        lstBoxMembers.DataSource = membersSource;
        txtMemberName.DataBindings.Add(new Binding("Text", membersSource, 
            "FirstName", false, DataSourceUpdateMode.OnPropertyChanged));
        txtMemberLastName.DataBindings.Add(new Binding("Text", membersSource, 
            "LastName", false, DataSourceUpdateMode.OnPropertyChanged));
    }

    private void btnAddMember_Click(object sender, EventArgs e)
    {
        var frmNew = new frmNewMember();
        if (frmNew.ShowDialog() == DialogResult.OK && frmNew.newMember != null) {
            members.Add(frmNew.newMember);
        }
    }

    private void btnMovePrevious_Click(object sender, EventArgs e)
    {
        if (membersSource.Position > 0) {
            membersSource.MovePrevious();
        }
        else {
            membersSource.MoveLast();
        }
    }

    private void btnMoveNext_Click(object sender, EventArgs e)
    {
        if (membersSource.Position == membersSource.List.Count - 1) {
            membersSource.MoveFirst();
        }
        else {
            membersSource.MoveNext();
        }
    }
}

Sample New Member Form:

public partial class frmNewMember : Form
{
    public Member newMember;

    private void btnSave_Click(object sender, EventArgs e)
    {
        if (string.IsNullOrEmpty(txtMemberName.Text) || 
            string.IsNullOrEmpty(txtMemberLastName.Text)) return;
        newMember = new Member(txtMemberName.Text, txtMemberLastName.Text);
    }
}

Sample Member class:

[Serializable()]
public class Member
{
    public Member() { }
    public Member(string firstName, string lastName)
    {
        this.FirstName = firstName;
        this.LastName = lastName;
    }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public override string ToString() => $"{this.FirstName} {this.LastName}";
}
Jimi
  • 29,621
  • 8
  • 43
  • 61