views:

151

answers:

4

I create a combobox in a class and want to set the selected value for that combobox. But when I do that, the selectedValue stays null and when I try to set the selectedIndex I get a ArgumentOutOfRangeException.

Code:

public Control GenerateList(Question question)
{
    // Clear the local givenAnswer collection
    _givenAnswer.Clear();

    // Get a list with answer possibilities
    List<QuestionAnswer> answers = question.GetAnswerSort();

    // Get a collection of given answers
    Collection<QuestionnaireAnswer> givenAnswers = question.GetGivenAnswers();

    _givenAnswer = givenAnswers;

    ComboBox cmb = new ComboBox();
    cmb.Name = "cmb";
    cmb.DisplayMember = "Answer";
    cmb.ValueMember = "Id";
    cmb.DataSource = answers;
    cmb.Dock = DockStyle.Top;

    // Check an answer is given to the question
    if (givenAnswers != null && givenAnswers.Count > 0)
    {
        cmb.Tag = givenAnswers[0].AnswerId;
        cmb.SelectedValue = givenAnswers[0].AnswerId; // answerId = 55, but SelectedValue stays null
    }

    cmb.SelectedIndex = 1; // For testting. This will throw a ArgumentOutOfRangeException
    cmb.DropDownStyle = ComboBoxStyle.DropDownList;
    cmb.SelectedIndexChanged += new EventHandler(cmb_SelectedIndexChanged);

    return cmb;
}

I hope someone can explain to me what is happening so I can understand why it isn't working.

Here is a complete little program what illustrates my problem. As you can see it doesn't set the SelectedValue, this stays null

namespace Dynamic_Create_Combo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            GenerateControls gc = new GenerateControls();
            Control c = gc.GenerateCombo();

            this.SuspendLayout();
            this.Controls.Add(c);
            this.ResumeLayout(true);
        }
    }

    public class GenerateControls
    {
        public Control GenerateCombo()
        {
            // Create datasource
            Collection<Car> cars = new Collection<Car>();
            Car c = new Car();
            c.Id = 1;
            c.Name = "Car one";
            cars.Add(c);

            Car c1 = new Car();
            c1.Id = 2;
            c1.Name = "Car two";
            cars.Add(c1);

            Car c2 = new Car();
            c2.Id = 2;
            c2.Name = "Car three";
            cars.Add(c2);

            ComboBox cmb = new ComboBox();
            cmb.DropDownStyle = ComboBoxStyle.DropDownList;
            cmb.DataSource = cars;
            cmb.DisplayMember = "Name";
            cmb.ValueMember = "Id";

            cmb.SelectedValue = 2;

            return cmb;
        }
    }

    public class Car
    {
        private int _id;
        private string _name;

        public int Id
        {
            get { return _id; }
            set { _id = value; }
        }

        public string Name 
        {
            get { return _name; }
            set { _name = value; }
        }
    }
}
+6  A: 

You've set the value member to be "Id" but you're trying to use "AnswerId" as the selected value.

Without more details, it's hard to say why setting SelectedIndex is throwing an ArgumentOutOfRangeException - perhaps the combobox is ignoring all values which don't have an "Id" property, thus giving you no values, so selecting index 1 is impossible?

EDIT: Okay, so it looks like it's only actually doing the binding when it becomes visible - or at some stage in the process. I've tried a few things to accelerate this, but they don't appear to help. What you can do is defer your selection:

EventHandler visibleChangedHandler = null;
visibleChangedHandler = delegate {
    cmb.SelectedIndex = 2;
    cmb.VisibleChanged -= visibleChangedHandler; // Only do this once!
};
cmb.VisibleChanged += visibleChangedHandler;

It's an ugly workaround, but it should at least help you to get going for the moment.

Jon Skeet
My combo it's ValueMember is Id, of type long. The AnswerId you referring to, is also of type long. I looks like the combo hasn't 'bind' the datasource yet. Because when I set the selectedValue on the actual form, it works. The problem is, I can't set the SelectedValue in the class, but I can set it on the form where the control takes his place.
Martijn
@Martijn: It would be really helpful if you could add a short but *complete* program so that we could try this for ourselves. It needn't be complicated - but currently we're just guessing.
Jon Skeet
@Jon: Ive edited my startpost with a complete program with illustrates my situation.
Martijn
@Martijn: That's not a complete program. Did you try compiling it with just the code you provided? Having added a `Main` method, removed the call to `InitializeComponent` and added the `using` directives, however, it compiles and runs without doing the appropriate selection... investigating.
Jon Skeet
When I run that code I get the exact same problem. I don't know what you mean. Why isn't this a complete program?
Martijn
@Martijn: Yes, I get the problem too. It's not a complete program because I can't cut and paste it into a new text file, compile and run it.
Jon Skeet
Ah okee. But what do I have to do or can I do, to get this working?
Martijn
@Martijn: I've just edited in a workaround. It's butt ugly I'm afraid :(
Jon Skeet
Thank you for the workaround. Despite it is uggly, I'm kind of new to this approach. If I understand this correctly you are unregistering the default visibleChangedHandler handler and replace it by your custom visibleChangedHandler? Which set the selectedindex (and unregister the default handler)?
Martijn
@Martijn: No, it's not unregistering any existing handlers - it's adding a handler which sets the selected index, but then unregistering itself. The idea is this event handler should only fire the *first* time the visibility changes. It's "self-deregistering".
Jon Skeet
Thank for the workaround. But I it remains vague why the selected index can't be set in the class. Or is that because the items aren't drawn yet or something like that..
Martijn
@Martijn: Not necessarily that they haven't been drawn - but the full databinding hasn't taken place. I don't know what prompts that, unfortunately :(
Jon Skeet
+2  A: 

This is just a guess, but maybe, the ComboBox doesn't bind the data in the DataSource until it's drawn. Check cmb.Items.Count in the line before the SelectedIndex = 1. If it is 0 try to first add the cmb to the Form before assigning SelectedIndex.

EDIT:

    public Control GenerateCombo() 
    { 
        // Create datasource 
        Collection<Car> cars = new Collection<Car>(); 
        Car c = new Car(); 
        c.Id = 1; 
        c.Name = "Car one"; 
        cars.Add(c); 

        Car c1 = new Car(); 
        c1.Id = 2; 
        c1.Name = "Car two"; 
        cars.Add(c1); 

        Car c2 = new Car(); 
        c2.Id = 2; 
        c2.Name = "Car three"; 
        cars.Add(c2); 

        ComboBox cmb = new ComboBox(); 
        cmb.DropDownStyle = ComboBoxStyle.DropDownList; 
        cmb.DataSource = cars; 
        cmb.DisplayMember = "Name"; 
        cmb.ValueMember = "Id"; 

        // add this: 
        EventHandler visibleChangedHandler = null; 
        visibleChangedHandler = delegate { 
            cmb.SelectedIndex = 2; 
            cmb.VisibleChanged -= visibleChangedHandler;
        }; 
        cmb.VisibleChanged += visibleChangedHandler; 

        // delete this: cmb.SelectedValue = 2; 

        return cmb; 
    } 
Hinek
The Item count is zero. When I add the control to the form I can set the SelectedValue (which I get from the Tag property..)
Martijn
this is Jon's workaround in action ...
Hinek
+1  A: 

You need to set the cmb.DataBindings and this might help.

MrFox
A: 

You may want to try SelectedItem instead of SelectedValue, and assigning the class that you want to it. For example:

Car c1 = new Car();
// snip
cmb.SelectedItem = c1;

Since more than one item can have a value of 2 (as far as the ComboBox knows, anyway), I think you will have difficulty setting the value via SelectedValue.

rakuo15
I've tried this, but any Selected****(item, index, value) doesn't work.
Martijn
It may be true that you are trying to add a value immediately after assigning a new datasource, but before that data becomes available in the listbox.Try binding to the event `DataSourceChanged` and set the selected item there.
rakuo15
And should I register to that event before or after I set the datasource property?
Martijn
I've tried both cases: before and after setting the DataSource property. I also tried DrawItem event, but none event fires..
Martijn