3

I have a group of check boxes, to be precise there are 3 boxes. It works for me when using if statement but I wonder there is a way to loop through check boxes and assign enumeration values when a box is checked or more.

The code looks like this:

if (chkTomato.Checked && !chkLettuce.Checked && !chkCarrot.Checked)
{
    cart.VegChosen = Veggies.Tomato;
}
else if (!chkTomato.Checked && chkLecctuce.Checked && !chkCarrot.Checked)
{
    cart.VegChosen = Veggies.Lecctuce;
}
else if (!chkTomato.Checked && !chkLecctuce.Checked && chkCarrot.Checked)
{
    cart.VegChosen = Veggies.Carrot;
}
else if (chkTomato.Checked && chkLettuce.Checked && chkCarrot.Checked)
{
    cart.VegChosen = Veggies.All;
}
else if (chkTomato.Checked && chkLettuce.Checked && !chkCarrot.Checked)
{
    cart.VegChosen = Veggies.TomatoAndLettuce;
}
else if (chkTomato.Checked && !chkLettuce.Checked && chkCarrot.Checked)
{
    cart.VegChosen = Veggies.TomatoAndCarrot;
}
else if (!chkTomato.Checked && chkLettuce.Checked && chkCarrot.Checked)
{
    cart.VegChosen = Veggies.LettuceAndCarrot;
}
else
{
    cart.VegChosen = Veggies.None;
}

I want to find out a way to loop it in case there are more than just 3 check boxes, the if statement would be very long.

Thank you!

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
knguyen158
  • 33
  • 4

3 Answers3

5

While this doesn't use loops, I expect this is what you're trying to achieve. Assuming your enum is declared like this:

[Flags]
public enum Veggies
{
    None = 0,
    Tomato = 1,
    Lettuce = 2,
    Carrot = 4,
    TomatoAndLettuce = Tomato | Lettuce,
    TomatoAndCarrot = Tomato | Carrot,
    LettuceAndCarrot = Lettuce | Carrot,
    All = Tomato | Lettuce | Carrot
}

Then you should be able to use a similar bitwise approach to assign values:

Veggies selectedVeggies = Veggies.None;
if (chkTomato.Checked)
{
    selectedVeggies = selectedVeggies | Veggies.Tomato;
}

if (chkLettuce.Checked)
{
    selectedVeggies = selectedVeggies | Veggies.Lettuce;
}

if (chkCarrot.Checked)
{
    selectedVeggies = selectedVeggies | Veggies.Carrot;
}

cart.VegChosen = selectedVeggies;

The net result of this will be the same as your current set of if statements. The reason we use 1, 2, 4, etc. for the enum values is because there isn't overlap between them when rendered in binary (1 is 001, 2 is 010, 4 is 100, etc.) so that specific bit can only identify that one enum value.

Also note that declarations such as TomatoAndLettuce and TomatoAndCarrot are perhaps also unnecessary, since you can use Enum.HasFlag().

For example:


var selectedVeggies = Veggies.Tomato | Veggies.Carrot;
// or var selectedVeggies = Veggies.TomatoAndCarrot; // effectively the same as above

if (selectedVeggies.HasFlag(Veggies.Tomato))
{
    cart.Add(new Tomato());
}

if (selectedVeggies.HasFlag(Veggies.Carrot))
{
    cart.Add(new Carrot());
}

// cart ends up with a Tomato and a Carrot

Further reading: What does the bitwise or | operator do?

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
  • 2
    Would it not be useful in this circumstance to use `1 << 0`, `1 << 1`, and `1 << 2` instead of `1`, `2`, and `4`? It gives a more clear indication of what's happening and what its purpose is (in my opinion at least). – Prime Mar 26 '21 at 05:45
  • 2
    @AlphaDelta To be honest, I was just following the style used in Microsoft's own examples. I think your option is also a good way (probably better way) of doing it, though perhaps bitwise shifts would require an additional explanation. – ProgrammingLlama Mar 26 '21 at 05:47
  • @AlphaDelta I would suggest undeleting your answer, as it does provide more insight into the binary side of things, so it's also useful. I'd give it my upvote at least. :) – ProgrammingLlama Mar 26 '21 at 05:50
  • Agree with llama, sorry.. bitwise shifts are very uncommon operators, so the next thing you have to do when you see it is google what it is. In order of "oh that's obvious" I personally think it goes "number sequence of 1, 2, 4, 8", "powers of two eg 2^0, 2^1, 2^2", "bitshifts" – Caius Jard Mar 26 '21 at 05:54
  • @Llama Sure thing, I've undeleted my answer – Prime Mar 26 '21 at 06:05
3

Create the enum like so:

enum Veggies {
    Tomato = 1 << 0,
    Lettuce = 1 << 1,
    Carrot = 1 << 2,
    All = Tomato | Lettuce | Carrot

}

This makes the value of Veggies.Tomato = 1 which is 0000 0001 in bits, Veggies.Lettuce = 2 which is 0000 0010, and Veggies.Carrot = 4 which is 0000 0100.

It is important to have the enum values as bit-shifted 1's (powers of 2) so that you can combine two enum values later as an int like I've done with Veggies.All, which is 0000 0111.

Change VegChosen to an int, then simply change your code to something like this to bitwise-or the enum value into the VegChosen int:

cart.VegChosen = 0;

if(chkTomato.Checked) cart.VegChosen |= Veggies.Tomato;
if(chkLettuce.Checked) cart.VegChosen |= Veggies.Lettuce;
if(chkCarrot.Checked) cart.VegChosen |= Veggies.Carrot;

Later if you want to test what veggies were chosen from cart.VegChosen you can bitwise-and with one of the enum values and check if it's 0 like so:

if((cart.VegChosen & Veggies.Carrot) != 0)
{
    //... cart.VegChosen contains Veggies.Carrot
}
if((cart.VegChosen & Veggies.Lettuce) != 0)
{
    //... cart.VegChosen contains Veggies.Carrot
}
//etc

This is typically ~50% faster/more performant than Enum.HasFlag() because HasFlag() contains various sanity checks, but if you're not programming specifically for performance it's better to use what is easier to use and read and in that respect I would recommend Llama's answer.

Prime
  • 2,410
  • 1
  • 20
  • 35
3

Once you e implemented Llamas answer of making a flags enum you can put all your checkboxes in a groupbox and do this:

var veg = Veggies.None:
groupbox.Controls
  .OfType<CheckBox>()
  .Where(c => c.Checked)
  .ToList()
  .ForEach(c => veg |= Enum.Parse<Veggies>(c.Name[3..]));

Then all you have to do is add more enum members and add more checkboxes where the checkbox name is like "chkXXX" where xxx is the name of the enum member

This is a looping construct: it gets all the controls on the groupbox and filters to only those of type checkbox, then reduces it to only checked checkboxes. It turns this into a list (so we can foreach it, because foreach is a list thing not a LINQ thing). Foreach will visit every checkbox and ask if it is checked, it will OR the existing flags enum value with the result of parsing the checkbox name (dropping the first 3 chars) to a Veggies. At the end of it your veg variable will represent all the checkboxes that were Checked

Note that using ranges and even Enum.Parse<T> required a fairly modern version of c# - if you're running a version that doesn't support ranges you can use Substring(3). You should make sure that all your checkbox names are well aligned with your enum names. If you want to get really trick you could create your checkboxes dynamically by enumerating the Enum and putting the enum value as the checkbox Tag when you add the control to the form (dynamically, in incrementing positions) this way your form will just adapt automatically to however many enum members you have

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • You could also use `.Where(c => c.Checked).Select(c => ...).Aggregate(e => ...)`, but I think a plain boring `foreach(...)` loop is easier to read. – Jeremy Lakeman Mar 26 '21 at 06:10
  • I would also consider defining a `Dictionary` somewhere and using that to drive the process instead of magic control naming. – Jeremy Lakeman Mar 26 '21 at 06:11
  • Indeed, or stuffing the the Veggies value in the Tag.. Seems reasonable to use name though, given that enum members have a name, checkboxes have a name.. And to some extent a Controls collection is like a `Dictionary>` but dems be some very worthy points, thanks Jeremy – Caius Jard Mar 26 '21 at 06:14