How to populate memtables from enumarations?

2019-08-04 04:39发布

问题:

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)

回答1:

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;


回答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.