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?
I generally do this by 2 means :
Quick and Dirty : using Tag property to store which control can be hidden, and saving names of controls that must be hidden
Long and Dirty ; use a DB to store controls, user profiles , rights (what is the access level of this control ,) etc ….
Forget about INI files when settings shouldn’t be user-configurable outside the application. There are better formats to store application-specific data users should never modify manually. The overuse of INI files is another bad habit of Delphi developers, most of whom looks no longer able to think outside strings and textual data – even when they are the wrong storage format.
@KMorath. If I do not want to code against any database like storage, what would you propose for a simple way of storing configuration settings?
Good old plain binary files? Remember when Turbo Pascal had excellent streaming capabilities and Load/Store methods for classes? What about a nice Delphi TStream and a TReader/TWriter? No need for databases here, but text files are made for humans, not applications. If you have a configuration file that a human should be able to modify outside the application in a simple text editor, INI files or whatever alike are a good choice. They are IMHO a very bad choice for configuration files only the application reads/writes and no human should ever modify outside it, especially if modifying them could impact application behaviour in ways you can’t think of while writing the application.
Hm … I’ve tried all approaches across times. Depending on the project I use different techniques. INI files are still most frequently used from my side – as long as I’m aware those can be messed up by human hands and therefore no critical values stored inside.
>Good old plain binary files? Remember when Turbo Pascal had excellent streaming
>capabilities and Load/Store methods for classes? What about a nice Delphi TStream >and a TReader/TWriter?
On the downside, to the best of my knowledge (and I could be wrong) Borland et al never documented its binary format, which makes it a somewhat dangerous format to store data in (I’ve seen questions on the net from people using other languages struggling to load a Delphi binary file.)
> No need for databases here, but text files are made for humans, not applications.
And programs should be made for humans too. In Eric S. Raymond’s “17 Unix Rules” he wrote:
>Rule of Clarity: Developers should write programs as if the most important
>communication is to the developer, including him- or herself, who will read and
>maintain the program rather than the computer. This rule aims to make code
>readable and comprehensible for whomever works on the code in future.
In Mike Gancarz’s “The UNIX Philosophy” he explicitly wrote:
>5. Store data in flat text files.
>If you have a configuration file that a human should be able to modify outside the
>application in a simple text editor, INI files or whatever alike are a good choice.
Humans should be able to modify everything. That’s who the software is for. It also makes it easier for people to use your code in other applications or your program as part of a process chain. As Raymond put it,
>Rule of Diversity: Developers should design their programs to be flexible and open.
>This rule aims to make programs flexible, allowing them to be used in other ways than
>their developers intended.
> They are IMHO a very bad choice for configuration files only the application
>reads/writes and no human should ever modify outside it
How can any developer know always and for all time that a human will not need to modify a configuration file? This also extends to making class properties and methods private, but that’s another issue. One of the biggest obstacles I’ve found to making old software run on modern OSes is just this problem that many old programs don’t expose settings to users in a readable/modifiable fashion.
> especially if modifying them could impact application behaviour in ways you can’t
>think of while writing the application.
Binary files are really bad. The poses all kind of issues with portability and readability.
I really like to have the settings stored in an INI file so that it is perfectly readable by a human and easily changed outside of the application. For the rare cases where this is not desired, my own TIniFile derived class has the ability to digitally sign the INI file as a whole or only a subset of the keys. When the signature is wrong because user changed data manually, then the application refuse to work.
Ken, at least on Linux, almost every program stores its settings in a text-based configuration file (the only exception being some that store settings in an sqlite file), and obviously none are written in Delphi. While I believe there are a lot of bad habits of Delphi developers, I don’t believe this is one of them.
@Arnau: yes, this is what I mentioned as a possible solution at the end of the post.
@Žarko Gajić : thank you for your share
@Arnaud : can you share more on your solution ? Merci par avance 😉
This is dangerous and bad advice! What happens when you need to add, delete or replace a control and the enumeration changes? All your users will end up with an INI file pointing to the wrong elements in the enumeration. So you would need a version number in your INI file and to interpret the number according to the mapping in effect at the time. If you really, really have to store details of named attributes in an INI file, then you must bite the bullet and store the names themselves.
@Tim: I can to some degree agree with you. Please note that the main idea of this post is “how to convert set value to integer and back”. I’ve added a note that if decide to go this way, you must not change the order of elements in the enumeration (therefore you must not delete elements). You can add new enumeration elements though. If you store the names: what if the names change? Certainly, one needs to be careful when implementing any solution.
To store an enumeration, I always use RTTI to get name and store a list of names. You can then change the number of items in the enumeration – but not the names. And the result is much more readable.
The JVCL component TJvAppIniFileStorage has a WriteSet() method which seems to work along the lines Francois suggests – it takes a PTypeInfo param for the set being written. I tested it with your set, and it produces these results:
which feels a pretty robust approach.