Click here to Skip to main content
12,253,990 members (64,150 online)
Click here to Skip to main content
Add your own
alternative version

Stats

17K views
361 downloads
14 bookmarked
Posted

Select multiple enum values with EnumFlagsSelector

, 26 Nov 2015 CPOL
Rate this:
Please Sign up or sign in to vote.
EnumFlagsSelector control allows to select multiple values from all possible values of enum

In my previous article “Select enum value with EnumSelector” I have discussed how to use the custom control EnumSelector to select one constant from all possible constants of enum.

Sometimes, you may want the user to select multiple values from all possible values of enum. Although one can use multiple EnumSelector instances to achieve this goal, a better solution is to use EnumFlagsSelector - a listbox which display each possible value of enum, decorated with Flags Attribute, with a check mark next to it.

Using the Control

 

EnumFlagsSelector control allows to select multiple values from all possible values of enum type. To use the control, You should inherit from EnumFlagsSelector with enum type which is decorated with Flags attribute. Each enum value can be checked to be included in the selection and unchecked to be removed from the selection.

The Selected property holds the current selected values. This property is always synchronized with the user interface. In other words, When the user check or uncheck enum values, the property value is updated to new selection immediately and when the property is updated in the program - the user interface is updated to reflect the change.

When the user change its selection by checking or unchecking enum values SelectionChanged event is raised. You can use the Selected property of the EnumFlagsSelector which raised this event, to find out the new selected value.

By default, the names of the enum values will be displayed in the user interface. While this is very convenient, sometimes this is not suitable (For example, a more detailed name should be displayed, the jargon used to name the values is unfamiliar to the target audience or the values needs to be translated to another language). In this case, You can set AllowFormat property and override OnFormat method. This method accepts the enum value to be displayed and should return a string representation of the value which will be displayed on the user interface.

The Flags Attribute

The Flags attribute which can be used to decorate enum, notify the compiler that we want that a variable of this enum should be treated as a bit field. Unlike the default enum semantics which allows us to store only one enum value (or list of mutually exclusive enum values) in a variable, The Flags attribute allows us to store multiple enum values in a variable and therefore can be used to store enum values that might occur in combination.

Here Some guidelines when creating this type of enum:

  • We can use the bit manipulation operators (& for AND , | for OR , ^ for EXCLUSIVE OR) on those values.
  • We should define enum values in powers of 2, that is, 1 (0001b) , 2 (0010b), 4 (0100b), 8 (1000b), and so on. This strategy will give the flags semantics by ensuring that those values can be freely combined using the bitwise OR operation and then be tested for existence using AND operation.
  • You can also define a special value for commonly used combinations by using OR operator on previous declared values.
  • You can also define value 0 as None - This special combination means that no flag is set.

The following demonstrate those guidelines

[Flags]
enum Sample {
    // special value - No flag set
    N0 = 0,        
    
    // The flags - enum values in powers of 2
    F0 = 1,        // 001b , (F0 & F2) = N0 , (F0 & F1) = N0 , (F0 & F0) = F0
    F1 = 2,        // 010b , (F1 & F2) = N0 , (F1 & F1) = F1 , (F0 & F0) = N0
    F2 = 4,        // 100b , (F2 & F2) = F2 , (F2 & F1) = N0 , (F2 & F0) = N0
        
    // commonly used combinations of the flags
    C1 = F1 | F0 , // 011b , (C1 & F2) = N0 , (C1 & F1) = F1 , (C1 & F0) = F0
    C2 = F2 | F0 , // 101b , (C2 & F2) = F2 , (C2 & F1) = N0 , (C2 & F0) = F0
    C3 = F2 | F1 , // 110b , (C3 & F2) = F2 , (C3 & F1) = F1 , (C3 & F0) = N0
    C4 = C1 | F2 , // 111b , (C4 & F2) = F2 , (C4 & F1) = F1 , (C4 & F0) = F0 , (C4 & C1) = C1
}

The FlagsHelper encapsulates common operations on bit fields:

public static class FlagsHelper {
  // Find whether the setted bits in checkBits are also setted in flags variable
  public static bool IsSet(this int flags,int checkBits) {
    return ((flags & checkBits) == checkBits);
  }
  //-------------------------------------------------------------------------
  // Set the setted bits in setBits in flags variable
  public static void Set(ref int flags,int setBits) {
    flags |= setBits;
  }
  //-------------------------------------------------------------------------
  // Clear the setted bits clearBits in flags variable
  public static void Clear(ref int flags,int clearBits) {
    flags &= (~clearBits);
  }
  //-------------------------------------------------------------------------
  // Clear all bits in flags variable
  public static void Clear(ref int flags) {
    flags = 0;
  }
}

The following program show how can we use this class

static void Main(string[] args) {
  int xx = 0;                 ; WriteBitField(xx); // 8 => False , 4 => False, 2 => False, 1 => False
  FlagsHelper.Set  (ref xx,8) ; WriteBitField(xx); // 8 =>  True , 4 => False, 2 => False, 1 => False
  FlagsHelper.Set  (ref xx,4) ; WriteBitField(xx); // 8 =>  True , 4 =>  True, 2 => False, 1 => False
  FlagsHelper.Set  (ref xx,1) ; WriteBitField(xx); // 8 =>  True , 4 =>  True, 2 => False, 1 =>  True
  FlagsHelper.Clear(ref xx,4) ; WriteBitField(xx); // 8 =>  True , 4 => False, 2 => False, 1 =>  True
  FlagsHelper.Clear(ref xx,1) ; WriteBitField(xx); // 8 =>  True , 4 => False, 2 => False, 1 => False
  FlagsHelper.Set  (ref xx,2) ; WriteBitField(xx); // 8 =>  True , 4 => False, 2 =>  True, 1 => False
  FlagsHelper.Clear(ref xx  ) ; WriteBitField(xx); // 8 => False , 4 => False, 2 => False, 1 => False
}

public static void WriteBitField(int xx) {
  System.Console.WriteLine("8 => {0,5} , 4 => {1,5}, 2 => {2,5}, 1 => {3,5}"
    ,xx.IsSet(8) , xx.IsSet(4), xx.IsSet(2), xx.IsSet(1));
}

The Demo program

The demo program allows the user to display or hide 4 rectangles with different colors (red, green, yellow, blue). The user can also display or hide all rectangles and can display and hide only the red and the blue rectangles.

To implement this feature the we create a new enum DisplayFlags with Flags attribute and DisplayFlagsSelector control.
We define DisplayFlags with DisplayRed, DisplayGreen, DisplayYellow and DisplayBlue enum values. Please note that those values are powers of 2.
We also define DisplayAll and DispleyRedBlue as combination of those flags.

[Flags]
public enum DisplayFlags {
    DisplayRed     = 0x0001,
    DisplayGreen   = 0x0002,
    DisplayYellow  = 0x0004,
    DisplayBlue    = 0x0008,
    DisplayNone    = 0,
    DispleyRedBlue = DisplayRed | DisplayBlue,
    DisplayAll     = DisplayRed | DisplayGreen | DisplayYellow | DisplayBlue,
}
public partial class DisplayFlagsSelector : EnumFlagsSelector<DisplayFlags>
}

When the user update its selection, we update the Visible property of the PictureBoxes.

private void winDisplayFlags_SelectionChanged(object sender,EventArgs ee) {
  winBoxBlue.Visible   = ((int)winDisplayFlags.Selected).IsSet((int)DisplayFlags.DisplayBlue);
  winBoxRed.Visible    = ((int)winDisplayFlags.Selected).IsSet((int)DisplayFlags.DisplayRed);
  winBoxGreen.Visible  = ((int)winDisplayFlags.Selected).IsSet((int)DisplayFlags.DisplayGreen);
  winBoxYellow.Visible = ((int)winDisplayFlags.Selected).IsSet((int)DisplayFlags.DisplayYellow);
}

About the control implementation

Creating the basic control

To create the control, choose in the project context menu Add > New Item ... > Visual C# Items > Windows Forms > UserControl. Type the name "EnumFlagsSelector.cs" and click Add. A new design screen of the new user control appears. Drag a CheckedListBox control into the design view and update its properties (Name) to winCheckList and Dock to Fill.

Adding enum values

As we want this control to be display enum values we make it generic with the enum type TT

public partial class EnumFlagsSelector<TT> : UserControl {

In the constructor we fill winDropDown with the enum values with help of Enum.GetValues

public EnumFlagsSelector() {
  InitializeComponent();
  foreach (object oo in Enum.GetValues(typeof(TT))) {
    if ((int)oo != 0) {
      winCheckList.Items.Add(oo,false);
    }
  }
}

SelectionChanged event

The SelectionChanged event is raised when the selection is changed. The UpdateSelected will raise this event when the _selected field changed.

public event EventHandler SelectionChanged;
private void UpdateSelected(int selected) {
  if (selected == _selected ) {
    return;
  }
  _selected = selected;
  if (SelectionChanged != null) {
    SelectionChanged(this, new EventArgs());
  }
}

The Selected property

The Selected property should be synchronized with the user interface:

  • In the property getter will return the _selected field
  • In the property setter - we will update the _selected field, Raise SelectionChanged event. We also updates the user interface. for each checkbox - we will check it if checkbox's value is setted or uncheck it otherwise.
public TT Selected {
  get {
    return (TT)((object)_selected);
  }
  set {
    int valueInt = (int)((object)value);
    if (_selected == valueInt) {
      return;
    }
    _ignoreCheckEvent = true; // No Item check events while check/unchecking
    for (int ii = 0; ii < winCheckList.Items.Count; ii++) {
      winCheckList.SetItemChecked(ii, FlagsHelper.IsSet(valueInt ,(int)winCheckList.Items[ii]));
    }
    _ignoreCheckEvent = false;
    UpdateSelected(valueInt);
  }
}
private bool _ignoreCheckEvent;
private int _selected = 0;

Synchronize with user interface

To ensure that Selected property getter will always return the selected values in the user interface, we will add a new handler winCheckList_ItemCheck to winCheckList's ItemCheck event using the properties window.

In this handler we will update the field to reflect the GUI state and raise our SelectionChanged event.

private void winCheckList_ItemCheck(object sender,ItemCheckEventArgs ee) {
  if (_ignoreCheckEvent) { // will be true when called from selected property setter
    return;
  }
  int selected = 0;
  if (ee.NewValue == CheckState.Checked) {
    FlagsHelper.Set(ref selected, (int)winCheckList.Items[ee.Index]);
  }
  for (int kk = 0; kk < winCheckList.Items.Count; kk++) {
    if ( ee.Index != kk && winCheckList.GetItemCheckState(kk) == CheckState.Checked) {
      FlagsHelper.Set(ref selected, (int)winCheckList.Items[kk]);
    }
  }
  UpdateSelected(selected);
}

Update display strings

In order to update names of the values, we add the OnFormat virtual method. We also add new handler winCheckList_Format to winCheckList's Format event which allows to us to convert a value to its associated display string.

public virtual string OnFormat(TT value) {
  return value.ToString();
}
private void winCheckList_Format(object sender,ListControlConvertEventArgs ee) {
  ee.Value = OnFormat((TT)ee.ListItem);
}

The AllowFormat property connect the handler to the event as requested

public bool AllowFormat {
  get {
    return _allowFormat;
  }
  set {
    if (value == _allowFormat) {
      return;
    }
    _allowFormat = value;
    if (value) {
      winCheckList.Format += winCheckList_Format;
    } else {
      winCheckList.Format -= winCheckList_Format;
    }
  }
}

History

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

napuzba
Israel Israel
I am kobi, a passionate software developer.
Actively seeking for new opportunities. Please feel free to contact me via my blog.
  • I'm working in C#, C++, PHP and Python
  • I'm building websites, mobile applications and desktop software.

You may also be interested in...

Comments and Discussions

 
QuestionHave you consider to post this as a tip? Pin
Nelek26-Nov-15 0:48
protectorNelek26-Nov-15 0:48 
BugEnumFlagsSelector doens't work properly with some enums Pin
Alexander Sharykin23-Oct-14 4:37
memberAlexander Sharykin23-Oct-14 4:37 
GeneralRe: EnumFlagsSelector doens't work properly with some enums Pin
Juergen Posny9-Nov-15 6:48
memberJuergen Posny9-Nov-15 6:48 
GeneralRe: EnumFlagsSelector doens't work properly with some enums Pin
Alexander Sharykin26-Nov-15 3:17
memberAlexander Sharykin26-Nov-15 3:17 
GeneralThe control was updated to support combinations of flags Pin
napuzba26-Nov-15 2:21
membernapuzba26-Nov-15 2:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160426.1 | Last Updated 26 Nov 2015
Article Copyright 2015 by napuzba
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid