0

Quick question, I know there is a foreach statement that checks each specified control in a form, but is there a forany statement that can check for a specific control amongst a group of the same types of controls, or is there a certain specification I can use in a foreach state that limits it. For example: Say I check if ANY RadioButton on a form is checked. If one is checked, it a message box will display "Hi". My approach would go something like:

foreach (Control rb in this.Controls.OfType<RadioButton>())
{
    if(((RadioButton(rb)).Checked == true)
    {
        MessageBox.Show("Hi");
        return;
    }
}

The problem with this is it checks ALL RadioButtons and sees if they are checked. I don't want that. I want it to check if anyone of the are checked. Is there a way I can manipulate this?

  • 1
    No, your current approach checks only until it has found a checked radiobutton – Tim Schmelter Feb 23 '17 at 13:39
  • @TimSchmelter Does it check every specified control? – Bradley William Elko Feb 23 '17 at 13:44
  • You could just write your own enumerable extension that executed a method on the first true – Callum Linington Feb 23 '17 at 13:45
  • 1
    Your example would return, and therefore break the loop, the first time it finds a checked button. My guess is that your code isn't actually checking any of the buttons you are looping through but is instead checking the sender control (wrapped as `rbs`) on each iteration. – Abion47 Feb 23 '17 at 13:45
  • @CallumLinington The `Any` method already basically does that. – Abion47 Feb 23 '17 at 13:46
  • 1
    `ForAny` = `Where` + [`ForEach`](http://stackoverflow.com/q/1509442/1997232). – Sinatr Feb 23 '17 at 13:47
  • @Abion47 as Tim already pointed out to the guy who answered with Any, no it doesnt do what OP wants... – Callum Linington Feb 23 '17 at 13:49
  • @Sinatr so would `foreach (Control rb in this.Controls.OfType().Where(x => x.Checked == true){}` work? – Bradley William Elko Feb 23 '17 at 13:50
  • @CallumLinington It does exactly what you are describing. The first time a check would return true, `Any` will return true, allowing code to be run when used in conjunction with an if block. If that isn't what OP actually wants, then he needs to clarify what it is he needs. – Abion47 Feb 23 '17 at 13:52
  • @BradleyWilliamElko It depends. Do you need references to all the buttons that are checked or do you just need to know that one of them is checked? – Abion47 Feb 23 '17 at 13:53
  • @BradleyWilliamElko, that would enumerate all checked radiobuttons of `this` (but not radiobuttons nested in another containers). – Sinatr Feb 23 '17 at 13:54
  • @Abion47 I need to know if one of them is checked. – Bradley William Elko Feb 23 '17 at 14:00
  • Your approach doesn't check if **any** of your radio buttons are checked, *except* for the one which is equal to `sender`.... The loop does nothing with the collection it is iterating over (no access to `rb` inside the loop) or am I missing something? – pinkfloydx33 Feb 23 '17 at 14:01
  • @BradleyWilliamElko Then your current `foreach` approach is fine (minus the typo that pinkfloydx33 and I have pointed out). If you must have a LINQ version, use the `Any` method as described by Ehsan Sajjad's answer. – Abion47 Feb 23 '17 at 14:05

3 Answers3

6

Linq is your friend here, Use Any() method which will return bool, but behind the scenes of-course it will also loop through the check-boxes it's just syntactic sugar.

Your all this code :

foreach (Control rb in this.Controls.OfType<RadioButton>())
{
    RadioButton rbs = sender as RadioButton();
    if(rbs.Checked == true)
    {
        MessageBox.Show("Hi");
        return;
    }
}

can be replaced with:

var anyRadioChecked = this.Controls.OfType<RadioButton>().Any(x=>x.Checked);
if(anyRadioButton)
{
   MessageBox.Show("Hi");
   return;
} 

and if you are inside a RadioButton event, and you want to check for the RadioButton is checked only if there is any RadioButton on the Form then you would need to do it following way:

RadioButton rbs = sender as RadioButton;
var anyRadioButton = this.Controls.OfType<RadioButton>().Any();
if(anyRadioButton && rbs.Checked)
{
   MessageBox.Show("Hi");
   return;
} 

The above will return true if atleast one checkbox is checked.

Ehsan Sajjad
  • 61,834
  • 16
  • 105
  • 160
  • 2
    OP asked for a method like ForAny instead of doing foreach – Ehsan Sajjad Feb 23 '17 at 13:41
  • 1
    but OP does also not enumerate the whole collection. He stops on the first checked button. If he'd initialize `bool` variable `anyChecked` to `true` and `break` the loop he would have the same code as you without LINQ. Also, `ForAny` sounds more like `Controls.OfType().Where(rb =>rb.Checked)` – Tim Schmelter Feb 23 '17 at 13:42
  • agreed but OP asked for a method built-in like ForAny, so we have `Any()` right :) – Ehsan Sajjad Feb 23 '17 at 13:43
  • @EhsanSajjad Does this also work in a `foreach` statement? And, could this be `bool anyChecked` instead of `var anyChecked`? – Bradley William Elko Feb 23 '17 at 13:46
  • @BradleyWilliamElko `var anyChecked` and `bool anyChecked` are identical in this context. – Abion47 Feb 23 '17 at 13:47
  • 1
    Your code contains the same typo that OP's code does. It's not checking if `rb` is checked. It's checking if `rbs` is checked. As such, your LINQ will produce different results than the foreach method. – Abion47 Feb 23 '17 at 13:48
  • oh alright, i misunderstood the question, i have updated answer. – Ehsan Sajjad Feb 23 '17 at 13:55
  • This only checks if there are any radio buttons on the form and if the one referenced to by sender is checked. Why is nobody using the values being iterated over (`rb` in op) – pinkfloydx33 Feb 23 '17 at 14:04
  • I'm pretty sure that the typo is OP's, and that rather than deviating from his intent you merely recopied his error. – Abion47 Feb 23 '17 at 14:06
  • @EhsanSajjad is there a way to get a radio button inside of a group box? – Bradley William Elko Feb 23 '17 at 15:05
2

Well, your current code does that already. You only enumerate the collection until the first checked RadioButton. You could use LINQ to shorten the code as Ehsan has already shown.

But is there a forany statement

This sounds like you want to use seomething like this:

var checkedRadioButtons = this.Controls.OfType<RadioButton>().Where(cb => cb.Checked);

Now you can enumerate all checked radio-buttons:

foreach(RadioButton btn in checkedRadioButtons)
{
    // ..
}

or if you only want to know if there is at least one:

bool isAnyCheckedRadioButton = checkedRadioButtons.Any();

or you want to know how many are checked:

int countCheckedRadioButtons = checkedRadioButtons.Count();

or you want to store them in a collection:

List<RadioButton> checkedRadioButtonList = checkedRadioButtons.ToList();

Maybe that LINQ query using Enumerable.Where is what you understand as ForAny:

Community
  • 1
  • 1
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • Except the original code doesn't do anything with the collection outside of using it to loop x times. Other than that I think the solution is correct. – pinkfloydx33 Feb 23 '17 at 14:06
  • I can't get the `bool` to work. I think it's because my radio buttons are stored in a panel. How do I fix that? – Bradley William Elko Feb 23 '17 at 15:22
  • @BradleyWilliamElko: even better, then you need to use: `panel.Controls.OfType() ...` instead of `this.Controls.OfType...` – Tim Schmelter Feb 23 '17 at 15:27
  • @TimSchmelter I figured it out, but I don't understand why the form won't detect it? It still is in the form. – Bradley William Elko Feb 23 '17 at 15:28
  • @BradleyWilliamElko: because `OfType` is an extension method that filters the given sequence by the type. It does not know that a control could contain child controls. The panel is such a container control which contains child controls, your RadioButtons. So you will never find controls that are nested in others(recursively). If you use [`Controls.Find(name, true)`](https://msdn.microsoft.com/en-us/library/system.windows.forms.control.controlcollection.find(v=vs.110).aspx) you will find controls by name even if they are sitting in another child controls if you pass `true` as second argument. – Tim Schmelter Feb 23 '17 at 15:35
  • But you can't use this method because it is not a type filter. I'd prefer using the correct container control, so `panel.OfType...`. That's also safer since there could be `RadioButtons` in other container-controls which you don't want to process. If you really needed a recursive approach you could use [this one](http://stackoverflow.com/a/2209896/284240). Then this would also find your RB's: `var q = Controls.FlattenChildren().OfType()...` – Tim Schmelter Feb 23 '17 at 15:37
  • @TimSchmelter Alright. Thank you for the clarification. – Bradley William Elko Feb 23 '17 at 15:39
-1

You could create your own extension method to do it - that's all ForEach, Select etc. are.

public static class EnumerableExtensions
{
    public static void ForFirst<T>(this IEnumerable<T> arr, Func<T, bool> predicate, Action<T> action)
    {
        using (var enumerator = arr.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                if (predicate(enumerator.Current))
                {
                    action(enumerator.Current);
                    return;
                }
            }
        }
    }
}

Usage:

this.Controls.OfType<RadioButton>()
    .ForFirst(
        rb => rb.Checked, 
        rb => MessageBox.Show("Hi from " + rb.ToString()));

You can use First(), Where(), Any() I have just offered a different but valid solution. This is solution is flexible, and a one liner...

Callum Linington
  • 14,213
  • 12
  • 75
  • 154
  • 2
    Might I suggest using `First` instead (or alternatively `FirstOrDefault`)? – Abion47 Feb 23 '17 at 13:55
  • No need for `rb as RadioButton` the `OfType` should already do that for you. And I agree with abion47 might as well call First – pinkfloydx33 Feb 23 '17 at 14:09
  • This looks very complex. I don't know if I can use or understand this. – Bradley William Elko Feb 23 '17 at 14:36
  • It is straightforward, all you're doing is getting the enumerator from the enumerable (the thing that actually iterates the list), then you're checking if you can move to the next element (which if it succeeds will change current) then it calls the predicate (the function that checks a condition) if that succeeds it will call the action (the function where you've defined what to do), and then returns. – Callum Linington Feb 23 '17 at 14:38
  • @CallumLinington I'm not going to doubt you on whether or not this works, but I'm not at all experienced with `static class` and `static void`. I don't know how to evaluate this code (given you didn't do it for me) and understand what is going on or how to fix it (if there was an error). – Bradley William Elko Feb 23 '17 at 14:48
  • Fair points, I created this in Linqpad first, so checked it against ints (the nice thing about this is that it is generic). The static stuff is just making it an extension method - static only means can't be instantiated, so not an instance method – Callum Linington Feb 23 '17 at 14:52
  • The static stuff is *not* what makes it an extension method. That would be the `this` before the first parameter. Without it, this would just be a regular static method. – Abion47 Feb 23 '17 at 15:37
  • I meant that the static and this is required to make it an extension method – Callum Linington Feb 23 '17 at 15:44