7

I am dynamically creating a Winforms multi-select ListBox and adding it into a flowpanel control. I bind a datasource from an object I created and verified that the DataSource has does in fact have approximately 14 elements. When I do a listBox.SetSelected(0, true) I get an System.ArgumentOutOfRangeException error thrown.

I have determined the problem is that while the DataSource has 14 elements, the Item collection has none (0) and is therefore throwing the exception. My question is why are these two different from one another, and why would I not simply do a foreach item in datasource add to the item collection?

The following is the code I have so far:

case InsertableItemParameter.ParameterType.ListBox:
    //note: two-way bindings are not possible with multiple-select listboxes
    Label lblListBox = new Label();
    lblListBox.Text = param.DisplayText;
    ListBox listBox = new ListBox();
    listBox.DataSource = param.Values;
    listBox.DisplayMember = "Value";
    listBox.SelectionMode = SelectionMode.MultiExtended;
    listBox.Size = new System.Drawing.Size(flowPanel.Size.Width - lblListBox.Size.Width - 10, 100);
    listBox.SetSelected(0, true);   //will throw argument out of range exception here!
    listBox.SetSelected(1, true);
    flowPanel.Controls.Add(lblListBox);
    flowPanel.Controls.Add(listBox);
    flowPanel.SetFlowBreak(listBox, true);
    break;

Below is an alternative solution I attempted and worked, but again why would I use DataSource versus Items collection?

case InsertableItemParameter.ParameterType.ListBox:
    //note: two-way bindings are not possible with multiple-select listboxes
    Label lblListBox = new Label();
    lblListBox.Text = param.DisplayText;
    ListBox listBox = new ListBox();
    //listBox.DataSource = param.Values;
    listBox.DisplayMember = "Value";
    listBox.SelectionMode = SelectionMode.MultiExtended;
    listBox.Size = new System.Drawing.Size(flowPanel.Size.Width - lblListBox.Size.Width - 10, 100);
    listBox.BeginUpdate();
    foreach (String paramater in param.Values)
    {
        listBox.Items.Add(paramater);
    }
    listBox.EndUpdate();
    listBox.SetSelected(0, true);
    listBox.SetSelected(1, true);
    flowPanel.Controls.Add(lblListBox);
    flowPanel.Controls.Add(listBox);
    flowPanel.SetFlowBreak(listBox, true);
    break;

ANSWER: Thanks for all the responses. The issue here is visibility and win-form rendering. While the difference between DataSource and Items collections were not really addressed save a few people, the true source of my problem was resolved by calling the SetSelected() method after the form was done drawing. This causes a lot of problems in the design of my application which I have to resolve, but this is was the problem. See the reply I marked as the answer.

Magnum
  • 1,555
  • 4
  • 18
  • 39
  • From what I understand of your post, the `ListBox.DataSource` is _where_ your data is coming _from_ and the `ListBox.Item` is what data you already have. – Brian Mar 04 '13 at 18:33
  • @Brian Actually, the DataSource is where I have data, and ListBox.Item is completely empty. When I attempted to add my DataSource to my Item collection using a simple `listBox.Items.Add(paramater);` I received an error stating I cannot add items to the Item collection when DataSource is set. – Magnum Mar 04 '13 at 18:40
  • Can you please post all the code? – Brian Mar 04 '13 at 18:43
  • @Brian I have added the alternative code I wrote without setting the DataSource. The above is all the code relevant to this question--all the other stuff is simply building/retrieving the data from my external sources. – Magnum Mar 04 '13 at 18:49
  • Thank you for posting that. Are you setting the `.DataSource` somewhere else in your code? – Brian Mar 04 '13 at 18:52
  • 1
    Check the answer below, I'm sure it will help you, the problem is that your control isn't visible at the time of DataSource setting which is a known [issue](http://stackoverflow.com/questions/9828153/cannot-data-bind-to-a-control-when-control-visible-false). Just add control to the parent control (in order to set it visible) and then you can set the selected items. – Nikola Davidovic Mar 04 '13 at 18:57
  • Why create the control at runtime? Can you have it on the form at design time and have it set `Visible = false` and then show it/set the datasource when you need it? Why the extra layer of complexity? – Brad Mar 04 '13 at 19:11
  • 1
    @Brad: My guess is that because a number of controls is unknown at design time, and can vary from 0 to infinity. – Victor Zakharov Mar 04 '13 at 19:12
  • @Neolisk this is true. The requirements is that the form gets designed through an externally controled XML file and can have an infinite number of listboxes to render. – Magnum Mar 04 '13 at 19:37

5 Answers5

4

Your problem probably lies elsewhere, because this code works fine:

string[] ds = {"123","321"};
listBox1.DataSource = ds;
listBox1.SetSelected(1, true);
MessageBox.Show(listBox1.Items.Count.ToString()); //returns 2

Tested in a brand new C# project with a listBox1 put on the form, and the above code sitting in Form_Load.

EDIT: I did not realize that creating a ListBox in runtime could make a difference, and especially because it matters when to set selected items. This code works:

string[] ds = { "123", "321" };
ListBox lst = new ListBox();
lst.DataSource = ds;
lst.Size = new Size(100,100);            
this.Controls.Add(lst);
//make sure to call SetSelected after adding the ListBox to the parent
lst.SetSelected(1, true);

Thanks to @Brad for pointing this out. So back on the original question, replace this:

listBox.SetSelected(0, true);
listBox.SetSelected(1, true);
flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox);

with this:

flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox);
listBox.SetSelected(0, true);
listBox.SetSelected(1, true);

And it should work.

Victor Zakharov
  • 25,801
  • 18
  • 85
  • 151
  • 2
    The difference between this code and his is that you put the listbox on the form at design time were as OP put it there at runtime. – Brad Mar 04 '13 at 19:02
  • 1
    Since you have putted the control on the parent control at design time makes your control visible at the time of setting of the DataSource so it works, OP sets the Parent control after he tries to access the Items collection. – Nikola Davidovic Mar 04 '13 at 19:05
  • So it's a rendering issue. The problem is I cannot set an item a selected item until after the form draw is completed which is very unfortunate. I need to get around this else I would have to track all listbox elements created to later bind to these values :( – Magnum Mar 04 '13 at 19:36
2

You have two options of how to get data to be available in a ListBox. You can set the DataSource or you can add the items manually via listBox.Items.Add(paramater). You cannot do both because they will step on each other hence your error

...cannot add items to the Item collection when DataSource is set.
Brad
  • 11,934
  • 4
  • 45
  • 73
  • Yes, I found this out. I added some additional code below the horizontal line I added to demonstrate disabling the .DataSource object, and using Items collection only. However, if you look at another answer written below by Neolisk, you can see that setting the DataSource and using SetSelected is working for him. I'm trying to figure out why now – Magnum Mar 04 '13 at 19:02
1

Items From MSDN

This property enables you to obtain a reference to the list of items that are currently stored in the ListBox. With this reference, you can add items, remove items, and obtain a count of the items in the collection. For more information about the tasks that can be performed with the item collection, see the ListBox.ObjectCollection class reference topics.

Datasource From MSDN

An object that implements the IList or IListSource interfaces, such as a DataSet or an Array. The default is null

I'm not an expert on this matter, but from what I read it appears that Items allows you to add/modify the contents in the list whereas Datasource retrieves and sets the content.

Syperus
  • 1,707
  • 2
  • 11
  • 11
1

Im not sure why there are two different Collections. The Items property seems more simple.

I found the reason for the exception: apparently you have to do things in a specific order, like this:

    //init the listbox
    var listBox1 = new ListBox();
    listBox1.Location = new System.Drawing.Point(122, 61);
    listBox1.Size = new System.Drawing.Size(205, 147);
    listBox1.SelectionMode = SelectionMode.MultiExtended;
    Controls.Add(listBox1); //<-- point of interest

    //then set the DataSource
    listBox1.DataSource = lst;
    listBox1.DisplayMember = "Name";
    listBox1.ValueMember = "Age";

    //then set the selected values
    listBox1.SetSelected(0, true);
    listBox1.SetSelected(1, true);

My Test class look like this:

public class Test
{
    private static Random r = new Random();
    public Test (string name)
    {
        Name = name;
        Age = r.Next(16, 45);
    }

    public string Name { get; set; }

    public int Age{ get; set; }
}

And lst is declared like this:

    var lst = new List<Test>()
                  {
                      new Test("jens"),
                      new Test("Tom"),
                      new Test("John"),
                      new Test("Don"),
                      new Test("Jenny"),
                  };
Jens Kloster
  • 11,099
  • 5
  • 40
  • 54
  • I tried your suggestion to add the listbox to the flowpanel control before doing the SetSelected. I'm beginning to suspect it's a Winforms rendering problem where the collections do not get updated until a certain point of the draw? I'm working on verifying this suspicion. – Magnum Mar 04 '13 at 19:00
  • @Magnum I agree. Perhaps with some right placed `BeginEdit`() and `EndEdit()` – Jens Kloster Mar 04 '13 at 19:05
  • @Magnum but did you try setting the `DataSource` *after* the `listBox`was added to `flowPanel`? – Jens Kloster Mar 04 '13 at 19:14
  • see the answer I marked, the main difference between your test and my problem is that I'm dynamically creating the listbox at runtime. If you use the designer, the listbox is present and rendered otherwise and using either DataSource exclusive or Items will work. – Magnum Mar 04 '13 at 19:42
1

The Items collection is populated from the DataSource only when the Control is visible. since you create your control dynamically, it is not added to the parent control and therefore not visible. Therefore you first need to have a Control that is visible on the screen. In your code you set DataSource and then set the selected items before your Control is visible on the FlowChart since it isn't added to the Parent control. You should change the sequence of the statements. You should add the listBox to the FlowPanel which will populate Items collection from the DataSource upon which you can execute SetSelected() method. Try this and note the changed order of the execution of your initial code:

ListBox listBox = new ListBox();
listBox.DataSource = param.Values;
listBox.DisplayMember = "Value";
listBox.SelectionMode = SelectionMode.MultiExtended;
listBox.Size = new System.Drawing.Size(flowPanel.Size.Width - lblListBox.Size.Width - 10, 100);
flowPanel.Controls.Add(lblListBox);
flowPanel.Controls.Add(listBox); //notice that you first add the listBox to the flowChart
listBox.SetSelected(0, true);   //and then you have items in the Items collection which you can select
listBox.SetSelected(1, true);
Nikola Davidovic
  • 8,556
  • 1
  • 27
  • 33