• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1739
  • Last Modified:

Accessing field name in records with variable name

I'm using Turbo Delphi for win32
suppose that I have this record

type
  TRec = record
         First: Integer;
         Second: Double;
         .......
   end;

I want to access fields (Read/Write) using thier names such as:

Rec : TRec;
AName: string;
begin
  AName:= 'First';
  TypeInfoOrSomething(Rec, AName):= 120;

 How can I do this
Thanks
Motaz
0
Motaz
Asked:
Motaz
  • 5
  • 5
  • 2
  • +2
1 Solution
 
mikelittlewoodCommented:
I prefer to use objects where you can store the field name and a variant property for the value of the actual field.
You can then extend the various basic procedures and functions I have given.
If you would like some more pointers please let me know and I will try to incorporate what else you might need.


unit Unit2;

interface

uses
  SysUtils, Classes, Contnrs, Variants, DB;

type

  TObjField = class( TObject)
  private
    FName: String;
    FFieldType: TFieldType;
    FValue: Variant;
  public
    function AsInteger: Integer;
    function AsString: String;
    function AsFloat: Double;

    property FieldName: String      read FName      write FName;
    property FieldType: TFieldType  read FFieldType write FFieldType;
    property Value: Variant         read FValue     write FValue;
  end;

  TMyDataObject = class( TObjectList)
    private
      slFieldNames: TStringList;

      function GetFieldObject( sFld: String): TObjField;
    public
      procedure LoadFromDataset( pDataset: TDataset);

      property FieldNames: TStringList read slFieldNames write slFieldNames;
      property ByFieldName[ sFld: String]: TObjField read GetFieldObject;
  end;

  TMyDataObjects = class( TObjectList)
    public
      procedure LoadFromDataset( pDataset: TDataset);
  end;

implementation

function TMyDataObject.GetFieldObject(sFld: String): TObjField;
var
  i: Integer;
begin
  Result := nil;
  i := FieldNames.IndexOf( sFld);
  if i >= 0 then
    Result := TObjField( Items[ i]);
end;

procedure TMyDataObject.LoadFromDataset( pDataset: TDataset);
var
  curField: TObjField;
  i: Integer;
begin
  for i := 0 to pDataset.FieldCount - 1 do
  begin
    // create an object field
    curField := TObjField.Create;
    // assign properties to the field object and store field name for later use
    with curField do
    begin
      FieldName := pDataSet.Fields[ i].FieldName;
      FieldType := pDataSet.Fields[ i].DataType;

      // check for nulls and default
      if pDataSet.Fields[ i].isnull then
        Value := Unassigned
      else
        Value := pDataSet.Fields[ i].Value;

      // store for later use
      FieldNames.Add( FieldName);
    end;
    // add the field to the book
    Add( curField);
  end;
end;

procedure TMyDataObjects.LoadFromDataset(pDataset: TDataset);
var
  MyDataObject: TMyDataObject;
begin
  with pDataset do
    while not eof do
    begin

      MyDataObject := TMyDataObject.Create;
      MyDataObject.LoadFromDataset( pDataset);
      Add( MyDataObject);

      Next;
    end;
end;

function TObjField.AsFloat: Double;
begin
  Result := Double( Value);
end;

function TObjField.AsInteger: Integer;
begin
  Result := Integer( Value);
end;

function TObjField.AsString: String;
begin
  Result := String( Value);
end;

end.


0
 
TNameCommented:
As Mike said, I guess you'll have to use an object.
I guess you should go the clean way he suggests, but just for the fun of it:

Warning - I guess this here is a little dirty ;)
(masquarades a simple container class as a component, just to be able to use typeInfo)
Haven't really tested this solution...
There might be drawbacks, it WILL after all be a component of Form1 (e.g. ComponentCount)


unit Unit1;

interface

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

type
  TRecClass = class(TComponent)
  private
     FFirst: Integer;
     FSecond: Double;
     FThird: String;
  published
     property First: Integer read FFirst write FFirst;
     property Second: Double read FSecond write FSecond;
     property Third: String read FThird write FThird;
  end;


type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    Rec:TRecClass;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  AName: string;
  PropInfo: PPropInfo;
begin
  Rec:=TRecClass.Create(Self);

  AName:= 'First';
  PropInfo := GetPropInfo(Rec.ClassInfo, AName);
  if Assigned(PropInfo) then begin
    SetInt64Prop(Rec,AName,120);
    ShowMessage(IntToStr(Rec.First));
  end;
 
  AName:= 'Second';
  PropInfo := GetPropInfo(Rec.ClassInfo, AName);
  if Assigned(PropInfo) then begin
    SetFloatProp(Rec, AName, 1.5);
    ShowMessage(FloatToStr(Rec.Second));
  end;

  AName:= 'Third';
  PropInfo := GetPropInfo(Rec.ClassInfo, AName);
  if Assigned(PropInfo) then begin
    SetStrProp(Rec, AName, 'Test');
    ShowMessage(Rec.Third);
  end;
end;



end.
0
 
TNameCommented:
By container class I mean a simple container for values, I'm aware that it's meant to mean something else usually...
You can also use SetOrdProp instead of SetInt64Prop.
0
Cloud Class® Course: C++ 11 Fundamentals

This course will introduce you to C++ 11 and teach you about syntax fundamentals.

 
Wim ten BrinkSelf-employed developerCommented:
Well, the following code will probably lead to several experts here to just drop from their chairs, amazed that it works but it works in D2006 and your Delphi version is similar to it. :-)


type
  TRec = record
    First: Integer;
    Second: Double;
    function GetFieldByName(const Name: string): Variant;
    procedure SetFieldByName(const Name: string; const Value: Variant);
    property FieldByName[const Name: string]: Variant read GetFieldByName write SetFieldByName;
  end;

function TRec.GetFieldByName(const Name: string): Variant;
begin
  if AnsiSameText(Name, 'First') then Result := First else
  if AnsiSameText(Name, 'Second') then Result := Second else
  ...
end;

procedure TRec.SetFieldByName(const Name: string; const Value: Variant);
begin
  if AnsiSameText(Name, 'First') then First := Value else
  if AnsiSameText(Name, 'Second') then Second :=  else
  ...
end;

And now you can use:
var
  Rec: TRec;
begin
  Rec.FieldByName('First') := 'Apple';
  ...

Btw, this is a very new feature of Delphi 2006 and higher and not everyone is very fond about it. But I like it because the record is still just a record and not a class. You could basically rewrite it as:

procedure SetFieldByName(var Rec: TRec; const Name: string; const Value: Variant);
begin
  if AnsiSameText(Name, 'First') then Rec.First := Value else
  if AnsiSameText(Name, 'Second') then Rec.Second :=  else
  ...
end;

Of course, an easier way to get the records by name through the RTTI should also be possible here. I'm just showing a nicer way to use records in D2006 here. Besides, the RTTI isn't that much different from this code either. It too has to compare the names until the right field is found...
0
 
MotazAuthor Commented:
Hello experts, and sorry for late reply
I'm afraid that I should be sticked with normal record, no methods, because the size is a very key factor in my work, I read from socket and I copy the buffer to the exact packed record size. So that I prefer no-object solution
Motaz
0
 
Wim ten BrinkSelf-employed developerCommented:
Motaz,

What I suggested is definitely a no-object solution! :-) Records are no objects but since Delphi 2006, Borland has added some basic options to link functions with records. You can't inherit them, you can't override them, and they can't be virtual or dynamic. Basically, it's just an easy way to link structure-related functions to their structures.
0
 
MotazAuthor Commented:
What about size, is this record:
TRec = record
    First: Integer;
    Second: Double;
    function GetFieldByName(const Name: string): Variant;
    procedure SetFieldByName(const Name: string; const Value: Variant);
    property FieldByName[const Name: string]: Variant read GetFieldByName write SetFieldByName;
  end;

equivilant to:
TRec = record
    First: Integer;
    Second: Double;
  end;

in it's size?
because I'm stick with certain structure and size
Motaz
0
 
Wim ten BrinkSelf-employed developerCommented:
It's exactly identical in size, yes. Basically, Delphi just converts it from:

TRec = record
    First: Integer;
    Second: Double;
    function GetFieldByName(const Name: string): Variant;
    procedure SetFieldByName(const Name: string; const Value: Variant);
    property FieldByName[const Name: string]: Variant read GetFieldByName write SetFieldByName;
  end;

to:

TRec = record
    First: Integer;
    Second: Double;
  end;
function GetFieldByName(Self: TRec; const Name: string): Variant;
with self do begin
end;
procedure SetFieldByName(Self: TRec; const Name: string; const Value: Variant);
with self do begin
end;

Or something similar. With classes and objects, the Delphi compiler has to include a virtual method table to maintain the links to the methods in your class. With a record, ALL procedures and functions are static! It's a new feature, btw. You won't find much information about it. It was introduced in Delphi 2006 after many people requesting for such functionality. So your structure will maintain it's fixed size.

Do keep in mind that this is very new functionality so your code won't be backwards compatible. And not many Delphi developers will be aware of this new functionality.
0
 
MotazAuthor Commented:
That great, but back to my real problem, is that I escaped from my 200 fields records, so that instead of writing
if FieldName = 'First' then
  Rec.First:= Value
else
....
else
if FieldName = 'R200th' then
  Rec.R200th:= Value

in this case no need for GetFieldByName,
I assummed that there is a solution to illiminate writing (if else 200 ) times

Motaz
0
 
Wim ten BrinkSelf-employed developerCommented:
Well, all I suggested was a location for where to add the functionality for retrieving and setting the values. And I suggested to use a simple method based on just a string value. TName provided an alternative solution through the use of the RTTI (Run-Time Type Information) to get the members of a class object. I think it's also possible to do so with just records, although I'm not 100% sure about this. TName might enlighten you a bit more about this part. :-)

(Otherwise, I'll hope to be able to take a look at it when I'm back home. Which is in about 6 hours from now...)
0
 
MeldrachaunCommented:
The bottom line here is that the record field names are not stored anywere after the compile, so there's no way to retrieve them.  Somehow you will need to do something extra to store those field names during the compile.  There are a number of ways to do this, what ever you are most comfortable with.  You can create matching classes with published information, that lets you use RTTI, but that can get messy.  An easier approach is simply to use a TStringList, store the field name in the string value, and use a pointer in the object value that can be used as an offset into the record structure.  There's probably a number of other ways to do it as well.  Are you interested in this kind of approach?
0
 
MotazAuthor Commented:
I thought that was easy, but it is obvious now the easy way is to use (if else if else) structure and access fields directly
Thanks for all experts who participate on this discussion
Motaz
0
 
Wim ten BrinkSelf-employed developerCommented:
Am just wondering why you accepted the answer from Meldrachaun...
0
 
MotazAuthor Commented:
because it is simple answer, "so there's no way to retrieve them" and that's what solution I used
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Cloud Class® Course: Microsoft Azure 2017

Azure has a changed a lot since it was originally introduce by adding new services and features. Do you know everything you need to about Azure? This course will teach you about the Azure App Service, monitoring and application insights, DevOps, and Team Services.

  • 5
  • 5
  • 2
  • +2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now