Overload TFormatSettings and incompatible types - delphi

i have this procedure:
procedure Initialize(out FormatSettings: TFormatSettings);
const
LongDayNamesEx : array [1..7] of string = ('Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica');
LongMonthNamesEx : array [1..12] of string = ('Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre');
begin
FormatSettings := TFormatSettings.Create;
with FormatSettings do
begin
LongDayNames := LongDayNamesEx;
LongMonthNames := LongMonthNamesEx;
end;
end;
And i get an error about incompatible types (E2008). How i can solve this problem? I don't want to use something as:
LongDayNames[1] := 'Lunedì';
LongDayNames[2] := 'Martedì';
...
LongDayNames[7] := 'Domenica';
LongMonthNames[1] := 'Gennaio';
LongMonthNames[2] := 'Febbraio';
...
LongMonthNames[12] := 'Dicembre';
if not stricly necessary.
Thanks for help.

You can do like this:
type
TDayNameArray = array[1..7] of string;
const
LongDayNamesEx: TDayNameArray = ('Måndag', 'Tisdag', 'Onsdag', 'Torsdag',
'Fredag', 'Lördag', 'Söndag');
var
fs: TFormatSettings;
begin
TDayNameArray(fs.LongDayNames) := LongDayNamesEx;

Andreas gave you a good answer to the direct question that you asked.
Taking a different approach, I think you could solve your problem more easily by passing the locale when you initlialise the object. For example:
FormatSettings := TFormatSettings.Create('it-IT');
for Italian. Then the system will fill out the locale specific settings, day names, month names etc.
Or perhaps you would use the overload that takes a locale ID is more appropriate. No matter, you surely get the idea.

To answer the question you asked directly, the obvious solution is to use a for loop. Combine a record helper and open array parameters to make it more easily called:
type
TTFormatSettingsHelper = record helper for TFormatSettings
procedure SetLongDayNames(const Values: array of string);
end;
procedure TTFormatSettingsHelper.SetLongDayNames(const Values: array of string);
var
Index: Integer;
Value: string;
begin
Assert(high(Values)-low(Values)
= high(Self.LongDayNames)-low(Self.LongDayNames));
Index := low(Self.LongDayNames);
for Value in Values do
begin
Self.LongDayNames[Index] := Value;
inc(Index);
end;
end;
And then to call this you simply write:
FormatSettings.SetLongDayNames(['Lunedì', 'Martedì', 'Mercoledì', 'Giovedì',
'Venerdì', 'Sabato', 'Domenica']);

Related

Strings getting corrupted in ComboBox.AddObject. How to add them the proper way?

I'm adding strings with objects (also strings) to a TComboBox, but getting corrupted strings when trying to retrieve them later.
This is how I'm adding them:
var
i: Integer;
sl: TStringList;
c: Integer;
s: PChar;
begin
for i := 1 to tblCalls.FieldCount do
if tblCalls.Fields[i - 1].Tag = 1 then
ListBox1.Items.Append(tblCalls.Fields[i - 1].FieldName);
sl := TStringList.Create;
try
LoadStyles(TStrings(sl));
for c := 0 to sl.Count - 1 do
begin
s := PChar(sl.Values[sl.Names[c]]);
ComboBox1.Items.AddObject(sl.Names[c], TObject(s));
end;
finally
sl.Free;
end;
end;
procedure LoadStyles(var AStylesList: TStrings);
var
f, n: String;
filelist: TStringDynArray;
begin
f := ExtractFilePath(ParamStr(0)) + 'Styles';
if (not DirectoryExists(f)) then
Exit;
filelist := TDirectory.GetFiles(f);
for f in filelist do
begin
n := ChangeFileExt(ExtractFileName(f), EmptyStr);
AStylesList.Add(n + '=' + f);
end;
end;
..and this is where I'm trying to retrieve a string object:
procedure TfrmOptions.ComboBox1Change(Sender: TObject);
var
si: TStyleInfo;
i: Integer;
s: String;
begin
i := TComboBox(Sender).ItemIndex;
s := PChar(TComboBox(Sender).Items.Objects[i]);
Showmessage(s); // --> Mostly shows a corrupted string (gibberish chars)
if (TStyleManager.IsValidStyle(s, si)) then
begin
if (not MatchStr(s, TStyleManager.StyleNames)) then
TStyleManager.LoadFromFile(s);
TStyleManager.TrySetStyle(si.Name);
end;
end;
I suspect that its something with the way I'm adding them. Perhaps I need to allocate memory at:
s := PChar(sl.Values[sl.Names[c]]);
Not sure. Looking at the help on StrNew, NewStr and StrAlloc, it says that those functions are deprecated. Can you help point out whats wrong?
There's nothing to keep the string alive. When you write:
s := PChar(sl.Values[sl.Names[c]]);
an implicit local variable of type string is created to hold whatever sl.Values[sl.Names[c]] evaluates to. That local variable goes out of scope, as far as the compiler is aware, nothing references it, and the string object is destroyed.
In fact, it's even worse than that. Because the assignment above happens in a loop, there is only one implicit local variable. Each time round the loop, the string that you asked the combo box to remember is destroyed.
You need to find a way to extend the lifetime of the string. You could do it like this:
var
StrPtr: ^string;
....
for c := 0 to sl.Count - 1 do
begin
New(StrPtr);
StrPtr^ := sl.Values[sl.Names[c]];
ComboBox1.Items.AddObject(sl.Names[c], TObject(StrPtr));
end;
Then when you need to access the string you can do so like this:
var
StrPtr: ^string;
....
TObject(StrPtr) := TComboBox(Sender).Items.Objects[i];
// do something with StrPtr^
When you clear the combo box you must also run through each item and call Dispose on the pointer.
Having said that, it's going to be much easier not to do it that way. Stop trying to force strings into the TObject data associated with each item. Instead keep a parallel string list containing these strings. When you need to look up a name look it up in that list rather than in the combo box.
I know this is an old question but I came across this problem again and rather than use the separate string list I used an object with a string value (I think someone suggested it in a comment) as follows:
Declare a type as TObject with a string value:
TStringObject = class(TObject)
StringValue : string;
end;
Then when adding your items declare a local var of TStringObject and create a new instance for each item:
var
strObj : TStringObject
begin
...
for c := 0 to sl.Count - 1 do
begin
strObj := TStringObject.Create;
strObj.StringValue := sl.Values[sl.Names[c]];
ComboBox1.Items.AddObject(sl.Names[c], strObj);
end;
And when retrieving the values:
s := TStringObject(TComboBox(Sender).Items.Objects[i]).StringValue;
As #Dejan Dozet mentions in the comments - you should always free the data objects before freeing the TStringList!

thousand separator in integer values [duplicate]

var i : integer;
i := 1234567;
Given the above, I want the string "1,234,567" as output (assuming UK locale). IntToStr just gives me "1234567". I'm sure there's a one-liner for this, but I can't find it...
Try the format function.
Label1.Caption := Format('%.0n', [i + 0.0]);
Or if you need to be threadsafe or want to ensure you use the system default locale or want to specify one:
function FormatIntFromLCID(const AValue: Integer; const LCID: Integer = LOCALE_SYSTEM_DEFAULT): string;
var
AFormatSettings: TFormatSettings;
begin
GetLocaleFormatSettings(LCID, AFormatSettings);
Result := FormatFloat('#,##0',AValue, AFormatSettings);
end;
see this post for a more complete discussion about formatting/locales
s := FormatFloat('#,##0', i);
I have this function to do it, where d means perhaps decimal number:
function dn(i: integer): string;
begin
result := format('%.0n', [i.ToDouble])
end;
Format('%n', [12345.678]);
stringreplace(format('%n',[1234567.0]),'.00','',[]);

How can I turn a list of properties into a stringlist?

I have a component with a lot of properties, many of which are types.
For example:
BackgroundStyle = [bsSolid, bsGradient, bsNone]
BorderStyle = [bsNone, bsSingle, bsWide]
I am building a form to allow the user to configure these properties at runtime and I would like to populate some dropdown lists dynamically, rather than having to type all of them in by hand.
Is this possible? Thanks!
Use RTTI for that. Specifically, look at the GetPropInfo() and GetEnumName() functions in the TypInfo unit.
Remy is on the ball with this one. Lately I just happen to do something similar and a bit of refactoring (within a text editor, so you mileage may vary with the complier):
class function TEnumerationRoutines.TitleCaseDescriptionFromOptions<T>: TStrings;
var
LRttiContext : TRttiContext;
LRttiEnumerationType: TRttiEnumerationType;
LTypeInfo : Pointer;
LPTypeInfo : PTypeInfo;
lp: Integer;
begin
LTypeInfo := TypeInfo(T);
LPTypeInfo := PTypeInfo(LTypeInfo);
if LPTypeInfo^.Kind <> tkEnumeration then
raise Exception.Create('Type is not an enum');
Result := TStringList.Create;
LRttiEnumerationType := LRttiContext.GetType(LTypeInfo) as TRttiEnumerationType;
for lp := LRttiEnumerationType.MinValue to LRttiEnumerationType.MaxValue do
Result.Add(GetEnumName(LTypeInfo, Ord(lp)));
end;
and call it with:
MyStrings := TEnumerationRoutines.TitleCaseDescriptionFromOptions<BackgroundStyle>;
or
MyStrings := TEnumerationRoutines.TitleCaseDescriptionFromOptions<BorderStyle>;

"descending" records in delphi?

I know you can't actually descend anything from a record, but I'm not sure how to summarize my problem in one sentence. Edit the title if you do.
What I want to do here is make an array of some generic type, which can be one of X number of types, the array would be filled with those custom types (they have different fields and that's what is important). The easy way is to make just an array of variant records, each variant has it's own type, but obviously can't redeclare identifiers like so:
GenericRec = Record
case SubTypeName: TSubTypeName of
type1name: (SubRec: Type1);
type2name: (SubRec: Type2);
...
typeNname: (SubRec: TypeN);
end;
Changing SubRec to SubRec1, SubRec2... SubRecN makes referencing painful, but not impossible.
And since I started looking for alternative solutions to the above problem, classes came to mind.
The obvious example to demonstrate what I am trying to achieve is TObject, an array of those can be assigned to many different things. That's what I want, but with records (and that's impossible to do), because I want to be able to save the records to file as well as read them back (also because it's something I'm already familiar with). Making my own simple class is not a problem, making a descendant class from that to represent my subtype - I can do that. But what about writing that to file and reading it back? This boils down to serialization, which I have no idea how to do. From what I gather it's not as easy and the class must be descended from TComponent.
TMyClass = Class
Does it make any difference if I make the class like above? It's nothing fancy and has at most 10 fields, including a few custom types.
Setting serialization aside (just because I have a lot of reading to do on that topic), use of classes here also might be out of the question.
At this point, what are my options? Should I abandon records and try this with classes? Or would it be a lot less complicated just to stick to records and deal with the variant "limitation"? I'm all about learning and if exploding the class approach might make me smarter, I'll do it. I've also just looked into TList too (never used it), but it seems that it doesn't mix too well with records, well maybe it can be done, but that might be out of my league at the moment. I'm open to any kind of suggestions. What do i do?
You're conflating serialization with "writing everything to disk with a single BlockWrite call." You can serialize anything you want, regardless of whether it descends from TComponent or TPersistent.
Although writing everything with a single BlockWrite call looks convenient at first, you'll quickly find it's not really what you want if your desired record types are going to store anything particularly interesting (like strings, dynamic arrays, interfaces, objects, or other reference- or pointer-based types).
You'll probably also find variant records unsatisfying since you'll be coding to the lowest common denominator. You won't be able to access anything in the record without checking the actual contained type, and the size of even the smallest amount of data will occupy the same amount of space as the largest data type.
The question seems to describe polymorphism, so you may as well embrace what the language already provides for that. Use an array (or list, or any other container) of objects. Then you can use virtual methods to treat them all uniformly. You can implement dynamic dispatch for records if you want (e.g., give each record a function pointer that refers to a function that knows how to deal with that record's contained data type), but in the end you'll probably just find yourself reinventing classes.
The "natural" way of handling such data is to use a class, and not a record. It will be much easier to work with, both at definition time and when dealing with implementation: in particular, virtual methods are very powerful to customize a process for a particular kind of class. Then use a TList/TObjectList or a TCollection, or a generic-based array in newer versions of Delphi to store the list.
About serialization, there are several ways to do it. See Delphi: Store data in somekind of structure
In your particular case, the difficulty comes from the "variant" kind of record you are using. IMHO the main drawback is that the compiler will refuse to set any reference-counted kind of variable (e.g. a string) within the "variant" part. So you'll be able to write only "plain" variables (like integer) within this "variant" part. A big limitation IMHO, which reduces the interest of this solution.
Another possibility could be to store the kind of record at the beginning of its definition, e.g. with a RecType: integer or even better with a RecType: TEnumerationType which will be more explicit than a number. But you'll have to write a lot of code by hand, and works with pointers, which is a bit error-prone if you are not very fluent with pointer coding.
So you can also store the type information of the record, accessible via TypeInfo(aRecordVariable). Then you can use FillChar to initialize the record content to zero, just after allocation, then use the following function to finalize the record content, just after disallocation (this is what Dispose() does internally, and you shall call it, otherwise you'll leak memory):
procedure RecordClear(var Dest; TypeInfo: pointer);
asm
jmp System.#FinalizeRecord
end;
But such an implementation pattern will just reinvent the wheel! It is in fact how class is implemented: the first element of any TObject instance is a pointer to its ClassType:
function TObject.ClassType: TClass;
begin
Pointer(Result) := PPointer(Self)^;
end;
There is also another structure in Delphi, which is called object. It is some kind of record, but it supports inheritance - see this article. It is the old style of OOP programming in Turbo Pascal 5.5 days, deprecated, but still available. Note that I discovered a weird compilation issue on newer versions of Delphi: sometimes, an object allocated on the stack is not always initialized.
Take a look at our TDynArray wrapper and its associated functions, who is able to serialize any record content, into binary or JSON. See Delphi (win32) serialization libraries question. It will work with variant records, even if they include a string in their unvariant part, whereas a plain "Write/BlockWrite" won't work with reference counted fields.
To do this with records, you would create different record types that have a common field(s) in front, and then put those same field(s) in the generic record. Then you can simply type-cast a pointer to a generic record to a pointer to a specific record when needed. For example:
type
PGenericRec = ^GenericRec;
GenericRec = Record
RecType: Integer;
end;
PType1Rec = ^Type1Rec;
Type1Rec = Record
RecType: Integer;
// Type1Rec specific fields...
end;
PType2Rec = ^Type2Rec;
Type2Rec = Record
RecType: Integer;
// Type2Rec specific fields...
end;
PTypeNRec = ^TypeNRec;
TypeNRec = Record
RecType: Integer;
// TypeNRec specific fields...
end;
var
Recs: array of PGenericRec;
Rec1: PType1Rec;
Rec2: PType2Rec;
RecN: PTypeNRec;
I: Integer;
begin
SetLength(Recs, 3);
New(Rec1);
Rec1^.RecType := RecTypeForType1Rec;
// fill Rec1 fields ...
Recs[0] := PGenericRec(Rec1);
New(Rec2);
Rec2^.RecType := RecTypeForType2Rec;
// fill Rec2 fields ...
Recs[1] := PGenericRec(Rec2);
New(RecN);
Rec3^.RecType := RecTypeForTypeNRec;
// fill RecN fields ...
Recs[2] := PGenericRec(RecN);
for I := 0 to 2 do
begin
case Recs[I]^.RecType of
RecTypeForType1Rec: begin
Rec1 := PType1Rec(Recs[I]);
// use Rec1 as needed...
end;
RecTypeForType1Re2: begin
Rec2 := PType2Rec(Recs[I]);
// use Rec2 as needed...
end;
RecTypeForTypeNRec: begin
RecN := PTypeNRec(Recs[I]);
// use RecN as needed...
end;
end;
end;
for I := 0 to 2 do
begin
case Recs[I]^.RecType of
RecTypeForType1Rec: Dispose(PType1Rec(Recs[I]));
RecTypeForType2Rec: Dispose(PType2Rec(Recs[I]));
RecTypeForTypeNRec: Dispose(PTypeNRec(Recs[I]));
end;
end;
end;
As for serialization, you do not need TComponent for that. You can serialize records, you just have to do it manually. For writing, write out the RecType value first, then write out the record-specific values next. For reading, read the RecType value first, then create the appropriate record type for that value, then read the record-specific values into it.:
interface
type
PGenericRec = ^GenericRec;
GenericRec = Record
RecType: Integer;
end;
NewRecProc = procedure(var Rec: PGenericRec);
DisposeRecProc = procedure(Rec: PGenericRec);
ReadRecProc = procedure(Rec: PGenericRec);
WriteRecProc = procedure(const Rec: PGenericRec);
function NewRec(ARecType: Integer): PGenericRec;
procedure DisposeRec(var Rec: PGenericRec);
procedure ReadRec(Rec: PGenericRec);
procedure WriteRec(const Rec: PGenericRec);
procedure RegisterRecType(ARecType: Integer; ANewProc: NewRecProc; ADisposeProc: DisposeRecProc; AReadproc: ReadRecFunc; AWriteProc: WriteRecProc);
implementation
type
TRecTypeReg = record
RecType: Integer;
NewProc: NewRecProc;
DisposeProc: DisposeRecProc;
ReadProc: ReadRecProc;
WriteProc: WriteRecProc;
end;
var
RecTypes: array of TRecTypeReg;
function NewRec(ARecType: Integer): PGenericRec;
var
I: Integer;
begin
Result := nil;
for I = Low(RecTypes) to High(RecTypes) do
begin
with RecTypes[I] do
begin
if RecType = ARecType then
begin
NewProc(Result);
Exit;
end;
end;
end;
raise Exception.Create('RecType not registered');
end;
procedure DisposeRec(var Rec: PGenericRec);
var
I: Integer;
begin
for I = Low(RecTypes) to High(RecTypes) do
begin
with RecTypes[I] do
begin
if RecType = Rec^.RecType then
begin
DisposeProc(Rec);
Rec := nil;
Exit;
end;
end;
end;
raise Exception.Create('RecType not registered');
end;
procedure ReadRec(var Rec: PGenericRec);
var
LRecType: Integer;
I: Integer;
begin
Rec := nil;
LRecType := ReadInteger;
for I = Low(RecTypes) to High(RecTypes) do
begin
with RecTypes[I] do
begin
if RecType = LRecType then
begin
NewProc(Rec);
try
ReadProc(Rec);
except
DisposeProc(Rec);
raise;
end;
Exit;
end;
end;
end;
raise Exception.Create('RecType not registered');
end;
procedure WriteRec(const Rec: PGenericRec);
var
I: Integer;
begin
for I = Low(RecTypes) to High(RecTypes) do
begin
with RecTypes[I] do
begin
if RecType = Rec^.RecType then
begin
WriteInteger(Rec^.RecType);
WriteProc(Rec);
Exit;
end;
end;
end;
raise Exception.Create('RecType not registered');
end;
procedure RegisterRecType(ARecType: Integer; ANewProc: NewRecProc; ADisposeProc: DisposeRecProc; AReadproc: ReadRecFunc; AWriteProc: WriteRecProc);
begin
SetLength(RecTypes, Length(RecTypes)+1);
with RecTypes[High(RecTypes)] do
begin
RecType := ARecType;
NewProc := ANewProc;
DisposeProc := ADisposeProc;
ReadProc := AReadProc;
WriteProc := AWriteProc;
end;
end;
end.
.
type
PType1Rec = ^Type1Rec;
Type1Rec = Record
RecType: Integer;
Value: Integer;
end;
procedure NewRec1(var Rec: PGenericRec);
var
Rec1: PType1Rec;
begin
New(Rec1);
Rec1^.RecType := RecTypeForType1Rec;
Rec := PGenericRec(Rec1);
end;
procedure DisposeRec1(Rec: PGenericRec);
begin
Dispose(PType1Rec(Rec));
end;
procedure ReadRec1(Rec: PGenericRec);
begin
PType1Rec(Rec)^.Value := ReadInteger;
end;
procedure WriteRec1(const Rec: PGenericRec);
begin
WriteInteger(PType1Rec(Rec)^.Value);
end;
initialization
RegisterRecType(RecTypeForType1Rec, #NewRec1, #DisposeRec1, #ReadRec1, #WriteRec1);
.
type
PType2Rec = ^Type2Rec;
Type2Rec = Record
RecType: Integer;
Value: Boolean;
end;
procedure NewRec2(var Rec: PGenericRec);
var
Rec2: PType2Rec;
begin
New(Rec2);
Rec2^.RecType := RecTypeForType2Rec;
Rec := PGenericRec(Rec2);
end;
procedure DisposeRec2(Rec: PGenericRec);
begin
Dispose(PType2Rec(Rec));
end;
procedure ReadRec2(Rec: PGenericRec);
begin
PType2Rec(Rec)^.Value := ReadBoolean;
end;
procedure WriteRec2(const Rec: PGenericRec);
begin
WriteBoolean(PType2Rec(Rec)^.Value);
end;
initialization
RegisterRecType(RecTypeForType2Rec, #NewRec2, #DisposeRec2, #ReadRec2, #WriteRec2);
.
type
PTypeNRec = ^Type2Rec;
TypeNRec = Record
RecType: Integer;
Value: String;
end;
procedure NewRecN(var Rec: PGenericRec);
var
RecN: PTypeNRec;
begin
New(RecN);
RecN^.RecType := RecTypeForTypeNRec;
Rec := PGenericRec(RecN);
end;
procedure DisposeRecN(Rec: PGenericRec);
begin
Dispose(PTypeNRec(Rec));
end;
procedure ReadRecN(Rec: PGenericRec);
begin
PTypeNRec(Rec)^.Value := ReadString;
end;
procedure WriteRecN(const Rec: PGenericRec);
begin
WriteString(PTypeNRec(Rec)^.Value);
end;
initialization
RegisterRecType(RecTypeForTypeNRec, #NewRecN, #DisposeRecN, #ReadRecN, #WriteRecN);
.
var
Recs: array of PGenericRec;
procedure CreateRecs;
begin
SetLength(Recs, 3);
NewRec1(Recs[0]);
PRecType1(Recs[0])^.Value : ...;
NewRec2(Recs[1]);
PRecType2(Recs[1])^.Value : ...;
NewRecN(Recs[2]);
PRecTypeN(Recs[2])^.Value : ...;
end;
procedure DisposeRecs;
begin
for I := 0 to High(Recs) do
DisposeRec(Recs[I]);
SetLength(Recs, 0);
end;
procedure SaveRecs;
var
I: Integer;
begin
WriteInteger(Length(Recs));
for I := 0 to High(Recs) do
WriteRec(Recs[I]);
end;
procedure LoadRecs;
var
I: Integer;
begin
DisposeRecs;
SetLength(Recs, ReadInteger);
for I := 0 to High(Recs) do
ReadRec(Recs[I]);
end;

Delphi: Types other than Integer for indexing TStringList items

Arrays can be indexed using user-defined enumerated types. For example:
type
TIndexValue = (ZERO = 0, ONE, TWO, THREE, FOUR);
var
MyArray: array[Low(TIndexValue) .. High(TIndexValue)] of String;
Elements from this array can then be referenced using TIndexValue values as an index:
MyArray[ZERO] := 'abc';
I am trying to obtain this same general functionality with a TStringList.
One simple solution is to cast every index value to an Integer type at the time of reference:
MyStringList[Integer(ZERO)] := 'abc';
Another solution (to hide all the casting) is to create a subclass of TStringList and defer all the casting to this subclass's subroutines that access the inherited Strings property:
type
TIndexValue = (ZERO = 0, ONE, TWO, THREE, FOUR);
type
TEIStringList = class(TStringList)
private
function GetString(ItemIndex: TIndexValue): String;
procedure SetString(ItemIndex: TIndexValue; ItemValue: String);
public
property Strings[ItemIndex: TIndexValue]: String
read GetString write SetString; default;
end;
function TEIStringList.GetString(ItemIndex: TIndexValue): String;
begin
Result := inherited Strings[Integer(ItemIndex)];
end;
procedure TEIStringList.SetString(ItemIndex: TIndexValue; ItemValue: String);
begin
inherited Strings[Integer(ItemIndex)] := ItemValue;
end;
This works fine for a single implementation that uses the enumerated type TIndexValue.
However, I would like to re-use this same logic or subclass for several different TStringList objects that are indexed by different enumerated types, without having to define TStringList subclasses for each possible enumerated type.
Is something like this possible? I suspect I may have to depend on Delphi's Generics, but I would be very interested to learn that there are simpler ways to achieve this.
I think that generics would be by far the most elegant solution. Using them would be as simple as rewriting your class above as:
TEIStringList<T> = class(TStringList)
and then replacing all TIndexValue references with T. Then you could create it just as any other generic:
var
SL: TEIStringList<TIndexValue>;
begin
SL:=TEIStringList<TIndexValue>.Create;
(...)
ShowMessage(SL[ZERO])
(...)
end;
If you insist on avoiding generics, maybe operator overloading would be of use. Something like the following should work:
type
TIndexValueHolder = record
Value : TIndexValue;
class operator Implicit(A: TMyRecord): integer;
end;
(...)
class operator TIndexValueHolder.Implicit(A: TMyRecord): integer;
begin
Result:=Integer(A);
end;
Then use with:
var
Inx : TIndexValueHolder;
begin
Inx.Value:=ZERO;
ShowMessage(SL[Inx]);
end
UPDATE:
You could adapt TIndexValueHolder for use in a for or while loop by adding Next, HasNext, etc. methods. This might end defeating the purpose, though. I'm still not sure what the purpose is, or why this would be useful, but here's some ideas for how to do it, anyways.
You probably can use a class helper and declare the default property index as Variant:
type
TEnum1 = (Zero = 0, One, Two, Three, Four);
TEnum2 = (Nul = 0, Een, Twee, Drie, Vier);
TEnum3 = (Gds = 0, Psajs, Oeroifd, Vsops, Wowid);
TStringListHelper = class helper for TStringList
private
function GetString(Index: Variant): String;
procedure SetString(Index: Variant; const Value: String);
public
property Strings[Index: Variant]: String read GetString write SetString;
default;
end;
function TStringListHelper.GetString(Index: Variant): String;
begin
Result := inherited Strings[Index];
end;
procedure TStringListHelper.SetString(Index: Variant; const Value: String);
begin
inherited Strings[Index] := Value;
end;
Testing code:
procedure TForm1.Button1Click(Sender: TObject);
var
Strings: TStringList;
begin
Strings := TStringList.Create;
try
Strings.Add('Line 1');
Strings.Add('Second line');
Strings[Zero] := 'First line';
Memo1.Lines.Assign(Strings);
Caption := Strings[Psajs];
finally
Strings.Free;
end;
end;
See edit history for a previous less successful attempt.

Resources