1

I use memtables to wire enumerated type with comboboxes using LiveBinding.

However I have a lot of them and they way I am doing is way too bad (copy/paste)

For example, I have the following enumeration:

 TEnumResourceType        = (trtApp, trtTab, trtSection, trtField, trtCommand, trtOther);

and for that I created a function to give the string equivalent:

function EnumResourceTypeToStr(AEnum: TNaharEnumResourceType): string;
begin
  case AEnum of
    trtApp     : result := 'Aplicação';
    trtTab     : result := 'Pagina (Tab)';
    trtSection : result := 'Secção';
    trtField   : result := 'Campo';
    trtCommand : result := 'Comando';
    trtOther   : result := 'Outro';
  end;
end;

In a datamodule I place my memtable and I need to populate it, I am using the AFTEROPEN event of the table with the following code:

procedure TDMGlobalSystem.vtResourceTypeAfterOpen(DataSet: TDataSet);
var
  enum : TEnumResourceType;
begin
  inherited;

  for enum := Low(TEnumResourceType) to High(TEnumResourceType) do
    DataSet.InsertRecord([EnumResourceTypeToStr(enum), Ord(enum)]);
end;

All that works, however I need to do that for each new enumaration and I have dozens. Eventually I will need to change my current memtable to other and that is an added concern to automate the process. The current memtable sometimes does not work on Android.

I am looking in a way to automate this process, or using generics, or whatever, that in the DataModule I only need something like: PopulateEnum(Table, Enum);

The best solution would be creating a component inherited from this memtable and somehow define what is the enum required and all the magic happens (including the selection of the enumtostr)

Eduardo Elias
  • 1,742
  • 1
  • 22
  • 49
  • Do you only want to fill the table with integers and strings? Why not using a `TDictionary` for that and to fill the table iterate over the key and get the string for that key? – Sir Rufo Jul 25 '14 at 12:59
  • If you want to iterate through (nearly) any enum look at this http://stackoverflow.com/questions/12379032/using-an-enum-with-generics – Sir Rufo Jul 25 '14 at 13:05

2 Answers2

3

Here is a generic wrapper for enums to get an array of integer,string pair representing the ordinal value and the name for the enums.

A little test

program so_24955704;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  EnumValueStore in 'EnumValueStore.pas';

type
  TEnumResourceType = ( trtApp, trtTab, trtSection, trtField, trtCommand, trtOther );

procedure PrintEnumValueStore( AEnumValueStore : TEnumValueStore );
var
  LEnumValuePair : TEnumValuePair;
begin
  for LEnumValuePair in AEnumValueStore.GetKeyValues do
    begin
      Writeln( LEnumValuePair.Key, '-', LEnumValuePair.Value );
    end;
end;

procedure TestEnum;
var
  LEnumValueStore : TEnumValueStore<TEnumResourceType>;
begin
  LEnumValueStore := TEnumValueStore<TEnumResourceType>.Create;
  try
    // print default names
    PrintEnumValueStore( LEnumValueStore );

    WriteLn;

    // set the custom names
    LEnumValueStore.SetValue( trtApp, 'Aplicação' );
    LEnumValueStore.SetValue( trtTab, 'Pagina (Tab)' );
    LEnumValueStore.SetValue( trtSection, 'Secção' );
    LEnumValueStore.SetValue( trtField, 'Campo' );
    LEnumValueStore.SetValue( trtCommand, 'Comando' );
    LEnumValueStore.SetValue( trtOther, 'Outro' );

    // print the default values
    PrintEnumValueStore( LEnumValueStore );
  finally
    LEnumValueStore.Free;
  end;
end;

begin
  try
    TestEnum;
  except
    on E : Exception do
      Writeln( E.ClassName, ': ', E.Message );
  end;
  ReadLn;

end.

will produce the following output

0-App
1-Tab
2-Section
3-Field
4-Command
5-Other

0-Aplicação
1-Pagina (Tab)
2-Secção
3-Campo
4-Comando
5-Outro

and here is the unit that will do the work

unit EnumValueStore;

interface

uses
  System.Generics.Collections;

type
  TEnumValuePair = TPair<Integer, string>;

  TEnumValueStore = class abstract
  public
    function GetKeyValues : TArray<TEnumValuePair>; virtual; abstract;
  end;

  TEnumValueStore<TEnumKey> = class( TEnumValueStore )
  private
    FValueDict : TDictionary<TEnumKey, string>;
  public
    constructor Create;
    destructor Destroy; override;
    procedure SetValue( AKey : TEnumKey; const AValue : string );
    function GetKeyValues : TArray<TEnumValuePair>; override;
  end;

implementation

uses
  SimpleGenericEnum;

{ TEnumValueStore<TEnumKey> }

constructor TEnumValueStore<TEnumKey>.Create;
begin
  inherited Create;
  FValueDict := TDictionary<TEnumKey, string>.Create;
end;

destructor TEnumValueStore<TEnumKey>.Destroy;
begin
  FValueDict.Free;
  inherited;
end;

function TEnumValueStore<TEnumKey>.GetKeyValues : TArray<TEnumValuePair>;
var
  LEnum : TEnum<TEnumKey>;
  LMin, LMax : Integer;
  LCount : Integer;
  LIdx : Integer;
  LStr : string;
begin
  LMin := LEnum.Ord( LEnum.Low );
  LMax := LEnum.Ord( LEnum.High );
  LCount := LMax - LMin + 1;
  SetLength( Result, LCount );

  LCount := 0;
  for LIdx := LMin to LMax do
    begin
      LEnum := LIdx;
      if FValueDict.ContainsKey( LEnum )
      then
        LStr := FValueDict[LEnum]
      else
        LStr := LEnum;
      Result[LCount] := TEnumValuePair.Create( LEnum, LStr );
      Inc( LCount );
    end;
end;

procedure TEnumValueStore<TEnumKey>.SetValue( AKey : TEnumKey; const AValue : string );
begin
  FValueDict.AddOrSetValue( AKey, AValue );
end;

end.

I use the unit SimpleGenericEnum but there is a small bug inside you need to correct

class function TEnum<T>.High: T;
begin
  // original code
  // Result := Cast(_TypeData.MaxValue);
  Result := Cast(GetTypeData.MaxValue);
end;

class function TEnum<T>.Low: T;
begin
  // original code
  // Result := Cast(_TypeData.MinValue);
  Result := Cast(GetTypeData.MinValue);
end;
Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
  • Exactly what I needed. I will add a method to populate a my standard dataset for representing enum and I am done. Thanks! – Eduardo Elias Jul 25 '14 at 14:27
2

I would have said you have two easier choices here

Your could replace EnumResourceTypeToStr(enum) with GetEnumName(TypeInfo(TEnumResourceType), ord(enum)) or some variation on it. This has the disadvantage that it simply returns the enum as it appears in your program.

Alternatively add a constant EnumNames: array [TEnumResourceType] of string = ('.... etc. populated with your list of strings. These can then be accessed as EnumNames[enum]. This allows you arbitrary strings and the compiler will remind you to add additional entries if you extend the enumeration.

Kanitatlan
  • 395
  • 1
  • 9
  • +1, your 2nd alternative is easy to maintain and comes with compile time check. – LU RD Jul 25 '14 at 14:53
  • +1 actually I wanted to define both answers as accepted. Your alternative for hold the strings is very useful, I will be adding this to the other solution and both will solve very well my problem. Thank you for that. – Eduardo Elias Jul 26 '14 at 13:39