I am trying to work with an array of colors that is a constant. But Delphi keeps giving me compiling errors. I can't seem to figure out what I'm doing wrong...
const
Statuses : array[0..3] of TAlphaColors =
(
TAlphaColors.Lightgray, //error here: says insert a '(', even though I already have one
TAlphaColors.Yellow,
TAlphaColors.Limegreen,
TAlphaColors.Blue
);
The problem you face is that TAlphaColor.Lightgray, and indeed all the other TAlphaColor.XXX that you name are ordinal true constant. Whereas TAlphaColors is a record type.
Let's take a look at the relevant definitions:
type
TAlphaColor = type Cardinal;
TAlphaColorRec = record
const
Alpha = TAlphaColor($FF000000);
Aliceblue = Alpha or TAlphaColor($F0F8FF);
Antiquewhite = Alpha or TAlphaColor($FAEBD7);
.... many more color constant omitted
constructor Create(const Color: TAlphaColor);
class var ColorToRGB: function (Color: TAlphaColor): Longint;
case LongWord of
0:
(Color: TAlphaColor);
2:
(HiWord, LoWord: Word);
3:
{$IFDEF BIGENDIAN}
(A, R, G, B: System.Byte);
{$ELSE}
(B, G, R, A: System.Byte);
{$ENDIF}
end;
So the constants are not of type TAlphaColorRec. Indeed it's one of the great frustrations of the language that you cannot declare nested constants in a record that are of that record type. These constants are ordinal true constants.
Note that the record itself has data in a variant part of the record. And the field of interest is the Color field. So, you could declare your constant array like so:
const
Statuses : array[0..3] of TAlphaColors = (
(Color: TAlphaColors.Lightgray),
(Color: TAlphaColors.Yellow),
(Color: TAlphaColors.Limegreen),
(Color: TAlphaColors.Blue)
);
If only Embarcadero had had the foresight to allow us to write code like this:
type
TMyRecord = record
public
const
MyConst: TMyRecord = ();
end;
Related
This question already has answers here:
Why do I get "type has no typeinfo" error with an enum type
(3 answers)
Closed 7 years ago.
One can use something like this to convert an enum to string:
uses
TypInfo;
type
Language = (Delphi,Delphi_Prism,CBuilder);
var
StrLanguage : String;
begin
StrLanguage := GetEnumName(TypeInfo(Language),integer(Delphi)) ;
end;
(taken from theroadtodelphi)
Is it possible to do the same for an enum that has custom values?
Something like this:
type
THotkey = (hkShift= 1, hkSpace= 3, hkEnter= 6);
As a workaround i am using placeholders to skip not used enums.
However that's not nice and is problematic if i have to skip huge gaps.
type
THotkeys = (hkShift, hkUnused1, hkSpace, hkUnused2, hkUnused3, hkEnter);
In your specific use case, you could use an array that relates to the enum, because enumerated constants with a specific value do not have RTTI as stated in the documentation:
Enumerated constants without a specific value have RTTI:
type SomeEnum = (e1, e2, e3);
whereas enumerated constants with a specific value, such as the
following, do not have RTTI:
type SomeEnum = (e1 = 1, e2 = 2, e3 = 3);
You can work around this like so:
type
THotkey = (hkShift, hkSpace, hkEnter);
THotkeyValues: array[Thotkey] of Integer = (1,3,6);
usage:
ShiftKeyValue := THotkeyValues[hkShift];
The answer is just in your example (if I've understood what you are asking)
type
THotkeys = (hkShift, hkUnused1, hkSpace, hkUnused2, hkUnused3, hkEnter);
....
var
StrLanguage : String;
begin
StrLanguage := GetEnumName(TypeInfo(THotkeys),integer(hkSpace)) ;
ShowMessage(IntToStr(integer(hkSpace)) + ' - ' + StrLanguage);
the result will be:
2 - hkSpace
I'd like to declare a type like this:
type
TDynMatrix<T> = TArray<TArray<T>>;
The compiler rejects this with:
[dcc32 Error] E2508 Type parameters not allowed on this type
I wondered whether the issue was related to the nesting of generics. But it seems not:
type
TDynArray<T> = TArray<T>;//pointless type I know, but for the sake of the Q
also results in the same compiler error.
The documentation for the compiler error left me knowing perhaps even less than I knew before I read it:
E2508 type parameters not allowed on this type (Delphi)
When using class references, you cannot use generic classes directly.
You need to use a wrapper class to be able to use generics.
program E2508;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyClass = class
end;
TMyClassClass<T> = class of TMyClass;
begin
Writeln('FAIL - E2508 type parameters not allowed on this type');
end.
Can anyone explain why I cannot declare generic types in this way?
Your code is invalid because you cannot re-declare a generic type as open generic type.
I would declare this as:
type
TDynMatrix<T> = array of TArray<T>;
This way you still have compatibility that you probably need: the elements of one dimension to a TArray<T>.
So then you can write
var
matrix: TDynMatrix<Integer>;
values: TArray<Integer>;
....
SetLength(matrix, 2, 2);
values := matrix[0];
values := Copy(matrix[1]);
etc.
Sorry no reasons, but this one is not limited to TArray,
Using the following type definitions:
TGenClass<T> = class fx: T; end;
TGenArray<T> = array of T;
TGenRecord<T> = record fx: T; end;
TGenProc<T> = procedure(const A: T);
I have tried all combinations:
TGenClassClass<T> = TGenClass<TGenClass<T>>;
TGenClassArray<T> = TGenClass<TGenArray<T>>;
TGenClassRecord<T> = TGenClass<TGenClass<T>>;
TGenClassProc<T> = TGenClass<TGenClass<T>>;
TGenArrayClass<T> = TGenArray<TGenClass<T>>;
TGenArrayArray<T> = TGenArray<TGenArray<T>>;
TGenArrayRecord<T> = TGenArray<TGenClass<T>>;
TGenArrayProc<T> = TGenArray<TGenClass<T>>;
TGenRecordClass<T> = TGenRecord<TGenClass<T>>;
TGenRecordArray<T> = TGenRecord<TGenArray<T>>;
TGenRecordRecord<T> = TGenRecord<TGenClass<T>>;
TGenRecordProc<T> = TGenRecord<TGenClass<T>>;
TGenProcClass<T> = TGenProc<TGenClass<T>>;
TGenProcArray<T> = TGenProc<TGenArray<T>>;
TGenProcRecord<T> = TGenProc<TGenClass<T>>;
TGenProcProc<T> = TGenClass<TGenProc<T>>;
They all fail.
You can declare types with complex type expressions, if those types are not generic themself:
TClassClass = TGenClass<TGenClass<Integer>>;
TClassArray = TGenClass<TGenArray<Integer>>;
TClassRecord = TGenClass<TGenClass<Integer>>;
TClassProc = TGenClass<TGenClass<Integer>>;
TArrayClass = TGenArray<TGenClass<Integer>>;
TArrayArray = TGenArray<TGenArray<Integer>>;
TArrayRecord = TGenArray<TGenClass<Integer>>;
TArrayProc = TGenArray<TGenClass<Integer>>;
TRecordClass = TGenRecord<TGenClass<Integer>>;
TRecordArray = TGenRecord<TGenArray<Integer>>;
TRecordRecord = TGenRecord<TGenClass<Integer>>;
TRecordProc = TGenRecord<TGenClass<Integer>>;
TProcClass = TGenProc<TGenClass<Integer>>;
TProcArray = TGenProc<TGenArray<Integer>>;
TProcRecord = TGenProc<TGenClass<Integer>>;
TProcProc = TGenClass<TGenProc<Integer>>;
There is a circumvention. You can declare the type within a class with a type parameter.
type
TTheClass<T> = class
type
TGenClassClass = TGenClass<TGenClass<T>>;
end;
So you can use TTheClass<T>.TGenClassClass as a type.
FWIW, inspired by the various comments and answers, this is the code that I believe is the most effective:
type
TDynMatrix<T> = array of TArray<T>;
TDynMatrix = class
public
class function New<T>(const Source: array of TArray<T>): TDynMatrix<T>; static;
end;
class function TDynMatrix.New<T>(const Source: array of TArray<T>): TDynMatrix<T>;
var
i: Integer;
begin
SetLength(Result, Length(Source));
for i := 0 to high(Result) do begin
Result[i] := Copy(Source[i]);
end;
end;
This allows be to declared variables like this:
var
RealM: TDynMatrix<Real>;
ComplexM: TDynMatrix<TComplex>;
And make new instances like this:
RealM := TDynMatrix.New<Real>([TArray<Real>.Create(...)]);
ComplexM := TDynMatrix.New<TComplex>([TArray<TComplex>.Create(...)]);
Now, if only the compiler's generic inference capabilities were a little better.
I am trying to get Unicode font glyph ranges (Delphi 6):
var GS:PGlyphSet;
GSSize:LongWord;
rng:TWCRange;
begin
GSSize := GetFontUnicodeRanges(Canvas.Handle, nil);
GetMem(Pointer(GS), GSSize);
try
GS.cbThis:=GSSize;
GS.flAccel:=0;
GS.cGlyphsSupported:=0;
GS.cRanges:=0;
if GetFontUnicodeRanges(Canvas.Handle, GS)<>0 then begin
for i:=0 to GS.cRanges-1 do begin
rng := GS.ranges[i];
The strange thing is that Length(GS.ranges) is 1, but GS.cRanges is 309 and when I try to access the second range GS.ranges[1] I get, of course, a range check error. Before I turned range checking on it has worked in some magical way.
Types for reference (from Windows module):
PWCRange = ^TWCRange;
{$EXTERNALSYM tagWCRANGE}
tagWCRANGE = packed record
wcLow: WCHAR;
cGlyphs: SHORT;
end;
TWCRange = tagWCRANGE;
PGlyphSet = ^TGlyphSet;
{$EXTERNALSYM tagGLYPHSET}
tagGLYPHSET = packed record
cbThis: DWORD;
flAccel: DWORD;
cGlyphsSupported: DWORD;
cRanges: DWORD;
ranges: array[0..0] of TWCRange;
end;
TGlyphSet = tagGLYPHSET;
This struct makes use of the so-called struct hack:
http://c-faq.com/struct/structhack.html
http://tonywearme.wordpress.com/2011/07/26/c-struct-hack/
The ranges member is a variable length array, placed inline in the struct. But you cannot actually encode that in a static C type. That's why you call the function to find out how much memory to allocate, and then heap allocate the struct. If you allocated it on the stack, or using SizeOf(...) then the struct would be too small.
The simplest thing to do is to disable range checking for the code that accesses ranges. Although the type declaration says that only 0 is a valid index for ranges, in fact 0..cRanges-1 are valid.
If you don't want to disable range checking for the relevant code, then take a pointer the element 0, and then use pointer arithmetic in your loop.
var
rng: PWCRange;
....
rng := #GS.ranges[0];
for i:=0 to GS.cRanges-1 do begin
// use rng^
inc(rng);
end;
This is, in my view, the cleanest way to write code for sequential access. For random access, and with range checking in force, you'd be compelled to declare some extra types to defeat range checking:
type
TWCRangeArray = array [0..(MaxInt div SizeOf(TWCRange))-1] of TWCRange;
PWCRangeArray = ^TWCRangeArray;
And then use type casting to access individual elements:
rng := PWCRangeArray(#GS.ranges)[i];
I am looking to inherite a enumaration in other one:
for example:
Type TMyTypeKind = (TTypeKind, enBoolean, enPath);
You can not. Compiler does not know how to interpret this. From the wiki :
An enumerated type defines an ordered set of values by simply listing identifiers that denote these values. The values have no inherent meaning.
Something similar is possible in the reverse order. If you know all the possible values, define it as a base type and declare subrange types of it. The subranges will be assignement compatible with the base type and with each other. It may or may not be a benefit.
type
TEnumAll = (enFirst, enSecond, enThird, enFourth, enFifth);
TEnumLower = enFirst..enThird;
TEnumMore = enFirst..enFourth;
procedure TForm1.Test1;
var
All: TEnumAll;
Lower: TEnumLower;
begin
for All := Low(TEnumAll) to High(TEnumAll) do begin
Lower := All;
end;
for Lower := Low(TEnumLower) to High(TEnumLower) do begin
All := Lower;
end;
end;
It can be done with an trick, using Include files. Example:
AdCommonAttributes.inc
canonicalName,
cn,
whenCreated,
description,
displayName,
distinguishedName,
instanceType,
memberOf,
modifyTimeStamp,
name,
objectCategory,
objectClass,
objectGuid,
showInAdvancedViewOnly
AdUserGroupCommonAttributes.inc:
msDSPrincipalName,
objectSid,
sAMAccountName
AdUserAttributers.inc:
accountExpires,
badPasswordTime,
badPwdCount,
c,
comment,
company,
department,
division,
employeeID,
givenName,
homeDirectory,
homeDrive,
lastLogon,
lockoutTime,
logonCount,
pwdLastSet,
sn,
telephoneNumber,
tokenGroups,
userAccountControl,
userPrincipalName
unit AdUserGroupCommonAttributes;
TAdUserGroupCommonAttributes = (
{$I AdCommonAttribs.inc}, {$I AdUserGroupCommonAttributes.inc}
);
unit AdGroupAttributes;
type
TAdGroupAttributes = (
{$I AdCommonAttribs.inc},
{$I AdUserGroupCommonAttributes.inc},
{$I AdGroupAttributes.inc}
);
unit AdUserAttributes;
type
TAdUserAttributes = (
{$I AdCommonAttribs.inc},
{$I AdUserGroupCommonAttributes.inc},
{$I AdUserAttributes.inc}
);
This is not possible because the enumerated names should be unique.
You cannot use the values of TTypeKind in another enumeration, it generates conflict.
However in Delphi 2009 there is a feature called scoped enums.
You can say TMyTypeKind.enBoolean.
But this does not solve the inheritance.
One way is to assign integer constants to the enum values:
Type TMyTypeKind = (enBoolean = High(TTypeKind) + 1, enPath = High(TTypeKind) + 2);
So you can have an index number that begins in Low(TTypeKind) and ends in High(TMyTypeKind)
See it for yourself: Ord(enBoolean)
I am afraid this is not possible at all. Theres nothing you can do about it, I am sorry,
When you type:
Type TMyTypeKind = (TTypeKind, enBoolean, enPath);
Delphi will see that TTypeKind is already a type and it will give you the follow error:
[DCC Error] xxx.pas(41): E2004 Identifier redeclared: 'TTypeKind'
As it was already said, you can't.
But you may do this way:
TBaseState = class
public const
stNone = 1;
stSingle = 2;
end;
TMyState = class(TBaseState)
public const
stNewState = 3;
end;
var
state: TMyState;
begin
ShowMessage(IntToStr(s.stNewState));
end;
It isn't the same with enums, but sometimes it helps.
I have an integer field in a ClientDataSet and I need to compare to some values, something like this:
I can use const
const
mvValue1 = 1;
mvValue2 = 2;
if ClientDataSet_Field.AsInteger = mvValue1 then
or enums
TMyValues = (mvValue1 = 1, mvValue2 = 2);
if ClientDataSet_Field.AsInteger = Integer(mvValue1) then
or class const
TMyValue = class
const
Value1 = 1;
Value2 = 2;
end;
if ClientDataSet_Field.AsInteger = TMyValues.Value1 then
I like the class const approach but it seems that is not the delphi way, So I want to know what do you think
Declaration:
type
TMyValues = class
type TMyEnum = (myValue1, myValue2, myValue3, myValue4);
const MyStrVals: array [TMyEnum] of string =
('One', 'Two', 'Three', 'Four');
const MyIntVals: array [TMyEnum] of integer =
(1, 2, 3, 4);
end;
Usage:
if ClientDataSet_Field.AsInteger = TMyValues.MyIntVals[myValue1] then
A cast would generally be my last choice.
I wouldn't say that class consts are not the Delphi way. It's just they have been introduced to Delphi quite recently, and a lot of books and articles you'll find on the internet were written before their introduction, and thus you won't see them widely used. Many Delphi developers (I'd say the majority) will have started using Delphi before they were made available, and thus they're not the first thing that one thinks about.
One thing to consider is backwards compatibility - class constants are relatively new to Delphi so if your code has to be sharable with previous versions than they are out.
I typically use enumerated types, with the difference from yours is that my first enumeration is usually an 'undefined' item to represent NULL or 0 in an int field.
TmyValues = (myvUndefined, myvDescription1, myvDescription2)
if ClientDataSet_Field.AsInteger = Ord(myvDescription1) then...
To use a little bit of Jim McKeeth's answer - if you need to display to the user a text viewable version, or if you need to convert their selected text into the enumerated type, then an array comes in handy in conjuction with the type:
const MYVALS: array [TmyValues ] of string = ('', 'Description1', 'Description2');
You can then have utility functions to set/get the enumerated type to/from a string:
Function MyValString(const pMyVal:TmyValues):string;
begin
result := MYVALS[Ord(pMyVal)];
end;
Function StringToMyVal(const pMyVal:String):TMyValues;
var i:Integer;
begin
result := myvUndefined;
for i := Low(MYVALS) to High(MYVALS) do
begin
if SameText(pMyVal, MYVALS[i]) then
begin
result := TMyValues(i);
break;
end;
end;
end;
Continuing on... you can have scatter routine to set a combo/list box:
Procedure SetList(const DestList:TStrings);
begin
DestList.Clear;
for i := Low(MYVALS) to High(MYVALS) do
begin
DestList.Insert(MYVALS[i]);
end;
end;
In code: SetList(Combo1.Items) or SetList(ListBox1.Items)..
Then if you are seeing the pattern here... useful utility functions surrounding your enumeration, then you add everything to it's own class and put this class into it's own unit named MyValueEnumeration or whaterver. You end up with all the code surrounding this enumeration in one place and keep adding the utility functions as you need them. If you keep the unit clean - don't mix in other unrelated functionality then it will stay very handy for all projects related to that enumeration.
You'll see more patterns as time goes and you use the same functionality over and over again and you'll build a better mousetrap again.
When using constants I recommend assigning the type when the data type is a numeric float.
Delphi and other languages will not always evaluate values correctly if the types do not match...
TMyValue = class
const
// will not compare correctly to float values.
Value1 = 1; // true constant can be used to supply any data type value
Value2 = 2; // but should only be compared to similar data type
// will not compare correctly to a single or double.
Value3 = 3.3; // default is extended in debugger
// will not compare correctly to a single or extended.
Value1d : double = Value1; // 1.0
Value2d : double = Value2; // 2.0
end;
Compared float values in if () and while () statements should be compared to values of the same data type, so it is best to define a temporary or global variable of the float type used for any comparison statements (=<>).
When compared to the same float data type this format is more reliable for comparison operators in any programming language, not just in Delphi, but in any programming language where the defined float types vary from variable to constant.
Once you assign a type, Delphi will not allow you to use the variable to feed another constant, so true constants are good to feed any related data type, but not for comparison in loops and if statements, unless they are assigned and compared to integer values.
***Note: Casting a value from one float type to another may alter the stored value from what you entered for comparison purposes, so verify with a unit test that loops when doing this.
It is unfortunate that Delphi doesn't allow an enumeration format like...
TController : Integer = (NoController = 0, ncpod = 1, nextwave = 2);
or enforce the type name for access to the enumeration values.
or allow a class constant to be used as a parameter default in a call like...
function getControllerName( Controller : TController = TController.NoController) : string;
However, a more guarded approach that provides both types of access would be to place the enumeration inside a class.
TController = class
//const
//NoController : Integer = 1;
//ncpod : Integer = 2;
//nextwave : Integer = 3;
type
Option = (NoController = 0, ncpod = 1, nextwave = 2);
public
Class function Name( Controller : Option = NoController) : string; static;
end;
implementation
class function TController.Name( Controller : Option = NoController) : string;
begin
Result := 'CNC';
if (Controller = Option.nextwave) then
Result := Result + ' Piranha'
else if (Controller = Option.ncpod) then
Result := Result + ' Shark';
Result := Result + ' Control Panel';
end;
This approach will effectively isolate the values, provide the static approach and allow access to the values using a for () loop.
The access to the values from a floating function would be like this...
using TControllerUnit;
function getName( Controller : TController.Option = TController.Option.NoController) : string;
implementation
function getName( Controller : TController.Option = TController.Option.NoController) : string;
begin
Result := 'CNC';
if (Controller = TController.Option.nextwave) then
Result := Result + ' Piranha'
else if (Controller = TController.Option.ncpod) then
Result := Result + ' Shark';
Result := Result + ' Control Panel';
end;
so many options! :-) i prefer enums and routinely use them as you describe. one of the parts i like is that i can use them with a "for" loop. i do use class constants as well but prefer enums (even private enums) depending on what i'm trying to achieve.
TMyType=class
private const // d2007 & later i think
iMaxItems=1; // d2007 & later i think
private type // d2007 & later i think
TMyValues = (mvValue1 = 1, mvValue2 = 2); // d2007 & later i think
private
public
end;
An option you haven't thought of is to use a lookup table in the database and then you can check against the string in the database.
eg.
Select value, Description from tbl_values inner join tbl_lookup_values where tbl_values.Value = tbl_lookup_values.value
if ClientDataSet_Field.AsString = 'ValueIwant' then