I am trying to make better use of VHDL protected types, so I threw together the following test (just for illustration, of course - my actual use case is considerably more complex):
type prot_type1 is protected
procedure set (new_data : integer);
impure function get return integer;
end protected prot_type1;
type prot_type1 is protected body
variable data : integer := 0;
procedure set (new_data : integer) is
begin
data := new_data;
end procedure set;
impure function get return integer is
begin
return data;
end function get;
end protected body prot_type1;
This compiles. However, the following line does not:
type prot_type1_array is array (natural range <>) of prot_type1;
Ashenden says (3rd Ed., p. 589) "Protected types cannot be used as elements of ... composite types". This is unfortunate. I was hoping to be able to create another protected type with the body:
type prot_type2 is protected body
variable data : prot_type1_array(0 to 3);
procedure set (idx : natural; new_data : integer) is
begin
data(idx).set(new_data);
end procedure set;
...
end protected body prot_type2;
and avoid duplicating the code in prot_type1.set() (which is admittedly trivial in this case, but would be much more complex in my actual use case). It seems my only choice, though, is (1) to basically rewrite the entirety of prot_type1 except with an array type for my private variable. Or (2), flatten the array internally, like:
type prot_type2 is protected body
variable data0 : prot_type1;
variable data1 : prot_type1;
procedure set (idx : natural; new_data : integer) is
begin
case idx is
when 0 =>
data0.set(new_data);
when 1 =>
data1.set(new_data);
when others =>
-- handle exceptions here
end case;
end procedure set;
...
end protected body prot_type2;
This works, but is mildly undesirable for small arrays, and is extremely undesirable for large arrays. Is there another way?
here is a suggestion based on Morten Zilmer comment. The prot1_type get an access on integer instead of a unique integer. I have used function append, remove and get to manage the integer values.
Here is the code :
type array_int is array (natural range <>) of integer;
type a_integer is access array_int;
type prot_type1 is protected
-- add a new value at the end of the vector
procedure append (new_data : integer);
-- remove a value from the vector, return 0 ik OK, -1 is the item doesn't exist
impure function remove (index : integer) return integer;
-- return the integer value of the item
impure function get(index : integer) return integer;
end protected prot_type1;
type prot_type1 is protected body
variable data : a_integer;
procedure append(new_data : integer) is
variable temp : a_integer;
begin
-- create a temporary vector with the new values
temp := new array_int'(data.all & new_data);
-- free memory of the real vector
Deallocate(data);
-- reallocate the real vector with the good values
data := new array_int'(temp.all);
-- free memory of the temporary vector
Deallocate(temp);
end procedure append;
impure function remove(index : integer) return integer is
variable temp : a_integer;
begin
if (index > data'length-1 or index < 0) then -- not sure if the vector is (0 to length -1) or (1 to length). to be tested !!!
return -1;
else
-- create a temporary vector with the new values
temp := new array_int'(data(0 to index-1) & data(index+1 to data'length-1));
-- free memory of the real vector
Deallocate(data);
-- reallocate the real vector with the good values
data := new array_int'(temp.all);
-- free memory of the temporary vector
Deallocate(temp);
return 0;
end if;
end function remove;
impure function get(index : integer) return integer is
begin
return data(index);
end function get;
end protected body prot_type1;
Related
I have a protected Hashed_Map with Vectors of data. To get an element from a specific Vector, I need to pass its key to the entry and, if the Vector is empty, wait for new elements to appear in it. In the barrier condition, the key argument is not yet available and I had to make an entry nested in procedure that takes a key. In this case, a warning appears about a possible blocking operation.
Is there any other way to do this?
with Ada.Containers.Vectors;
with Ada.Containers.Hashed_Maps;
package Protected_Map is
use Ada.Containers;
type Element_Key is new Positive;
type Data_Type is null record;
package Data_Vectors is new Vectors
(Index_Type => Natural,
Element_Type => Data_Type);
function Data_Vector_Hash
(Key : Element_Key) return Ada.Containers.Hash_Type is
(Hash_Type (Key));
package Data_Vector_Maps is new Hashed_Maps
(Key_Type => Element_Key,
Element_Type => Data_Vectors.Vector,
Hash => Data_Vector_Hash,
Equivalent_Keys => "=",
"=" => Data_Vectors."=");
protected Map is
procedure Create (Key : out Element_Key);
procedure Put (Data : Data_Type);
procedure Get
(Key : Element_Key;
Data : out Data_Type);
procedure Delete (Key : Element_Key);
private
entry Get_Element
(Key : Element_Key;
Data : out Data_Type);
Data_Vector_Map : Data_Vector_Maps.Map;
end Map;
end Protected_Map;
Since your Element_Key is a discrete type, you could use an entry family (an array of entries). There's also no need to use an actual map here, an array will suffice.
In order to use an entry family, you would need to constrain the range of Element_Key to suit your actual problem (at least one popular compiler implements entry families as actual arrays, so you'll quickly run out of memory if the range is large).
Thus:
package Protected_Map is
use Ada.Containers;
type Element_Key is new Positive range 1..10; -- constrained range
type Data_Type is null record;
package Data_Vectors is new Vectors
(Index_Type => Natural,
Element_Type => Data_Type);
type Data_Vector_Array is array(Element_Key) of Data_Vectors.Vector;
protected Map is
procedure Put (Key : Element_Key; Data : Data_Type);
entry Get
(Element_Key) -- entry family
(Data : out Data_Type);
procedure Delete (Key : Element_Key);
private
Data_Vector_Map : Data_Vector_Array;
end Map;
end Protected_Map;
and the entry body:
entry Get
(for Key in Element_Key) -- entry family
(Data : out Data_Type)
when not Data_Vector_Map(Key).Is_Empty
is
begin
...
end Get;
and then (for example)
for Key in Element_Key'Range loop
Map.Get(Key)(The_Data);
end loop;
If the map key in your example is really some discrete value within a finite range, then the answer of #egilhh is indeed to consider. If this is not the case, then you might solve the problem by using a Get entry and some additional private Get_Retry entry as shown in the example below.
This "pattern" is used when you want to check for the availability of some item (the Get entry) and if not, requeue the request to another entry (Get_Retry) where it'll wait until new items arrive. The pattern is often used for programming thread-safe resource managers.
In this pattern, the Get entry is always enabled (i.e. the guard never blocks) so requests are always allowed to enter and see if an item of interest is already available:
entry Get (Key : Map_Key; Data : out Data_Type)
when True -- Never blocking guard.
is
begin
if Data_Available (Key) then
Data := Data_Vector_Map (Key).Last_Element;
Data_Vector_Map (Key).Delete_Last;
else
requeue Get_Retry; -- No data available, try again later.
end if;
end Get;
If no item is available, then the request is requeued to the Get_Retry entry. This (private) entry has a guard that is unblocked by the Put subprogram. If an item arrives via Put, then Put will record the number of requests waiting for a retry, unblock the guard, and allow pending requests to see if the new item is of interest to them.
procedure Put (Key : Map_Key; Data : Data_Type) is
begin
Data_Vector_Map (Key).Append (Data);
-- If there are requests for data, then record the number
-- of requests that are waiting and open the guard of Get_Retry.
if Get_Retry'Count /= 0 then
Get_Retry_Requests_Left := Get_Retry'Count;
Get_Retry_Enabled := True;
end if;
end Put;
Once all pending requests are served once, Get_Retry will disable itself to prevent any request that were requeued again to itself to be served for a second time.
entry Get_Retry (Key : Map_Key; Data : out Data_Type)
when Get_Retry_Enabled -- Guard unblocked by Put.
is
begin
-- Set guard once all pending requests have been served once.
Get_Retry_Requests_Left := Get_Retry_Requests_Left - 1;
if Get_Retry_Requests_Left = 0 then
Get_Retry_Enabled := False;
end if;
-- Check if data is available, same logic as in Get.
if Data_Available (Key) then
Data := Data_Vector_Map (Key).Last_Element;
Data_Vector_Map (Key).Delete_Last;
else
requeue Get_Retry; -- No data available, try again later.
end if;
end Get_Retry;
Note: both entry families (as discussed in the answer of #egilhh), as well as this pattern were discussed in a recent AdaCore blogpost.
protected_map.ads
with Ada.Containers.Vectors;
with Ada.Containers.Hashed_Maps;
package Protected_Map is
use Ada.Containers;
type Map_Key is new Positive;
type Data_Type is new Integer;
function Data_Vector_Hash (Key : Map_Key) return Hash_Type is
(Hash_Type (Key));
package Data_Vectors is new Vectors
(Index_Type => Natural,
Element_Type => Data_Type);
package Data_Vector_Maps is new Hashed_Maps
(Key_Type => Map_Key,
Element_Type => Data_Vectors.Vector,
Hash => Data_Vector_Hash,
Equivalent_Keys => "=",
"=" => Data_Vectors."=");
protected Map is
procedure Create (Key : Map_Key);
procedure Delete (Key : Map_Key);
procedure Put (Key : Map_Key; Data : Data_Type);
entry Get (Key : Map_Key; Data : out Data_Type);
private
entry Get_Retry (Key : Map_Key; Data : out Data_Type);
Get_Retry_Requests_Left : Natural := 0;
Get_Retry_Enabled : Boolean := False;
Data_Vector_Map : Data_Vector_Maps.Map;
end Map;
end Protected_Map;
protected_map.adb
package body Protected_Map is
protected body Map is
------------
-- Create --
------------
procedure Create (Key : Map_Key) is
begin
Data_Vector_Map.Insert (Key, Data_Vectors.Empty_Vector);
end Create;
------------
-- Delete --
------------
procedure Delete (Key : Map_Key) is
begin
Data_Vector_Map.Delete (Key);
end Delete;
---------
-- Put --
---------
procedure Put (Key : Map_Key; Data : Data_Type) is
begin
Data_Vector_Map (Key).Append (Data);
-- If there are requests for data, then record the number
-- of requests that are waiting and unblock the guard of Get_Retry.
if Get_Retry'Count /= 0 then
Get_Retry_Requests_Left := Get_Retry'Count;
Get_Retry_Enabled := True;
end if;
end Put;
--------------------
-- Data_Available --
--------------------
function Data_Available (Key : Map_Key) return Boolean is
begin
return Data_Vector_Map.Contains (Key) and then
not Data_Vector_Map (Key).Is_Empty;
end Data_Available;
---------
-- Get --
---------
entry Get (Key : Map_Key; Data : out Data_Type)
when True -- No condition.
is
begin
if Data_Available (Key) then
Data := Data_Vector_Map (Key).Last_Element;
Data_Vector_Map (Key).Delete_Last;
else
requeue Get_Retry; -- No data available, try again later.
end if;
end Get;
---------------
-- Get_Retry --
---------------
entry Get_Retry (Key : Map_Key; Data : out Data_Type)
when Get_Retry_Enabled -- Guard unblocked by Put.
is
begin
-- Set guard once all pending requests have been served once.
Get_Retry_Requests_Left := Get_Retry_Requests_Left - 1;
if Get_Retry_Requests_Left = 0 then
Get_Retry_Enabled := False;
end if;
-- Check if data is available, same logic as in Get.
if Data_Available (Key) then
Data := Data_Vector_Map (Key).Last_Element;
Data_Vector_Map (Key).Delete_Last;
else
requeue Get_Retry; -- No data available, try again later.
end if;
end Get_Retry;
end Map;
end Protected_Map;
main.adb
with Ada.Text_IO; use Ada.Text_IO;
with Protected_Map;
procedure Main is
task Getter;
task body Getter is
Data : Protected_Map.Data_Type;
begin
Protected_Map.Map.Get (2, Data);
Put_Line (Data'Image);
Protected_Map.Map.Get (1, Data);
Put_Line (Data'Image);
Protected_Map.Map.Get (3, Data);
Put_Line (Data'Image);
Protected_Map.Map.Get (1, Data);
Put_Line (Data'Image);
end;
begin
Protected_Map.Map.Create (1);
Protected_Map.Map.Create (2);
Protected_Map.Map.Create (3);
Protected_Map.Map.Put (1, 10);
delay 0.5;
Protected_Map.Map.Put (1, 15);
delay 0.5;
Protected_Map.Map.Put (2, 20);
delay 0.5;
Protected_Map.Map.Put (3, 30);
end Main;
output
$ ./obj/main
20
15
30
10
I really would like to see how it's done, the compiler keeps assuming I have integer indexes and returns errors.
How to pass the following array:
countc: Array['a'..'z'] of Integer;
to a function?
In traditional Pascal, before you can pass something like your array to a function, you have to declare a type that your array is an instance of, like this
type
TSimpleArray = Array['A'..'Z', '0'..'9'] of integer;
var
ASimpleArray : TSimpleArray;
In other words, you can't specify the array's bounds in the definition of the function/procedure.
Once you've defined your array type like the above, you can declare a function (or procedure) that has a parameter of the defined type, like this:
function ArrayFunction(SimpleArray : TSimpleArray) : Integer;
var
C1,
C2 : Char;
begin
ArrayFunction := 0;
for C1 := 'A' to 'Z' do
for C2 := '0' to '9' do
ArrayFunction := ArrayFunction + SimpleArray[C1, C2];
end;
which obviously totals the contents of the array.
More modern Pascals like Delphi and FPC's ObjectPascals also support other ways of declaring an array-type parameter, but they have to be zero-based (which precludes the use of char indexes). Delphi and FPC also support the use of `Result' as an alias for the function name, as in
function ArrayFunction(SimpleArray : TSimpleArray) : Integer;
[...]
begin
Result := 0;
which saves time and effort if you rename a function or copy/paste it to define another function.
I'm kinda a Delphi-newbie and I don't get how the Sort method of a TList of Records is called in order to sort the records by ascending integer value.
I have a record like the following:
type
TMyRecord = record
str1: string;
str2: string;
intVal: integer;
end;
And a generic list of such records:
TListMyRecord = TList<TMyRecord>;
Have tried to find a code-example in the help files and found this one:
MyList.Sort(#CompareNames);
Which I can't use, since it uses classes. So I tried to write my own compare function with a little different parameters:
function CompareIntVal(i1, i2: TMyRecord): Integer;
begin
Result := i1.intVal - i2.intVal;
end;
But the compiler always throws a 'not enough parameters' - error when I call it with open.Sort(CompareIntVal);, which seems obvious; so I tried to stay closer to the help file:
function SortKB(Item1, Item2: Pointer): Integer;
begin
Result:=PMyRecord(Item1)^.intVal - PMyRecord(Item2)^.intVal;
end;
with PMyRecord as PMyRecord = ^TMyRecord;
I have tried different ways of calling a function, always getting some error...
The Sort overload you should be using is this one:
procedure Sort(const AComparer: IComparer<TMyRecord>);
Now, you can create an IComparer<TMyRecord> by calling TComparer<TMyRecord>.Construct. Like this:
var
Comparison: TComparison<TMyRecord>;
....
Comparison :=
function(const Left, Right: TMyRecord): Integer
begin
Result := Left.intVal-Right.intVal;
end;
List.Sort(TComparer<TMyRecord>.Construct(Comparison));
I've written the Comparison function as an anonymous method, but you could also use a plain old style non-OOP function, or a method of an object.
One potential problem with your comparison function is that you may suffer from integer overflow. So you could instead use the default integer comparer.
Comparison :=
function(const Left, Right: TMyRecord): Integer
begin
Result := TComparer<Integer>.Default.Compare(Left.intVal, Right.intVal);
end;
It might be expensive to call TComparer<Integer>.Default repeatedly so you could store it away in a global variable:
var
IntegerComparer: IComparer<Integer>;
....
initialization
IntegerComparer := TComparer<Integer>.Default;
Another option to consider is to pass in the comparer when you create the list. If you only ever sort the list using this ordering then that's more convenient.
List := TList<TMyRecord>.Create(TComparer<TMyRecord>.Construct(Comparison));
And then you can sort the list with
List.Sort;
The concise answer:
uses
.. System.Generics.Defaults // Contains TComparer
myList.Sort(
TComparer<TMyRecord>.Construct(
function(const Left, Right: TMyRecord): Integer
begin
Result := Left.intVal - Right.intVal;
end
)
);
I want to share my solution (based on the input I have gathered here).
It's a standard setup. A filedata class that holds data of a single file in a generic TObjectList. The list has the two private attributes fCurrentSortedColumn and fCurrentSortAscending to control the sort order. The AsString-method is the path and filename combined.
function TFileList.SortByColumn(aColumn: TSortByColums): boolean;
var
Comparison: TComparison<TFileData>;
begin
result := false;
Comparison := nil;
case aColumn of
sbcUnsorted : ;
sbcPathAndName: begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcSize : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<int64>.Default.Compare(Left.Size,Right.Size);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcDate : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<TDateTime>.Default.Compare(Left.Date,Right.Date);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcState : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<TFileDataTestResults>.Default.Compare(Left.FileDataResult,Right.FileDataResult);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
end;
if assigned(Comparison) then
begin
Sort(TComparer<TFileData>.Construct(Comparison));
// Control the sort order
if fCurrentSortedColumn = aColumn then
fCurrentSortAscending := not fCurrentSortAscending
else begin
fCurrentSortedColumn := aColumn;
fCurrentSortAscending := true;
end;
if not fCurrentSortAscending then
Reverse;
result := true;
end;
end;
I found a much simpler modified sort function to alphabetize a TList of records or nonstandard list of items.
Example
PList = ^TContact;
TContact = record //Record for database of user contact records
firstname1 : string[20];
lastname1 : string[20];
phonemobile : Integer; //Fields in the database for contact info
phonehome : Integer;
street1 : string;
street2 : string;
type
TListSortCompare = function (Item1,
Item2: TContact): Integer;
var
Form1: TForm1;
Contact : PList; //declare record database for contacts
arecord : TContact;
Contacts : TList; //List for the Array of Contacts
function CompareNames(i1, i2: TContact): Integer;
begin
Result := CompareText(i1.lastname1, i2.lastname1) ;
end;
and the function to call to sort your list
Contacts.Sort(#CompareNames);
--in the package
type t_array is array (natural range <>) of std_logic_vector (7 downto 0);
type p_array is access t_array;
--in my testbench
variable my_array : p_array := null;
begin
my_array := new t_array(0 to 19);
my_array := ( X"00",X"00",X"00",X"00",X"FF",
X"FF",X"FF",X"FF",X"00",X"00",
X"FF",X"FF",X"FF",X"FF",X"FF",
X"FF",X"FF",X"FF",X"FF",X"FF" );
Error: Target type util_lib.tb_pkg.p_array in variable assignment is different from expression type util_lib.tb_pkg.t_array.
How can I compactly assign all the elements of the array?
(1). Dereference your poincough access type.
my_array.all := (...);
(2) Initialise it from a function
begin
my_array := new_array(20);
The gory details of initialising it can be buried in the function, which could calculate the values algorithmically, copy them from a constant array, or even read the contents from file.
constant initialiser : t_array := (...);
function new_array(length : natural range initialiser'range) return t_array is
variable temp : p_array := new t_array(0 to length - 1);
begin
-- either this
for i in temp'range loop
temp(i) := initialiser(i);
end loop;
-- or simply this
temp.all := initialiser(temp'range);
return temp;
end new_array;
(note the constraint on arguments to new_array : that ensures it won't create an array larger than the initialiser.)
If you like one step, you can also do:
--in my testbench
variable my_array : p_array := null;
begin
my_array := new t_array'( X"00",X"00",X"00",X"00",X"FF",
X"FF",X"FF",X"FF",X"00",X"00",
X"FF",X"FF",X"FF",X"FF",X"FF",
X"FF",X"FF",X"FF",X"FF",X"FF" );
You can also to do this in an initialization:
--in my testbench
variable my_array : p_array := new t_array'(
X"00",X"00",X"00",X"00",X"FF",
X"FF",X"FF",X"FF",X"00",X"00",
X"FF",X"FF",X"FF",X"FF",X"FF",
X"FF",X"FF",X"FF",X"FF",X"FF" );
begin
Your error message:
Error: Target type util_lib.tb_pkg.p_array in variable assignment is different from expression type util_lib.tb_pkg.t_array.
tells us the subtype of the target doesn't match the right hand expression.
That can be cured several ways:
library ieee;
use ieee.std_logic_1164.all;
package initialize is
--in the package
type t_array is array (natural range <>) of std_logic_vector (7 downto 0);
type p_array is access t_array;
end package;
library ieee;
use ieee.std_logic_1164.all;
use work.initialize.all;
entity testbench is
end entity;
architecture fum of testbench is
begin
process
--in my testbench
variable my_array : p_array := null;
begin
my_array := new t_array(0 to 19);
my_array (my_array'range) := (
X"00",X"00",X"00",X"00",X"FF",
X"FF",X"FF",X"FF",X"00",X"00",
X"FF",X"FF",X"FF",X"FF",X"FF",
X"FF",X"FF",X"FF",X"FF",X"FF" );
wait;
end process;
end architecture;
here using a slice name with the range provided by my_array'range.
Without the range the target name is interpreted as an access type name. As a slice name with a discrete range the target name denotes the value of the object type denotes:
IEEE Std 1076-2008 8. Names 8.1 General Paragraph 3 - 4:
Certain forms of name (indexed and selected names, slice names, and attribute names) include a prefix that is a name or a function call. If the prefix of a name is a function call, then the name denotes an element, a slice, or an attribute, either of the result of the function call, or (if the result is an access value) of the object designated by the result. Function calls are defined in 9.3.4.
A prefix is said to be appropriate for a type in either of the following cases:
— The type of the prefix is the type considered.
— The type of the prefix is an access type whose designated type is the type considered.
Here it helps to understand designate is a synonym for denote used to describe the relationship between a value of an access type and the object it references.
paragraph 5:
The evaluation of a name determines the named entity denoted by the name. The evaluation of a name that has a prefix includes the evaluation of the prefix, that is, of the corresponding name or function call. If the type of the prefix is an access type, the evaluation of the prefix includes the determination of the object designated by the corresponding access value. In such a case, it is an error if the value of the prefix is a null access value. It is an error if, after all type analysis (including overload resolution), the name is ambiguous.
In this case you can use a slice name that encompasses the entire array.
You can use a selected name for the access type object designates:
architecture fie of testbench is
begin
process
variable my_array : p_array := null;
begin
my_array := new t_array(0 to 19);
my_array.all := ( X"00",X"00",X"00",X"00",X"FF",
X"FF",X"FF",X"FF",X"00",X"00",
X"FF",X"FF",X"FF",X"FF",X"FF",
X"FF",X"FF",X"FF",X"FF",X"FF" );
wait;
end process;
end architecture;
8.3 Selected names paragraph 5:
For a selected name that is used to denote the object designated by an access value, the suffix shall be the reserved word all. The prefix shall belong to an access type.
Using these methods distinguishes between assignment to an object of an access type (which isn't the type of the composite in the right hand expression) and the allocated object designated by the object of the access type.
I am running Lazarus 0.9.30.
I have a standard TStringGrid on a form and have a function that dynamically adds TGridColumns objects to it at run time. I have a collection of objects that contain all the attributes of each column (that I read out of a file at run time), and I want to associate each object with its corresponding column header.
I have tried the code below but at run time when I try to access the object behind the column header object, I get a 'nil object returned. I suspect the reason this is occurring is that the grid cell (that holds the column title) is blank, and you can't associate objects with grid cells that are empty.
type
TTmColumnTitles = class(TTmCollection)
public
constructor Create;
destructor Destroy; override;
function stGetHint(anIndex : integer) : string;
end;
type
TTmColumnTitle = class(TTmObject)
private
FCaption : string;
FCellWidth : integer;
FCellHeight : integer;
FFontOrientation : integer;
FLayout : TTextLayout;
FAlignment : TAlignment;
FHint : string;
procedure vInitialise;
public
property stCaption : string read FCaption write FCaption;
property iCellWidth : integer read FCellWidth write FCellWidth;
property iCellHeight : integer read FCellHeight write FCellHeight;
property iFontOrientation : integer read FFontOrientation write FFontOrientation;
property Layout : TTextLayout read FLayout write FLayout;
property Alignment : TAlignment read FAlignment write FAlignment;
property stHint : string read FHint write FHint;
constructor Create;
destructor Destroy; override;
end;
procedure TTmMainForm.vLoadGridColumnTitles
(
aGrid : TStringGrid;
aCollection : TTmColumnTitles
);
var
GridColumn : TGridColumn;
aColumnTitle : TTmColumnTitle; //Just a pointer!
anIndex1 : integer;
anIndex2 : integer;
begin
for anIndex1 := 0 to aCollection.Count - 1 do
begin
aColumnTitle := TTmColumnTitle(aCollection.Items[anIndex1]);
GridColumn := aGrid.Columns.Add;
GridColumn.Width := aColumnTitle.iCellWidth;
GridColumn.Title.Font.Orientation := aColumnTitle.iFontOrientation;
GridColumn.Title.Layout := aColumnTitle.Layout;
GridColumn.Title.Alignment := aColumnTitle.Alignment;
GridColumn.Title.Caption := aColumnTitle.stCaption;
aGrid.RowHeights[0] := aColumnTitle.iCellHeight;
aGrid.Objects[anIndex1, 0] := aColumnTitle;
end; {for}
end;
Just assigning an object to the Objects property isn't enough. You have to draw the title caption from that object yourself in an OnDrawCell event handler, or assign the Cells property as well.
and you can't associate objects with grid cells that are empty
Yes you can. The string and the object of one cell 'work' independent of each other.
So it should be:
for anIndex2 := 0 to aGrid.ColCount - 1 do
begin
aColumnTitle := aCollection.Items[anIndex2]; // Is aCollection.Count in sync
// with aGrid.ColCount??
aGrid.Cells[anIndex2, 0] := aColumnTitle.Caption;
aGrid.Objects[anIndex2, 0] := aColumnTitle;
end;