Records are fixed in size - the definition of a record must contain fixed length fields. We are allowed to have strings, but either their length must be specified (for example a : String[20]), or a pointer to the string is stored in the record. In this case, the record cannot be used to write the string to a file. The TPoint type is an example of a record. Before we go any further, let us look at a simple example.
type TCustomer = record name : string[30]; age : byte; end; var customer : TCustomer; begin // Set up our customer record customer.name := 'Fred Bloggs'; customer.age := 23; end;When we define a record, each field is simply accessed by name, separated by a dot from the record variable name. This makes records almost self documenting, and certainly easy to understand.
Above, we have created one customer, and set up the customer record fields.
type TCustomer = record name : string[30]; age : byte; end; var John, Nancy : TCustomer; begin // Set up our customer records with John do begin name := 'John Moffatt'; // Only refer to the record fields age := 67; end; with Nancy do begin name := 'Nancy Moffatt'; // Only refer to the record fields age := 77; end; end;
Please note that this is quite a complex piece of code - it uses a procedure that takes a variable number of parameters, specially passed in square brackets (see Procedure for more on procedures).
// Full Unit code. // ----------------------------------------------------------- // You must store this code in a unit called Unit1 with a form // called Form1 that has an OnCreate event called FormCreate. unit Unit1; interface uses Forms, Dialogs; type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure ShowCustomer(const fields: array of string); end; var Form1: TForm1; implementation {$R *.dfm} // Include form definitions procedure TForm1.FormCreate(Sender: TObject); type // Declare a customer record TCustomer = Record firstName : string[20]; lastName : string[20]; address1 : string[100]; address2 : string[100]; address3 : string[100]; city : string[20]; postCode : string[8]; end; var customers : array[1..3] of TCustomer; i : Integer; begin // Set up the first customer record with customers[1] do begin firstName := 'John'; lastName := 'Smith'; address1 := '7 Park Drive'; address2 := 'Branston'; address3 := 'Grimworth'; city := 'Banmore'; postCode := 'BNM 1AB'; end; // Set up the second and third by copying from the first customers[2] := customers[1]; customers[3] := customers[1]; // And then changing the first name to suit in each case customers[2].firstName := 'Sarah'; customers[3].firstName := 'Henry'; // Now show the details of these customers for i := 1 to 3 do with customers[i] do ShowCustomer([firstName, lastName, address1, address2, address3, city, postCode]); end; // A procedure that displays a variable number of strings procedure TForm1.ShowCustomer(const fields: array of string); var i : Integer; begin // Display all fields passed - note : arrays start at 0 for i := 0 to Length(fields)-1 do ShowMessage(fields[i]); ShowMessage(''); end; end.
The Delphi TRect type illustrates the first concept. It is defined like this:
type TRect = packed record case Integer of 0: (Left, Top, Right, Bottom: Integer); 1: (TopLeft, BottomRight: TPoint); end;Here we have a record that holds the 4 coordinates of a rectangle. The Case clause tells Delphi to map the two following sub-sections onto the same area (the end) of the record. These variant sections must always be at the end of a record. Note also that the case statement has no end statement. This is omitted because the record finishes at the same point anyway.
The record allows us to store data in two ways:
var rect1, rect2 : TRect; begin // Setting up using integer coordinates rect1.Left := 11; rect1.Top := 22; rect1.Right := 33; rect1.Bottom := 44; // Seting up rect2 to have the same coordinates, but using points instead rect2.TopLeft := Point(11,22); rect2.BottomRight := Point(33,44); end;The TRect record showed two methods of reading from and writing to a record. The second concept is to have two or more record sub-sections that have different formats and lengths.
This time we will define a fruit record that has a different attribute section depending on whether the fruit is round or long:
type // Declare a fruit record using case to choose the // diameter of a round fruit, or length and height ohterwise. TFruit = Record name : string[20]; Case isRound : Boolean of // Choose how to map the next section True : (diameter : Single); // Maps to same storage as length False : (length : Single; // Maps to same storage as diameter width : Single); end; var apple, banana : TFruit; begin // Set up the apple as round, with appropriate dimensions apple.name := 'Apple'; apple.isRound := True; apple.diameter := 3.2; // Set up the banana as long, with appropriate dimensions banana.name := 'Banana'; banana.isRound := False; banana.length := 7.65; banana.width := 1.3; // Let us display the fruit dimensions: if apple.isRound then ShowMessageFmt('Apple diameter = %f',[apple.diameter]) else ShowMessageFmt('Apple width = %f , length = %f', [apple.width, apple.length]); if banana.isRound then ShowMessageFmt('Banana diameter = %f',[banana.diameter]) else ShowMessageFmt('Banana width = %f , length = %f', [banana.width, banana.length]); end;Note that the Case statement now defines a variable, isRound to hold the type of the variant section. This is very useful, and recommended in variable length subsections, as seen in the code above.