helplessprogrammer
helplessprogrammer

Reputation: 53

Is there a more elegant way to write logic for multiple checkboxes being checked other than multiple if statements?

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

Answers (5)

Jimi
Jimi

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):

CheckBox Manager class

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

SᴇM
SᴇM

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

Aleks Andreev
Aleks Andreev

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

jdweng
jdweng

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

V0ldek
V0ldek

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

Related Questions