0

In my application I have many sets defined:

eBlockTypes = (btNone,btUndefined,btStone, btYellowFlower, btWoodBrown...);

sMinerals = set of eBlockTypes; 

var
  mineralsRare: sMinerals;
  mineralsPlants: sMinerals;
  mineralsAll: sMinerals;
  mineralsDeep: sMinerals;
  mineralsWalkable: sMinerals;
  mineralsDiggable: sMinerals; 

then I have an object that has 'sMinerals' as one of it's fields. Is it possible to read set's 'name' when loading object properties from file?

edit: more details. Let's say that object definition in file looks like that:

[item]
Computer
[requires]
3 Circuit board
1 Medium CPU
3 Plastic
[placement]
mineralsWalkable

so I can parse the file and read all the properties besides the set 'mineralsWalkable'. I know that I could compare that string with some TStrings holding all the sets names but the quiestion is: is it possible get that set by converting the string to variable somehow?

laggyluk
  • 195
  • 2
  • 14
  • Sure, it's possible. But you need to [edit] to provide a clearer explanation of what you want to do before we can tell you how to do so. – Ken White Jul 16 '13 at 03:14
  • Lookup [GetEnumValue](http://docwiki.embarcadero.com/Libraries/XE4/en/System.TypInfo.GetEnumValue) and [GetEnumName](http://docwiki.embarcadero.com/Libraries/XE4/en/System.TypInfo.GetEnumName). Those should be helpful in solving your problem...along with a pretty simple case statement. – Sherlock70 Jul 16 '13 at 06:41
  • from what I've read those can convert string to set's element but not to set's 'type' – laggyluk Jul 16 '13 at 07:22

2 Answers2

1

What you can read from a file depends on what's in the file. If you wrote the names of your variables into the file, then you should be able to read them, too. If not, then you can't. Variables names are not inherently written to files when you wrote data, though.

Identify the technique used to write the names into your file. To read them, simply perform the inverse operation. If you wrote the data delimited in some way, then read until you encounter a delimiter. If you preceded the name with its character length, then read the length, and then read that many characters. If you didn't write the names with a technique that's invertible, then you'll have to change how you write your data before proceed to reading it.

Your question asked whether it was possible to read the names, and the answer is yes. You've since asked another question, which is whether it's possible to "convert" the name read from the file into the actual variable with the corresponding name. The answer to that is no.

Ordinary variables do not have RTTI; Delphi does not maintain the names of all the variables in a program. Once the compiler finishes its job, the names cease to exist within the program.

The easiest way to get the variable is to set up a mapping from string to set value. Read the name from the file, look up the name in a data structure, and use the corresponding value. A TDictionary<string, sMinerals> would be perfect for that. Just populate the data structure as your program starts up.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • I wrote it 'by hand' :P object's are predefined and don't need saving. So it's a variable name. I know that typeinfo can convert set elements to string and back but didn't know how to do it with 'set' type variable rather than elements – laggyluk Jul 16 '13 at 03:51
  • Maybe you wrote it by hand, but that wasn't my point. You wrote it in such a way that it could be read; in this case, it's on a line by itself preceded by what looks like some fixed, known header. So look for the header, and then read until the next line break, and you have your string. I don't see why you would have doubted that you'd be able to read that name in the first place. – Rob Kennedy Jul 16 '13 at 04:26
  • alright, I edited the question to make it clearer and take 'no' for an answer – laggyluk Jul 16 '13 at 07:29
0

Pretty Simple... You need to save your variable mineralsWalkable to a string representation of your eBlockTypes. You'll use GetEnumName to do that. You then need to convert your string representation of your eBlockTypes to an actual eBlockType and then add that to your mineralsWalkable. You'll use GetEnumValue to do that.

The following example shows taking the string representation...placing it in a set...and then taking the set...and moving it back to a string...

object Form54: TForm54
  Left = 0
  Top = 0
  Caption = 'Form54'
  ClientHeight = 290
  ClientWidth = 554
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 56
    Top = 160
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
  end
  object Edit1: TEdit
    Left = 56
    Top = 64
    Width = 265
    Height = 21
    TabOrder = 1
    Text = 'Edit1'
  end
  object cbType1: TCheckBox
    Left = 248
    Top = 91
    Width = 97
    Height = 17
    Caption = 'Type1'
    TabOrder = 2
  end
  object cbType2: TCheckBox
    Tag = 1
    Left = 248
    Top = 114
    Width = 97
    Height = 17
    Caption = 'Type2'
    TabOrder = 3
  end
  object cbType3: TCheckBox
    Tag = 2
    Left = 248
    Top = 137
    Width = 97
    Height = 17
    Caption = 'Type3'
    TabOrder = 4
  end
end

unit Unit54;
{Note the code assumes cbType1.Tag = 0, cbType2.Tag = 1, and cbType3.Tag = 2}
interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, TypInfo, IniFiles;

type
  TMyType = (mtOne, mtTwo, mtThree);
  TMyTypes=  set of TMyType;

  TForm54 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    cbType1: TCheckBox;
    cbType2: TCheckBox;
    cbType3: TCheckBox;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    private
    function getTypes1: TMyTypes;
    procedure setMyTypes1(const Value: TMyTypes);
    { Private declarations }

  public
    { Public declarations }
    property Types1: TMyTypes read getTypes1 write setMyTypes1;

    procedure SaveMyTypes(aVariableName: string; aMyTypes: TMyTypes);
    function ReadMyTypes(aVariableName: string): TMyTypes;
  end;

var
  Form54: TForm54;

implementation

{$R *.dfm}

procedure TForm54.Button1Click(Sender: TObject);
var
  a_MyTypes: TMyTypes;
  a_Str: string;
  a_Index: integer;
begin
  a_MyTypes := [];
  a_Str := '';
  Include(a_MyTypes,  TMyType(GetEnumValue(TypeInfo(TMyType), 'mtOne')));
  Include(a_MyTypes,  TMyType(GetEnumValue(TypeInfo(TMyType), 'mtTwo')));
//purpoesly have mtThree3 instead of mtThree
  Include(a_MyTypes,  TMyType(GetEnumValue(TypeInfo(TMyType), 'mtThree3')));
  for a_Index := Ord(Low(TMyType)) to Ord(High(TMyType)) do
    if TMyType(a_Index) in a_MyTypes then
      if a_Str = '' then
        a_Str := GetEnumName(TypeInfo(TMyType), a_Index)
      else
        a_Str := a_Str + ',' + GetEnumName(TypeInfo(TMyType), a_Index);
//should be mtOne, mtTwo
  Edit1.Text := a_Str;
end;

procedure TForm54.FormCreate(Sender: TObject);
begin
  Types1 := ReadMyTypes('Types1');
end;

procedure TForm54.FormDestroy(Sender: TObject);
begin
  SaveMyTypes('Types1', Types1);
end;

function TForm54.getTypes1: TMyTypes;
var
  a_Index: integer;
begin
  Result := [];
  for a_Index := 0 to Self.ComponentCount - 1 do
    if Self.Components[a_Index] is TCheckBox and (TCheckBox(Self.Components[a_Index]).Checked) then
      Include(Result, TMyType(Self.Components[a_Index].Tag));
end;

function TForm54.ReadMyTypes(aVariableName: string): TMyTypes;
var
  a_Ini: TIniFile;
  a_Var: string;
  a_List: TStrings;
  a_Index: integer;
begin
  Result := [];
  a_Ini := nil;
  a_List := nil;
  a_Ini := TIniFile.Create('MyType.ini');
  a_List := TStringList.Create;
  try
    a_Var := a_Ini.ReadString('Sets', aVariableName, '');
    a_List.Delimiter := ',';
    a_List.DelimitedText := a_Var;
    for a_Index := 0 to a_List.Count - 1 do
    begin
      Include(Result, TMyType(GetEnumValue(TypeInfo(TMyType), a_List[a_Index])));
    end;
  finally
    a_Ini.Free;
    a_List.Free;
  end;
end;

procedure TForm54.SaveMyTypes(aVariableName: string; aMyTypes: TMyTypes);
var
  a_Ini: TIniFile;
  a_Index: integer;
  a_Var: string;
begin
  a_Var := '';
  a_Ini := TIniFile.Create('MyType.ini');
  try
    for a_Index := Ord(Low(TMyType)) to Ord(High(TMyType)) do
      if TMyType(a_Index) in aMyTypes then
        if a_Var = '' then
          a_Var := GetEnumName(TypeInfo(TMyType), a_Index)
        else
          a_Var := a_Var + ',' + GetEnumName(TypeInfo(TMyType), a_Index);
    a_Ini.WriteString('Sets', aVariablename, a_Var);
  finally
    a_Ini.Free;
  end;
end;

procedure TForm54.setMyTypes1(const Value: TMyTypes);
var
  a_Index: integer;
begin
  for a_Index := 0 to Self.ComponentCount - 1 do
    if Self.Components[a_Index] is TCheckBox then
      TCheckBox(Self.Components[a_Index]).Checked := TMyType(TCheckBox(Self.Components[a_Index]).Tag) in Value;

end;

end.
House of Dexter
  • 386
  • 1
  • 7
  • still this converts set elements from string and back. I don't want to list elements in file since there are lots of them and sets are used as grouping method exactly for that reason – laggyluk Jul 19 '13 at 11:57
  • I don't understand what you want in your file then...You either save the value of Variable...The variable by itself means nothing ...This means something mineralsDiggable = btStone, btYellowFlower. So you save that string representation in your file. – House of Dexter Jul 19 '13 at 17:17
  • After looking at the types...looks like another MineCraft ;) – House of Dexter Jul 19 '13 at 18:59
  • sets grouping block types are defined in code. if I'd load elements of set when parsing object file then it would work until mineralsDiggable definition is changed (new type of block added) so it misses the point. Wanted to know if I can convert set name like I can with enum name. Never mind, I just wanted to know if it's possible but if not then it's not a big problem. – laggyluk Jul 20 '13 at 13:33
  • a Variable...which all a set is...It has no meaning other than to say...these elements are within this set...If you need to just to be able to go from you String Names to the actual set variable...then create a function that takes the string name and returns the correct Set Variable if the Set is a const and defined in code. – House of Dexter Jul 22 '13 at 13:52
  • yeah, I made TFPGMap for that – laggyluk Jul 23 '13 at 07:14