1

I have a windows form that just exists to take input from user, for all intents and purposes it is just a label and a corresponding input box (textbox, checkbox, masket textbox etc).

I have programatically placed all the input fields in a TabIndex order that is optimal for cycling through them in a way that fits with their locations (tab down each column of inputs, then to the top of the next column).

The person that I am building this project for has stipulated that only like each textbox to come available one at a time, as the previous one has been filled out. This is a crude way of doing it with no validation, but essentially something like this...

if (String.IsNullOrEmpty(textbox1.Text))
{
    textbox2.Enabled = true
}

So this is fine to do with two textboxes in this example, but the form has 28 different inputs on it, so an absurd series of if statements will only be a last resort.

My thoughts has been to put all the inputs in a List, ideally in the same order as is their TabIndexes. I tried to do this using a foreach loop...

    List<Control> inputsList = new List<Control>();
    public void initialiseControls()
    {
        //control position to insert control into list at specified index
        int cntrlpos = 0;
        //for every control in form
        foreach (Control cntrl in this.Controls)
        {
            //not counting labels (not input fields)
            if (!(cntrl is Label))
            {
                //set list position to equal control's TabIndex
                cntrlpos = cntrl.TabIndex;
                //insert the control into the list at the position reflecting TabIndex
                inputsList.Insert(cntrlpos, cntrl); //<---- Error Occurs

                //ASK TEXTBOX TO OUTPUT LIST POSITION AS TEST
                //foreach (var txtbx in this.Controls.OfType<TextBox>())
                //{
                //    txtbx.Text = Convert.ToString(cntrlpos);
                //}
            }
        }

As soon as the function is called, an exception is thrown stating that "Index must be within the bounds of the list". When I put a breakpoint into the code, it showed cntrlpos to equal 29, which is more than the 28 total input controls there are on the form.

I don't know where to go from here, if anyone can offer some advice on the code above to place the Controls into the list in the correct order (or point me in the direction of another method to do something like this), then I would really appreciate it.

Thanks, Mark

marcuthh
  • 592
  • 3
  • 16
  • 42

2 Answers2

2

To make your list, try this:

List<Control> inputList =
    (from Control c in getAllControls(this)
     where c.TabStop
     orderby c.TabIndex
     select c).ToList();

Define the method getAllControls elsewhere in your form class:

IEnumerable<Control> getAllControls(Control parent)
{
    foreach (Control control in parent.Controls)
    {
        yield return control;
        foreach (Control descendant in getAllControls(control))
            yield return descendant;
    }
}

(Taken and modified slightly from Recursive control search with Linq)

This will make it so that you get even nested controls (such as those in panels or groupboxes).

You can't just use the TabIndex as an index into your list, because even stuff like labels have tab indices, which will mess up your indices.

Community
  • 1
  • 1
Jashaszun
  • 9,207
  • 3
  • 29
  • 57
  • `.OfType()` is probably better than the `Where` – Hogan Jul 28 '15 at 17:37
  • @Hogan Except that we need to check for elements that are *not* labels, not elements that *are*. – Jashaszun Jul 28 '15 at 17:40
  • Thanks for your reply, this makes sense and is a lot more efficient than my method. I've just tried to implement it but can't seem to access the `Where`, `OrderBy` or `ToList()` functions. Also, where are you getting 'c' from? Does this need to be declared somewhere like `Control c = new Control();`? Thanks @Jashaszun – marcuthh Jul 28 '15 at 18:22
  • 1
    Change `this.Controls` to `this.Controls.Cast()`. Also, this won't find nested controls, only those that are **directly** contained by the Form itself. – Idle_Mind Jul 28 '15 at 18:28
  • Got it, I'll run some tests and see what I get! Thankyou @Jashaszun – marcuthh Jul 28 '15 at 18:31
  • 1
    @Idle_Mind Thanks for the suggestion to get *all* controls. – Jashaszun Jul 28 '15 at 18:34
  • 1
    Change `getAllControls(parent)` to `getAllControls(control)`. Also, change `where !(c is Label)` to `where c.TabStop`. Now you've to a list of controls that the user would visit when they hit Tab... – Idle_Mind Jul 28 '15 at 18:43
  • @Jashaszun - Good point -- I'm just a `.OfType()` evangelist even when it won't help. – Hogan Jul 28 '15 at 21:11
0

I think you've over complicated it...

Just use Control.GetNextControl:

Retrieves the next control forward or back in the tab order of child controls.

For example, with just TextBoxes:

    private void textBoxes_TextChanged(object sender, EventArgs e)
    {
        Control ctl = (Control)sender;
        if (!String.IsNullOrEmpty(ctl.Text))
        {
            Control next = this.GetNextControl(ctl, true);
            if (next != null)
            { 
                next.Enabled = true;
            }
        }
    }

Obviously you might need a slightly more complicated check for some other types of controls in a different handler, but you'd still just grab the next control to enable using GetNextControl().

Idle_Mind
  • 38,363
  • 3
  • 29
  • 40