A quick look at the todo list revealed: today is the day to add the following new functionality to my Delphi application: allow the user to change the visibility of some user interface elements on a one-could-say very complex form. There are several controls on the form some users could find to be “too advanced” and they basically will never need em. Naturally, users would prefer such elements to be made invisible to make their final user interface experience less complex (messy) – so that they can better focus on those elements that are important to them.
The first idea is simple: let’s provide an enumeration listing those controls that can be hidden by the user. Let’s group those in a set type variable and expose as a property. Add (inside the configuration screen) a list of checkboxes allowing the user to specify what elements he/she wants to see and what should be hidden. All set, let’s do some coding…
In this application I’m saving all application / user specific configuration settings in an INI file. The TIniFile exposes various WriteSomeValueType procedures. There’s no WriteSet. Oh, so the question to solve before we do any coding: “how to preserve (save/load) set type property value in an INI (or in a database) ”
So, given the above my implementation (simplified for the purposes of this article, obviously) looks like:
type TUIElement = (uiButton, uiCheckBox, uiRadioButton, uiPanel, uiGroupBox, uiEdit); TUIElements = set of TUIElement; … property VisibleUIElements : TUIElements read fUIElements;
I will allow the user to specify if the listed elements (button, checkbox, radio button, …) are visible on the form.
If the user specifies (for example) the following for the VisibleUIElements = [uiButton, uiRadioButton, uiGroupBox], the button is then made visible by simply:
Button1.Visible := uiButton in VisibleUIElements;
But, how do I get the value of VisibleUIElements I can store an in INI file? How do I get, for example, the integer value of VisibleUIElements ?
The first idea is: flags / bits. Each enumeration value is a flag which can be on or off to say if the enumeration value is or is not in the set (and therefore the corresponding control visible or not on the form).
Here’s therefore how to calculate the integer representation (value) of the VisibleUIElements property value:
function VisibleUIElementsToInteger: integer; var ui : TUIElement; begin result := 0; for ui in [Low(TUIElement) .. High(TUIElement)] do if ui IN VisibleUIElements then result := result + 1 shl Integer(ui); end;
If VisibleUIElements = [uiButton] the result of this function would be “1”, if VisibleUIElements = [uiButton, uiRadioButton] the result is “5”. If all enumeration values are in the set, VisibleUIElementsToInteger would return 63. In fact for any possible VisibleUIElements combination the function would return a unique value. What’s that magic?
If each of the possible enumeration values would have the actual following values (note: each value is assigned the next greater power of 2):
uiButtonInt = 1, uiCheckBoxInt = 2, uiRadioButtonInt = 4, uiPanelInt = 8, uiGroupBoxInt = 16, uiEditInt = 32
I could do bitwise operations like
Button1.Visible := uiButton AND VisibleUIElementsToInteger <> 0;
If you are not 100% sure what is “shl” and “bitwise”, here’s some quick help:
A decimal value of 0 is “0000” in the binary system,
A decimal value of 1 is “0001” in the binary system,
A decimal value of 2 is “0010” in the binary system,
A decimal value of 4 is “0100” in the binary system,
A decimal value of 8 is “1000” in the binary system, and so on…
Note how digit “1” moves from the right side one step left for each next greater power of 2.
Now, doing some bitwise operations:
How do I know if 4 is “in” 13?
Well, here’s how: 13 decimal is “1101” binary. 4 AND 13 is “0100” AND “1101” and this results in “0100” which is not 0 – and therefore 4 is in 13.
How do I know if 2 is “in” 13?
Well, here’s how: 13 decimal is “1101” binary. 2 AND 13 is “0010” AND “1101” and this results in “0000” which is 0 – and therefore 2 is not in 13.
Also note how 13 is 8+4+1 – and that’s a unique calculation to have 13 from powers of 2.
I could therefore use the VisibleUIElementsToInteger function to convert the VisibleUIElements set type value to an integer and save to my INI file.
To convert back from the integer value to my set value I can use:
procedure VisibleUIElementsFromInteger(const vuiInt : integer); var ui : TUIElement; begin fVisibleUIElements := ; for ui in [Low(TUIElement) .. High(TUIElement)] do if 1 shl Integer(ui) AND vuiInt <> 0 then Include(fVisibleUIElements, ui); end;
And that’s it. Now you can easily store set type property values to/from an INI file, or even an integer database field.
Here’s the saving part (when form closes for example):
procedure SaveToIni; var uiInt : integer; begin with TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini')) do try WriteInteger('config', 'VisibleUIElements', VisibleUIElementsToInteger); finally Free; end; end;
Here’s the loading part (when the form is displayed to the user):
procedure LoadFromIni; var uiInt : integer; begin with TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini')) do try uiInt := ReadInteger('config', 'VisibleUIElements', 0); VisibleUIElementsFromInteger(uiInt); //UpdateUIForVisibility; finally Free; end; end;
Note: once you rely on the above, you must not change the order of the elements as they are defined in the TUIElement enumeration – as this will change their boolean values. Also note that by default, the ordinalities of enumerated values start from 0 and follow the sequence in which their identifiers are listed in the type declaration – this is why uiButton has a value of 0, uiCheckBox is 1, and so on.
You can download a sample project and give it a try.
Another Way To Directly Convert A Set to Integer and Back
Just while I was finishing my code, I’ve found a post on stackoverflow where a Delphi developer provides a smart way to directly typecast set value to an integer and back (if your set will have up to 32 elements).
Anyhow, have you had a need for something like this? What did you do?