Click here to Skip to main content
15,881,655 members
Please Sign up or sign in to vote.
2.00/5 (2 votes)
Hello,

I am trying to create an extended version of the ListBox Control. I want to be able to add additional methods to the Items collection. Consider my code:

C#
public class ListBoxEx : ListBox
{
	private ObjectCollectionEx itm = null;

	public ListBoxEx()
	{
		itm = new ObjectCollectionEx(this);
	}
	~ListBoxEx()
	{
		itm.Clear();
		itm = null;
	}
	new public ObjectCollectionEx Items
	{
		get
		{
			return this.itm;
		}
		set
		{
			this.itm = value;
		}
	}
}

public class ObjectCollectionEx : ListBox.ObjectCollection
{
	private ListBoxEx listBox = null;

	public ObjectCollectionEx(ListBoxEx owner) : base((ListBox)owner)
	{
		this.listBox = owner;
	}

	public ObjectCollectionEx(ListBoxEx owner, ObjectCollectionEx value) : base((ListBox)owner, (ListBox.ObjectCollection)value)
	{
		this.listBox = owner;
	}

	public ObjectCollectionEx(ListBoxEx owner, object[] value) : base((ListBox)owner, value)
	{
		this.listBox = owner;
	}

	public new int Add(object item)
	{
		return base.Add(item);
	}

	public new void AddRange(ListBox.ObjectCollection value)
	{
		base.AddRange(value);
	}

	public new void AddRange(object[] items)
	{
		base.AddRange(items);
	}

	public new void Clear()
	{
		base.Clear();
	}

	public new bool Contains(object value)
	{
		return base.Contains(value);
	}

	public new void CopyTo(object[] destination, int arrayIndex)
	{
		base.CopyTo(destination, arrayIndex);
	}

	public new int Count
	{
		get
		{
			return base.Count;
		}
	}

	public new bool Equals(object obj)
	{
		return base.Equals(obj);
	}

	public new int IndexOf(object value)
	{
		return base.IndexOf(value);
	}

	public new void Insert(int index, object item)
	{
		base.Insert(index, item);
	}

	public new void Remove(object value)
	{
		base.Remove(value);
	}

	public new void RemoveAt(int index)
	{
		base.RemoveAt(index);
	}
	
	public void NewMethod()
	{
		//do something
	}
}


When I use the new ListBox, all is well, except when I try to do something like this:

C#
        private void button1_Click(object sender, EventArgs e)
{
// not actual code, for example purposes only.
    listBoxEx1.Items.Add("Hello");
    listBoxEx1.Items.Add("World");
    MessageBox.Show(listBoxEx1.SelectedIndex);    // returns -1, unless I have selected an item previously... [problem #1 — SA] normal behaviour
    listBoxEx1.SelectedIndex = 0;                 // throws an InvalidArgument exception (Value of '0' is not valid for 'SelectedIndex'.) [problem #2 — SA]
    MessageBox.Show(listBoxEx1.SelectedItem);     // throws an IndexOutOfRange Exception if an item is selected, otherwise NullReferenceException is thrown. [problem #3 — SA]
}


I'm really at wit's end here, so please, if anybody could please help, I'd be eternally grateful. Thanks.
Posted
Updated 26-Jul-13 16:42pm
v4
Comments
Sergey Alexandrovich Kryukov 26-Jul-13 15:27pm    
Which "ListBox" type, exactly? Fully-qualified name, please.
—SA
SpaghettiCoder 26-Jul-13 15:54pm    
The listbox class that I am interested in is the one found under the namespace System.Windows.Forms
Sergey Alexandrovich Kryukov 26-Jul-13 16:25pm    
Please, to avoid any confusions, always provide fully-qualified type names. Also, please tag the application type or UI library you are using.
—SA

Please look at your code samples: I numbered them #1.. #3.

Problem #1:
Not a problem; this is correct behavior.

Problems #2 and #3:
The are stemmed from the fact that you have hidden Items property by adding new public ObjectCollectionEx Items. From this moment, you have two collections of items. If you use, say, inherited Add, you add some item to the inherited correction. But when you try to get access the item using the Items property, you access your new collection and cannot access the inherited one. Apparently, it can create as much mess as it can.

The whole idea is wrong. You cannot override the property Items, because it's not virtual:
http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.items.aspx[^],
http://msdn.microsoft.com/en-us/library/system.windows.forms.listbox.items.aspx[^].

(Unfortunately, you did not show exact type of ListBox, but there are unrelated classes with such name. You always need to show fully-qualified names of relevant types when asking questions.)

—SA
 
Share this answer
 
You've missed an important point which is to tell the list box that it has a new collection. To do that you must override and create the new collection within ListBox.CreateItemCollection. Once that's done, the SelectedIndex, SelectedItem and FindString problems should go away.

I'd try something like this:
C#
public class ObjectCollectionEx : ListBox.ObjectCollection {
  // duplicate the original constructors
  public ObjectCollectionEx(ListBox owner) : base(owner) { }
  public ObjectCollectionEx(ListBox owner, ListBox.ObjectCollection value) : base(owner, value) { }
  public ObjectCollectionEx(ListBox owner, object[] value) : base(owner, value) { }

  public new int Add(Object item) {
    return base.Add(MessWithObject(item));
  }

  // Make it visibly obvious that the new collection is being used!
  private Object MessWithObject(Object item) {
    return String.Format("{0} ({1})", item, item.GetType());
  }
}

public class ListBoxEx : ListBox {
  protected override ListBox.ObjectCollection CreateItemCollection() {
    return new ObjectCollectionEx(this);
  }

  public new ObjectCollectionEx Items {
    get { return (ObjectCollectionEx)base.Items; }
  }
}

Alan.
 
Share this answer
 
I think I've experienced an epiphany. In case anybody else is interested, here's how:

I will have to override the default property "SelectedIndex" using the SendMessage() function of the olden days of VB 6.0:
C#
public class ListBoxEx : ListBox
{
    // just supplement the above class with the properties / methods below... 

    [DllImport("USER32.DLL")]
    private static extern int SendMessage(IntPtr hWnd, uint message, int wParam, string lParam);
    [DllImport("USER32.DLL")]
    private static extern int SendMessage(IntPtr hWnd, uint message, int wParam, int lParam);
    [DllImport("USER32.DLL", CharSet = CharSet.Auto)]
    private static extern int SendMessage(IntPtr hWnd, uint message, int wParam, StringBuilder lParam);

    private const int LB_FINDSTRING = 0x018F;
    private const int LB_FINDSTRINGEXACT = 0x1A2;
    private const int LB_SETCURSEL = 0x0186;
    private const int LB_GETCURSEL = 0x0188;
    private const int LB_GETTEXT = 0x0189;

    ...

    new public int FindString(string s)
    {
        return SendMessage(this.Handle, LB_FINDSTRING, -1, s);
    }

    new public int FindStringExact(string s)
    {
        return SendMessage(this.Handle, LB_FINDSTRINGEXACT, -1, s);
    }

    new public int SelectedIndex
    {
        get
        {
            return SendMessage(this.Handle, LB_GETCURSEL, 0, 0);
        }
        set
        {
            SendMessage(this.Handle, LB_SETCURSEL, value, 0);
        }
    }

    new public object SelectedItem
    {
        get
        {
            StringBuilder s = new StringBuilder ( 256 );    // clean this up call LB_GETTEXTLEN first!
            SendMessage(this.Handle, LB_GETTEXT, this.SelectedIndex, s);
            return s;
        }
        set
        {
            this.SelectedIndex = this.FindStringExact(value.ToString());
        }
    }
}


Yes, the code is kludgy, esp. considering that you'd also have to define the SelectedValue, SelectedIndices, etc. properties, but those I don't feel like tackling those yet. It will just have to do for now, unless someone here presents us with a better, simpler solution.


Ahh, I feel like Archimedes =P
 
Share this answer
 
v3

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900