-1

I'd like to have a basic ListBox with associated Add and Delete buttons. The Delete button be enabled if and only if there is a selection in the ListBox, and the Add button should append an item to the end of the ListBox.

My first attempt was to just make a List<string> items field, like so:

public partial class Form1 : Form {
    public Form1 {
        InitializeComponent();
        this.listBox1.DataSource = this.items;
    }

    private void addButton_Click(object sender, EventArgs e) {
        this.items.Add("item {this.items.Count + 1}");
    }

    private void deleteButton_Click(object sender, EventArgs e) {
        this.items.RemoveAt(this.listBox1.SelectedIndex);
    }

    private void listBox1_SelectedIndexChanged(object sender, EventArgs e) {
        this.deleteButton.enabled = (this.listBox1.SelectedIndex != -1);
    }

    List<string> items = new List<string>();
}

But, it turns out that the ListBox does not seem to update automatically as the items in the list change. Here's a related question about it.

Most advice suggested just running listBox1.DataSource = null; this.listBox1.DataSource = this.items; every time I updated the list, but that to me defeats the purpose of binding.

Another answer suggested using an ObservableCollection for my list's data type, but that did nothing different.

Finally I found the BindingList class and it seemed to do what I want; the ListBox automatically reflects the contents of the items field. (If there is another solution that is actually a best practice and not some piece of cargo cult folklore pasted around the Internet, please let me know.)


However, when the first item I add to items appears in the ListBox, the row becomes selected. And I can see that listBox1.SelectedIndex changes from -1 to 0. But, the listBox1_SelectedIndexChanged event does not fire. Hence, the state of the Delete button doesn't get updated properly.

(As an aside, this UI pattern is extremely commonplace, and it seems disastrous that there's so much conflicting information on implementing such a simple thing.)

rgov
  • 3,516
  • 1
  • 31
  • 51
  • And if I add a single item to `this.items` in the constructor, the `SelectedIndexChanged` callback fires twice. I can't win. – rgov Jul 02 '18 at 12:59

2 Answers2

0

I also ran into the same problem. Since this was the first question I came across, maybe my answer will save someone some time.

When DataSource is assigned an empty BindingList and items are added to that BindingList later, like so:

private BindingList<MyType> _myList = new BindingList<MyType>();

public MyForm()
{
    listBox1.DataSource = _myList;
}

private void LoadData()
{
    foreach (MyType item in GetDataFromSomewhere())
    {
        _myList.Add(item);
    }
}

then after adding the first item, that will be selected in the ListBox, and listBox1.SelectedIndex will be 0 - but the SelectedIndexChanged event will not have been fired.

Looks like this could be a bug in ListBox as suggested here and here.

A workaround could be to derive from ListBox as Juan did, or just check if the BindingList contains any items before adding:

        bool isFirstItem = (0 == _myList.Count);
        _myList.Add(item);
        if (isFirstItem)
        {
            // Your choice: set listBox1.SelectedIndex to -1 and back to 0,
            // or call the event handler yourself.
        }
lot_styx
  • 25
  • 4
-1

Try manually selecting the last element, instead of relying on the default behavior. This will trigger the event.

public partial class Form1 : Form
{
    BindingList<string> items = new BindingList<string>();
    public Form1()
    {
        InitializeComponent();
        this.listBox1.DataSource = items;
    }

    private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
        this.deleteButton.Enabled = (this.listBox1.SelectedIndex != -1);
    }

    private void addButton_Click(object sender, EventArgs e)
    {
        this.items.Add($"item {this.items.Count + 1}");
        this.listBox1.SelectedIndex = -1;
        this.listBox1.SelectedIndex = this.listBox1.Items.Count - 1;
    }

    private void deleteButton_Click(object sender, EventArgs e)
    {
        this.items.RemoveAt(this.listBox1.SelectedIndex);
    }
}
LolPython
  • 168
  • 1
  • 11
  • No, this solution does not work. As soon as I add to `this.items`, the `SelectedIndex` is set to 0. If I then set it again to 0, no event is fired. – rgov Jul 02 '18 at 16:19
  • You're right. I was able to get it to work by setting the index to -1, then the index of the last item. See my edit. It feels like a hack, but I can't think of another way. Edit: you already tried it – LolPython Jul 02 '18 at 16:27
  • In what way does it not work? The event is firing for me – LolPython Jul 02 '18 at 16:33