0

I have got a for loop to loop through controls in a TableLayoutPanel. In the TableLayoutPanel, there is a series of questions and the user need to tick one of the four Checkboxes to answer that question. The first question is visible, upon ticking an answer, the second question and its Checkboxes become visible. and so on till the 9th question, which ends the form.

At the end there is a Save button where the back-end code has a for loop to loop through all the checkboxes in the tablelayoutpanel to check which checkboxes are checked which are not checked.

When the loop starts running, it starts checking the Checkboxes in the second column, not from the first, not from the beginning. The first column Checkboxes are checked at the end of the loop, which is messing up the order.

Here is the for loop

foreach (Control control1 in tableLayoutPanel1.Controls.OfType<CheckBox>())
{

    CheckBox checkBox = (CheckBox)control1;
    String s = checkBox.Name;

    //boolean value for checkbox if is checked or not
    bool value = checkBox.Checked;

     listValue.Add(value);
}

Why is that happening, what can be done to force the loop to start at the first column Checkboxes?

Jérôme
  • 1,254
  • 2
  • 20
  • 25
Mezo
  • 85
  • 6
  • Instead of using a foreach(), use a for loop and access tableLayoutPanel.Controls[i] that way preserving the order they were added. Foreach is not guaranteed to iterate in the collection order. – Jon Apr 18 '17 at 14:36
  • 1
    @Mangist foreach shouldnt magically run in random orders – EpicKip Apr 18 '17 at 14:37
  • @Mezo if you debug do the checkbox's have the index ordered like you are expecting them to? – EpicKip Apr 18 '17 at 14:38
  • @Mangist I'm sorry, what? As a blind rule, this simply isn't true in C#. See http://stackoverflow.com/a/3097774/1165998 – David L Apr 18 '17 at 14:39
  • @EpicKip No. They are not. The ones at the beginning in the tablelayoutpanel are checked at the end by the loop, thus, there boolean value is at the end of the list. – Mezo Apr 18 '17 at 14:42
  • 2
    @DavidL, You may be right in effect, but note that `Controls` isn't guaranteed to be an array underneath. It would be perfectly valid for it to be backed by a Dictionary, for example. Probably not a good idea, but possible. The standard there refers to arrays only. – Dark Falcon Apr 18 '17 at 14:43
  • @DarkFalcon I don't disagree that it may not be backed by an array and that, in turn, that could cause pseudo-random access. My point is that you cannot blindly state that all foreach loops are not guaranteed ordered access without at least explaining the caveats that may cause exceptions. – David L Apr 18 '17 at 14:46
  • 1
    @DavidL: i guess Mangist used the wrong term. Ofc it's guranteed that a `foreach` enumerates the items in a specific order. But it's not guaranteed that this order matches your expectations. – Tim Schmelter Apr 18 '17 at 14:51
  • @TimSchmelter agreed and that is all I'm trying to point out. It is misleading for anyone who might read it as gospel. – David L Apr 18 '17 at 14:51

3 Answers3

1

When checking the debugger I saw controls added with the designer will take space [0] of the array of controls. So if I add button1, 2 and 3 and run a foreach, I will see button 3 first.

NoText

(Added 2 buttons, then 3 checkbox's so its in 'reversed' order by default)

I have found a solution for this, this way you have to name your checkbox's with numbers corresponding to the question number though.

int amountOfQuestions = 3; 
int index = 0;
while ( index < amountOfQuestions )
{
    index++;
    var checkbox = (CheckBox) this.Controls.Find( "CheckBox" + index, false ).First();
    var value = checkbox.Checked;
}

You can also add the CheckBox's in an array, that way you decide the order and can add one in between without a hassle:

CheckBox[] checkBoxArray = { checkBox1, checkBox2, checkBox3 };
//Add the checkbox's in the array in the order you want
foreach ( CheckBox checkBox in checkBoxArray )
{
    var value = checkBox.Checked;
}
EpicKip
  • 4,015
  • 1
  • 20
  • 37
  • This works, but I'm not sure that relying on the name of the control is the best approach. Working solution, I'll give you that, but maintaining it could be tricky. What if you wanted to insert a question in the middle of the survey, then you have to rename all the check boxes? – Jon Apr 18 '17 at 15:20
  • You can also add them to an array manually, I'll insert that method tomorrow (sleep time now) – EpicKip Apr 18 '17 at 20:31
1

You can just loop through the cells:

for (int y = 0; y < tlp.RowCount; ++y) {
  for (int x = 0; x < tlp.ColumnCount; ++x) {
    CheckBox cb = tlp.GetControlFromPosition(x, y) as CheckBox;
    if (cb != null) {
      MessageBox.Show(cb.Name);
    }
  }
}
LarsTech
  • 80,625
  • 14
  • 153
  • 225
0

Here is a solution, where you can enumerate the check boxes one of 2 ways, either by the Y coordinate in the table or the row number in the table.

tableLayoutPanel1.Controls.Add(new CheckBox(), 0, 0);
tableLayoutPanel1.Controls.Add(new CheckBox(), 0, 1);
tableLayoutPanel1.Controls.Add(new CheckBox(), 0, 2);
tableLayoutPanel1.Controls.Add(new CheckBox(), 0, 3);
tableLayoutPanel1.Controls.Add(new CheckBox(), 0, 4);

// Option 1
// Enumerate controls in order of height (from first to last on the form)
foreach (var checkBox in tableLayoutPanel1.Controls.OfType<CheckBox>().OrderBy(c => c.Location.Y))
{
    // ...
}

// Option 2
// Enumerate controls based on what row they are in the tabletLayoutPanel
foreach (var checkBox in tableLayoutPanel1.Controls.OfType<CheckBox>().OrderBy(c => tableLayoutPanel1.GetRow(c)))
{
    // ...
}
Jon
  • 3,230
  • 1
  • 16
  • 28
  • Both options were able to get me the first column checkboxes but not in the correct order. It will get me Checkbox3 first, then Checkbox1, then Checkbox4 then Checkbox2. – Mezo Apr 18 '17 at 15:29
  • When you added your checkboxes to the form, did you specify the row number for each one when adding to the tableLayoutPanel? Look at how I added the checkboxes, specifying row 0,1,2,3 etc. – Jon Apr 18 '17 at 15:33
  • It worked, it was just the order of checkbox declaration in the designer code, that made the difference, which I did not think of. The top checkboxes in tablelayoutpanel, where declared and assigned the column/row values at the end. – Mezo Apr 18 '17 at 15:41