Solved

Accessing field name in records with variable name

Posted on 2006-11-11
14
1,162 Views
Last Modified: 2010-04-05
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
Comment
Question by:Motaz
  • 5
  • 5
  • 2
  • +2
14 Comments
 
LVL 15

Expert Comment

by:mikelittlewood
ID: 17920795
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
 
LVL 28

Expert Comment

by:TName
ID: 17920982
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
 
LVL 28

Expert Comment

by:TName
ID: 17921057
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
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17928537
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
 
LVL 7

Author Comment

by:Motaz
ID: 17936914
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
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17937170
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
 
LVL 7

Author Comment

by:Motaz
ID: 17937367
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
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17937482
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
 
LVL 7

Author Comment

by:Motaz
ID: 17937743
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
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17937928
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
 
LVL 4

Accepted Solution

by:
Meldrachaun earned 500 total points
ID: 17954101
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
 
LVL 7

Author Comment

by:Motaz
ID: 17954853
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
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 17955039
Am just wondering why you accepted the answer from Meldrachaun...
0
 
LVL 7

Author Comment

by:Motaz
ID: 17955472
because it is simple answer, "so there's no way to retrieve them" and that's what solution I used
0

Featured Post

What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

Join & Write a Comment

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

708 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now