Reputation: 53
I am writing a windows forms application that has 4 check boxes that can be checked at the same time. I want to check to see what boxes are checked and do specific logic based on which boxes are checked. The issue being that if I end up trying to write code to check each possible combination of the check boxes I would have to write a long list of if else statements, and that is without checking for if there is only one checked. Is there any more elegant way to check whether or not multiple check boxes are checked? or am I stuck using if-else logic?
//This is the type of code I am trying to avoid
//check all the conditions
//check1 is a check to add keys
//check2 is to create docs
//check3 is to create keys
//check4 is to reorder keys
//createKeys() creates keywords
//createDocs() creates documents
//addKeys() adds keywords
//reorderKeys reorders the keywords
if(check1 && check2 && check3 && check4){
createKeys();
createDocs();
addKeys();
reorderKeys();
}
else if(check1 && check2 && check3){
createKeys();
createDocs();
addKeys();
}
else if(check1 && check2 && check4){
createDocs();
addKeys();
reorderKeys();
}
else if(check1 && check2){
createDocs();
addKeys();
}
else if(check1){
addKeys();
}
else if(check2 && check3 && check4){
createKeys();
createDocs();
reorderKeys();
}
else if(check2 && check4){
createDocs();
reorderKeys();
}
else if(check2){
createDocs();
}
else if(check3 && check4){
createKeys();
reorderKeys();
}
else if(check3){
createKeys();
}
else if(check4){
reorderKeys();
}
Code update to be more specific
Upvotes: 0
Views: 1650
Reputation: 32223
(I know you already have an answer, consider this option as a possible alternative in another occasion).
You can associate a specific Action with a Control or a collection of controls, then perform all the associated actions when it's required, triggering the actions in a sequence. A specific sequence, if required, as it is here.
For example, your createKeys()
method has a higher priority when selected, createDocs()
one level less, but it must be executed before the others when those are active too and so on.
So, using a manager class, you can associate all the actions (method calls) to the state of a Control, define the Action priority, then perform all the actions in one call when a conditon changes (a CheckBox is selected, a Button is clicked etc.).
Using the ActionManager
class, you can associate Actions to Controls, add them to the class then call PerformActions()
to execute all the active method:
ActionManager.Actions.AddRange(new[] {
new ActionManager.ActionControl(() => CreateKeys(), this.chkCreateKeys, 1),
new ActionManager.ActionControl(() => CreateDocs(), this.chkCreateDocs, 2),
new ActionManager.ActionControl(() => AddKeys(), this.chkAddKeys, 3),
new ActionManager.ActionControl(() => ReorderKeys(), this.chkReorderKeys, 4)
});
When needed, just call:
ActionManager.PerformActions();
and all methods are executed.
This is how it performs, on a CheckedChanged
event (could be a Button click or anything else):
ActionManager
class:
public class ActionManager
{
public static List<ActionControl> Actions { get; set; } = new List<ActionControl>();
public static void PerformActions() {
var sequence = Actions.Where(c => c.Control.Checked).OrderBy(a => a.ActionPriority);
foreach (var action in sequence) {
action.Action?.Invoke();
}
}
public class ActionControl
{
public ActionControl(Action action, CheckBox control, int priority) {
this.Action = action;
this.Control = control;
this.ActionPriority = priority;
}
public Action Action { get; }
public CheckBox Control { get; }
internal int ActionPriority { get; }
}
}
Upvotes: 2
Reputation: 7213
For 4 checkboxes you can do something like this:
Add them into array
bool[] checkboxes = { check1, check2, check3, check4 };
then select their 10 power by its indices, so you can use their Sum
to get unique values for each one:
var selected = checkboxes
.Reverse() // reversing to get result with correct order
.Select((s, i) => new { IsChecked = s, Index = Math.Pow(10, i) })
.Where(w => w.IsChecked)
.Sum(s => s.Index);
so you will get 1100
for true, true, false, false
, or 101
for false, true, false, true
, etc.
and you can have Dictionary<double, Action>
for all possibilities, like:
Dictionary<double, Action> methods = new Dictionary<double, Action>
{
{ 0000, YourMethod },
{ 0001, YourMethod1 },
{ 0010, YourMethod2 },
{ 0011, YourMethod3 },
{ 0100, YourMethod4 },
{ 0101, YourMethod5 },
{ 0110, YourMethod6 },
{ 0111, YourMethod7 },
{ 1000, YourMethod8 },
{ 1001, YourMethod9 },
{ 1010, YourMethod10 },
{ 1011, YourMethod11 },
{ 1100, YourMethod12 },
{ 1101, YourMethod13 },
{ 1110, YourMethod14 },
{ 1111, YourMethod15 },
};
and call it like this:
methods[selected]();
Upvotes: 2
Reputation: 7054
An alternative to if/else
would be a method table. You can create a dictionary and put all code as Action
var methodTable = new Dictionary<(bool, bool, bool, bool), Action>
{
[(false, false, false, true)] = () => { /* do something */ },
[(false, false, true, false)] = () => { /* do something else */ },
[(false, false, true, true)] = SomeClassMethod,
// and so on
};
Now you can invoke code from if/else
block with single line:
methodTable[(check1, check2, check3, check4)].Invoke();
EDIT
If there is some combination of checkboxes you do not want to process you have to options:
Add empty handlers to methodTable
like:
[(false, false, false, true)] = () => {}, // do nothing
Or (which is better for me) use TryGetValue
method:
if (methodTable.TryGetValue((check1, check2, check3, check4), out var method))
method.Invoke();
Upvotes: 7
Reputation: 34421
Use an enumeration :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication46
{
public partial class Form1 : Form
{
enum CheckBoxEnum
{
CHECKBOXNONE = 0,
CHECKBOX1 = 1,
CHECKBOX2 = 2,
CHECKBOX3 = 4,
CHECKBOX4 = 8,
}
List<CheckBox> checkboxes;
CheckBoxEnum boxEnum = CheckBoxEnum.CHECKBOXNONE;
public Form1()
{
InitializeComponent();
checkboxes = new List<CheckBox>() { checkBox1, checkBox2, checkBox3, checkBox4 };
foreach (CheckBox box in checkboxes)
{
box.Click += new EventHandler(box_Click);
}
switch (boxEnum)
{
case CheckBoxEnum.CHECKBOX1 | CheckBoxEnum.CHECKBOX3 :
break;
}
}
private void box_Click(object sender, EventArgs e)
{
CheckBox box = sender as CheckBox;
if (box.Checked)
{
boxEnum |= (CheckBoxEnum)(1 << checkboxes.IndexOf(box));
}
else
{
boxEnum &= ~(CheckBoxEnum)(1 << checkboxes.IndexOf(box));
}
}
}
}
Upvotes: 2
Reputation: 10563
We'll be using sets of numbers representing which checkboxes are set, so {1, 3}
means check1 && check3
.
If all the logic is completely separate, i.e. there is no connection between actions taken for {a_1, a_2, ..., a_n}
and {a_1, a_2, ..., a_n, x}
for any a_1, ..., a_n
and x
, then obviously you need to have 2^n
separate procedures for each combination. That means that either way we have to branch into 2^n
different code flows. So the question boils down to whether we can decide which set to use in a less code consuming way.
Obviously, you need to somehow wire up behaviour to different sets. That means that no matter what we do, under the assumption that we can have 2^n
completely unrelated procedures, one for each set, you still have to have at least 2^n
statements that will link a particular set to a particular procedure. So the short answer is no, even if you got rid of if/else
statements, for example encoding the checks as sequences of bits and then mapping these sequences to procedures, you'd get rid of the logic here, but somewhere else in the code you'd have to have mappings like:
Dictionary<string, Action> logic = new Dictionary<int[], Action>();
logic["{1}"] = DoStuffFor1;
logic["{2}"] = DoStuffFor2;
...
logic["{N}"] = DoStuffForN;
logic["{1, 2}"] = DoStuffFor12;
logic["{1, 3}"] = DoStuffFor13;
...
var checkboxSet = Encode(check1, check2, ..., checkN); // Implementation of Encode should be obvious.
logic[checkboxSet].Invoke();
so still 2^n
statements. Thus, for a general case there's no way of decreasing the number of lines you write, but you might find a map of delegates more elegant than a swarm of if/else
statements.
If, however, the logic for different sets of checkboxes is interconnected somehow, which will be the case in any reasonable real life application, you could probably collapse the if
statements to shorten the code. But without more details about the actual logic of the program it's impossible to tell.
EDIT:
Actually, now that I come to think of it, you could have a very convoluted way of resolving a method name at runtime using reflection, and by using a naming convention for your methods so that the names for all handlers are nonambiguous (for example a method handling {1, 42, 100}
would be named DoStuffFor_1_42_100
etc.) you could fetch a method with name equivalent to the checked checkboxes and invoke it. It would still require you to write the 2^n
procedures, though. Also I think we can unanimously agree that it's a terrible idea to use reflection for this thing and rely on a naming convention for the code to work.
Upvotes: 1