8

For a ListBox (With Selection mode set to One), I wish to track whether there's a selected item or none selected. To do so, I subscribed a method to SelectedIndexChanged and checked if the SelectedIndex is -1 or not. However, I noticed that the event doesn't fire after calling Items.Clear(), even though SelectedIndex changes to -1 (if it wasn't already -1).

Why doesn't it fire? I know I can work around this by assigning -1 to SelectedIndex before clearing the list. But is there a better way?

Here's a simple code to replicate this:

using System;
using System.Windows.Forms;

namespace ns
{
    class Program
    {
        static ListBox lst = new ListBox();

        public static void Main()
        {
            lst.SelectedIndexChanged += new EventHandler(lst_SelectedIndexChanged);

            lst.Items.Add(1);

            Console.WriteLine("Setting selected index to 0...");
            lst.SelectedIndex = 0; //event fire here

            Console.WriteLine("(Selected Index == {0})", lst.SelectedIndex);

            Console.WriteLine("Clearing  all items...");
            lst.Items.Clear(); //event *should* fire here?!

            //proof that the selected index has changed
            Console.WriteLine("(Selected Index == {0})", lst.SelectedIndex);
        }

        static void lst_SelectedIndexChanged(object sender, EventArgs e)
        {
            Console.WriteLine("[!] Selected Index Changed:{0}", lst.SelectedIndex);
        }
    }
}

Edit: I am considering making a custom list by making a class that inherits from ListBox, or by making a user control. However I'm not sure how to approach this. Any ideas on hiding/overriding the clear method using either inheritance/userControl? Would it require hiding/overriding other methods as well or is there a way to avoid this?

  • Suggestion: set SelectedIndex to -1 AFTER the call to Clear(). You never know when this method might fail. – Adi Mar 30 '11 at 20:52
  • A better workaround might be to just call the handler directly. But like your current workaround, that still requires extra code every time the list is cleared. – Ben Voigt Mar 30 '11 at 20:53
  • You might want to consider why you need to respond to Clear() changing the index. The event exists because the code needs to be executed in response to an occurrence out of your control (i.e. the user changing their selection.) Calling clear is an explicit act on your part, so whatever code needs to be executed can just be called right then. – dlev Mar 30 '11 at 21:00
  • @Adi: Setting it after won't raise the event (it is already -1 after Clear() so it doesn't change). Am I sensing sarcasm? @Ben Voigt - Yes, that's the problem. I want to call Clear() without requiring an extra code to make it work right. @dlev - That'll still give me refractoring problems. Wherever I'll want to call Clear(), I'll to remeber to add a workaround to make the event fire. That doesn't feel right. – Shmuel Valariola Mar 30 '11 at 22:04
  • No sarcasm from my side. And you are right about the event not being raised after Clear(). – Adi Mar 30 '11 at 22:32

4 Answers4

7

Looking at the code in Reflector, the Clear() method on Items just resets the .Net object's internal object list (and does not, as you noticed, fire OnSelectedIndexChanged).

The SelectedIndex property returns -1 because the logic in the property's getter dictates that -1 should be returned if there are no items in the internal list.

dlev
  • 48,024
  • 5
  • 125
  • 132
  • 3
    But isn't it sensible the it should fire the event? Is this by design? Or a bug? – Shmuel Valariola Mar 30 '11 at 22:14
  • 1
    Closest answer. Accepted. However, if anyone can think of a good workaround that can "hide" or "override" Clear so as to make sure the event is fired, I'd be happy to hear about it. – Shmuel Valariola May 25 '11 at 18:58
3

Clear() only clears the internal collection of the control. Clear() won't fire the SelectedIndexChanged event because that event will only be raised by changing the CurrentlySelectedIndex. Try using lst.ClearSelected() instead. Calling this method is equivalent to setting the SelectedIndex property to negative one (-1). You can use this method to quickly unselect all items in the list. Alternatively you can try calling Items.Clear() and follow it with a call to ListBox.RefreshItems

Mr. Young
  • 2,364
  • 3
  • 25
  • 41
  • This will set the selectedIndex to -1, but it will not remove the items from the list. – Prescott Mar 30 '11 at 20:54
  • as Prescott said it will not remove the items. And I want the items removed. – Shmuel Valariola Mar 30 '11 at 22:13
  • That would require an additional operation to clear the data context of the control. You'll need to additionally call [Items.Clear()](http://msdn.microsoft.com/en-us/library/system.windows.forms.listbox.objectcollection.clear.aspx) – Mr. Young Apr 12 '11 at 21:33
  • I can see the point now, you meant using ClearSelected instead of setting the indext to -1 dierctly and **then** calling Clear. However, I still don't think that Clear should behave like that. Also, I don't see the how you suggest I call ListBox.RefreshItems, it is protected. – Shmuel Valariola May 25 '11 at 19:13
  • Yeah, you are correct on RefreshItems. I overlooked that it was marked as protected. Apologies. – Mr. Young May 27 '11 at 15:45
1

probably a hackish solution but this is what i thought of:

class myListBox
    {
        public ListBox myList;

        public myListBox()
        {
            myList = new ListBox();
        }

        public void listClear()
        {
            if (myList.Items.Count > 0)
            {
                myList.SelectedIndex = 0;
            }
            myList.Items.Clear();
        }

    }

than you can call this like this in your main form:

            myListBox example = new myListBox();
            example.myList.Items.Add("Example");
            example.myList.SelectedIndexChanged += new EventHandler(lst_SelectedIndexChanged);
            this.Controls.Add(example.myList);
            example.listClear();

maybe that could solve your problem.

Eli Braginskiy
  • 2,867
  • 5
  • 31
  • 46
  • Thank you for your answer and sorry for my late response. Do note that SelectedIndex needs to be -1 ("none selected") and not 0 ("first item selected"). Also, you can safely remove the if statement. As for the general proposed solution, I find it neat to group the method that resets & clears the ListBox with the ListBox object itself as you did. However, I'm concerned that anyone using this class might forget of ListClear and will use myList.Clear (especially since s/he need to use all other inner methods of myList). if only there was a way to "lock" myList.CLear() here... – Shmuel Valariola Apr 27 '11 at 21:45
  • You might want to make `myListBox` extend `ListBox`. That way you keep full functionality of `ListBox`, can use it everywhere that expects a `ListBox` class. Sadly that does not solve the confusion between `Items.Clear` and a custom `listClear` method. You should also raise `SelectedIndexChanged` event in the custom clear method, because I think that event does not fire when setting the `SelectedIndex` property. – Xilconic Apr 11 '13 at 12:33
0

This is my way, it's compatible with existed code.

public class DetailsListView : ListView
{
    public new class ListViewItemCollection : ListView.ListViewItemCollection
    {
        private DetailsListView m_owner;
        public ListViewItemCollection(DetailsListView owner)
            : base(owner)
        {
            m_owner = owner;
        }

        public override void Clear()
        {
            base.Clear();
            m_owner.FireChanged();
        }
    }

    private void FireChanged()
    {
        base.OnSelectedIndexChanged(EventArgs.Empty);
    }


    private ListViewItemCollection m_Items;

    public DetailsListView()
    {
        m_Items = new ListViewItemCollection(this);

        View = View.Details;
        GridLines = true;
        HideSelection = false;
        FullRowSelect = true;
    }

    public new ListViewItemCollection Items
    {
        get
        {
            return m_Items;
        }
    }

}
IlPADlI
  • 1,943
  • 18
  • 22