Is there a easier way to define a Enum type based on a boolean value?
Asked Answered
E

3

6

Overview

Not entirely sure if the question is worded appropriately or not, but I previously asked this question which relates to this one: How do I correctly implement a Set in a class as a property?

I like to keep code as short, minimal and readible as possible, and this is where I think some code could be written better but I am running into problems.


An example first of 2 ways to read the value in a Set:

The long way:

if (Delphi1 in IDECompatibility) then
  CheckListBox1.Checked[0] := True;
if (Delphi2 in IDECompatibility) then
  CheckListBox1.Checked[1] := True;
if (Delphi3 in IDECompatibility) then
  CheckListBox1.Checked[2] := True;

The cleaner, short and better way:

CheckListBox1.Checked[0] := (Delphi1 in IDECompatibility);
CheckListBox1.Checked[1] := (Delphi2 in IDECompatibility);
CheckListBox1.Checked[2] := (Delphi3 in IDECompatibility);

Now I want to do it the other way, to set the values.

Currently the only way I know is the long way:

if CheckListBox1.Checked[0] then
  IDECompatibility := IDECompatibility + [Delphi1]
else
  IDECompatibility := IDECompatibility - [Delphi1];

if CheckListBox1.Checked[1] then
  IDECompatibility := IDECompatibility + [Delphi2]
else
  IDECompatibility := IDECompatibility - [Delphi2];

if CheckListBox1.Checked[2] then
  IDECompatibility := IDECompatibility + [Delphi3]
else
  IDECompatibility := IDECompatibility - [Delphi3];

If possible I would like to do something like this:

IDECompatibility[Delphi1] := CheckListBox1.Checked[0]; // Array type required
IDECompatibility[Delphi2] := CheckListBox1.Checked[1]; // Array type required
IDECompatibility[Delphi3] := CheckListBox1.Checked[2]; // Array type required

There is the Exclude and Include members but I am unsure if these are going to be needed here or not.

So, as described above - Is there a easier way to define a Enum type based on a boolean value?

Thank you.

Evoy answered 5/7, 2014 at 9:37 Comment(18)
Are you really sure, that an enum is the right choice here at all? You now today that there will be more enum values in the future that you cannot name today (XE7 or XTE ...). To me I would use a collectionWeigh
What are Delphi1, Delphi2, and Delphi3 meant to represent? Are you trying to offer compatibility customization to an end user based on the version of Delphi they are running? If yes, this sounds like a case for conditional compilation perhaps...Negrete
Regardless of his usecase it would be nice if an enum set syntaxwise could be used like an array of boolean that goes over an enum.Mulloy
Ignore what Delphi1, Delphi2 Delphi3 is shown as, this was just for the example purposes.Evoy
Could you do this with a generic list? TList<TSomeEnum> - or better, a custom TList<T>; something that would prevent duplicates, etc.Negrete
You are looking for an easy UI representation of a set and not another way for the OI, right?Weigh
You can mimic this with an indexed property. But you won't be able to access it with OI. From what I've seen, people usually publish the set field and provide also a public indexed property prefixed by Is or Has, in your case e.g. HasIDECompatibility for acccess that you've shown, like this.Filippa
@Filippa this is not for a published property so no worries about visibility in the Object Inspector, in fact it could just be in any regular class.Evoy
Well, I would personally keep both. The one for overall access to the set field (which can be published; which is not for you in this case) and the Has... one as a helper for easier access.Filippa
Im not familiar with those keywords Is and Has so I might check those out.Evoy
No no, they are not keywords. They just indicate the meaning of the property. I'm sure you've already met methods like IsVisible, HasChildren or others with those prefixes. This is similar, but it's an indexed property by which you can read as well as write. It's just a helper property for working with a set field as it was array (similar to what you've shown).Filippa
I would build a generic wrapper having a DisplayName and Selected as properties organized in a list inside a SetWrapper class. Now you can easily populate this to a UI list control and read and set the enum value through the wrapper.Weigh
@Filippa thanks for clearing that up, I assumed they were keywords.Evoy
@SirRufo do you mean similarly to what TLama mentioned? If so I think some code sample would be helpful if anyone wants to write one up as an answer if it means I cannot simply do what I originally thought.Evoy
You could improve readability right away if you lose those spurious parentheses around boolean expressions...Kneeland
@FreeConsulting thats by habit, and from the sounds of it a bad one? I always seem to wrap expressions in (), I think you have to for multiple expression statements and so probably why I have been doing it for single expression statements. I also have OCD which does not help me because whenever I look at any code it always seems wrong and could be written better so I constantly change things for better or for worse and it is exremely annoying me!Evoy
@Blobby, and I'm a perfectionist, and it is considered a disease in collaborative open-source world :-) Anyway, unnecessary parentheses is not a good habit, definitely, because they serves no purpose other than burning reader's attention for nothing. There are visible boundaries for expressions already, eg := expr;.Kneeland
Regarding your original problem: consider creating a mapping between listbox index and enum's ordinality and using a loop. Also, if your IDECompatibility is eligible to be passed as var parameter, you can use Include/Exclude intrinsics. Also, if you start with [], you can lose else branches and end up with correct set. As you see, there is a room for improvement without redesigning the whole thing.Kneeland
F
5

No, there is no array like access available for set type in Delphi at this time. I would suggest making a helper indexed property which can mimic array like access and which will hide a code needed to include or exclude an element from the set.

From what I've seen so far, people usually prefix properties that has some elementary access by Is or Has prefixes. In your case I would follow this rule and make a property called HasIDECompatibility or let's say IsIDECompatible. This property will be of Boolean type and will have a getter, by which you return the information whether the element is inside the internal set field or not. In setter it will either include or exclude the element into the set depending on the input value.

In a more generalized code it would be:

type
  TElement = (
    Element1,
    Element2,
    Element3
  );
  TElements = set of TElement;

  TMyClass = class
  private
    FElements: TElements;
    function GetElement(Kind: TElement): Boolean;
    procedure SetElement(Kind: TElement; Value: Boolean);
  public
    // this property is for direct access to the set field; if your class would
    // be a TComponent descendant and you'd publish this property, you'd see it
    // in the Object Inspector represented by a set of check boxes
    property Elements: TElements read FElements write FElements;
    // this property is just a helper for array like access to the internal set
    // field
    property HasElement[Kind: TElement]: Boolean read GetElement write SetElement;
  end;

implementation

{ TMyClass }

function TMyClass.GetElement(Kind: TElement): Boolean;
begin
  Result := Kind in FElements;
end;

procedure TMyClass.SetElement(Kind: TElement; Value: Boolean);
begin
  if Value then
    Include(FElements, Kind)
  else
    Exclude(FElements, Kind);
end;

And an example usage:

procedure TForm1.Button1Click(Sender: TObject);
var
  MyClass: TMyClass;
begin
  MyClass := TMyClass.Create;
  try
    // write elementary property value
    MyClass.HasElement[Element1] := CheckBox1.Checked;
    MyClass.HasElement[Element2] := CheckBox2.Checked;
    // read elementary property value
    CheckBox3.Checked := MyClass.HasElement[Element1];
    CheckBox4.Checked := MyClass.HasElement[Element2];

    // or you can access MyClass.Elements set at once as you were used to do
    // which gives you two ways to include or exclude elements to the set
  finally
    MyClass.Free;
  end;
end;
Filippa answered 6/7, 2014 at 10:25 Comment(5)
This looks promising, and a well explained answer too :)Evoy
This could live on the stack as a record if a clear method existed, no?Tylertylosis
I mean that instead of hitting the heap with a class, a record could be used. But you still need to clear the the FElements contents with a method prior to using the record.Tylertylosis
@LURD, that TMyClass is the initial OP's class (from the linked question) and what I've shown is just an extension to it (well, modified for better readability for the purpose of this question). Actually, what I've just added is the HasElement property.Filippa
Aah, now I see. I did not follow that link. Sorry.Tylertylosis
M
5

I know you mentioned XE, but someone looking at this question might be interested in this answer for a more recent version.

In XE6 this can be accomplished (almost) with a set helper:

type
  TMyRange = 0..7;
  TMySet = set of TMyRange;

type
  TMySetHelper = record helper for TMySet
  public
    function GetElement(Index: TMyRange): Boolean;
    procedure SetElement(Index: TMyRange; const Value: Boolean);
    property Element[Index: TMyRange]: Boolean read GetElement write SetElement;
  end;

As it is not allowed to make Element the default array property, you have to specify the property name.

var
  MySet: TMySet;
begin
  MySet.Element[0] := False;
  MySet.Element[1] := not MySet.Element[0];
end;
Moyna answered 5/7, 2014 at 15:26 Comment(7)
The set mentioned in this question is a property. Will a record helper work for property ? I don't think so, but cannot verify at this time...Filippa
@TLama, at least it compiles.Moyna
Interesting. So it might work. It makes me wonder what happens if there's a setter for such property. I would expect the compiler to fail in that case.Filippa
@TLama, I agree that it shoudn't work in the first place. The same as a plain Include or Exclude on that set property shouldn't work either.Moyna
@FreeConsulting, as always it is the question about "can be done" and "should be done". I for myself appreciate that it can be done as there might be some cases where it would come in handy. Although, I suggest using it sparsely.Moyna
Uwe, you've got me wrong, probably. I was grumbling about wacky syntax which allows record helper type associated with set type. Now I'm on Free Pascal side.Kneeland
I can confirm that it doesn't work with properties. It compiles and executes, but doesn't change the value. I think it was clear but I didn't want to leave it open here :)Equiponderate
F
5

No, there is no array like access available for set type in Delphi at this time. I would suggest making a helper indexed property which can mimic array like access and which will hide a code needed to include or exclude an element from the set.

From what I've seen so far, people usually prefix properties that has some elementary access by Is or Has prefixes. In your case I would follow this rule and make a property called HasIDECompatibility or let's say IsIDECompatible. This property will be of Boolean type and will have a getter, by which you return the information whether the element is inside the internal set field or not. In setter it will either include or exclude the element into the set depending on the input value.

In a more generalized code it would be:

type
  TElement = (
    Element1,
    Element2,
    Element3
  );
  TElements = set of TElement;

  TMyClass = class
  private
    FElements: TElements;
    function GetElement(Kind: TElement): Boolean;
    procedure SetElement(Kind: TElement; Value: Boolean);
  public
    // this property is for direct access to the set field; if your class would
    // be a TComponent descendant and you'd publish this property, you'd see it
    // in the Object Inspector represented by a set of check boxes
    property Elements: TElements read FElements write FElements;
    // this property is just a helper for array like access to the internal set
    // field
    property HasElement[Kind: TElement]: Boolean read GetElement write SetElement;
  end;

implementation

{ TMyClass }

function TMyClass.GetElement(Kind: TElement): Boolean;
begin
  Result := Kind in FElements;
end;

procedure TMyClass.SetElement(Kind: TElement; Value: Boolean);
begin
  if Value then
    Include(FElements, Kind)
  else
    Exclude(FElements, Kind);
end;

And an example usage:

procedure TForm1.Button1Click(Sender: TObject);
var
  MyClass: TMyClass;
begin
  MyClass := TMyClass.Create;
  try
    // write elementary property value
    MyClass.HasElement[Element1] := CheckBox1.Checked;
    MyClass.HasElement[Element2] := CheckBox2.Checked;
    // read elementary property value
    CheckBox3.Checked := MyClass.HasElement[Element1];
    CheckBox4.Checked := MyClass.HasElement[Element2];

    // or you can access MyClass.Elements set at once as you were used to do
    // which gives you two ways to include or exclude elements to the set
  finally
    MyClass.Free;
  end;
end;
Filippa answered 6/7, 2014 at 10:25 Comment(5)
This looks promising, and a well explained answer too :)Evoy
This could live on the stack as a record if a clear method existed, no?Tylertylosis
I mean that instead of hitting the heap with a class, a record could be used. But you still need to clear the the FElements contents with a method prior to using the record.Tylertylosis
@LURD, that TMyClass is the initial OP's class (from the linked question) and what I've shown is just an extension to it (well, modified for better readability for the purpose of this question). Actually, what I've just added is the HasElement property.Filippa
Aah, now I see. I did not follow that link. Sorry.Tylertylosis
E
0

Based on Uwes solution, I created this helper (requires XE6) which can be used for variables and properties:

TGridOptionsHelper = record helper for TGridOptions
public
  ///  <summary>Sets a set element based on a Boolean value</summary>
  ///  <example>
  ///    with MyGrid do Options:= Options.SetOption(goEditing, False);
  ///    MyVariable.SetOption(goEditing, True);
  ///  </example>
  function SetOption(GridOption: TGridOption; const Value: Boolean): TGridOptions;
end;

function TGridOptionsHelper.SetOption(
  GridOption: TGridOption; const Value: Boolean): TGridOptions;
begin
  if Value then Include(Self, GridOption) else Exclude(Self, GridOption);
  Result:= Self;
end;

Remark: compared to Uwes solution, I also omitted code for reading a set element - that should be done using the in operator.

Equiponderate answered 9/5, 2018 at 11:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.