|
Technical answers from the trenches |
|
Obtaining Names of Enumerated Values
| ||||
Posted: 23 April 2003 |
||||
  |
Applies to: Delphi 2 and later |
|||
  |
Audience: Intermediate |
|||
Question: Is there a way to convert the value of an enumerated type back to its name?Answer: Yes, using Delphi's Runtime Type Information (RTTI). Specifically, the GetEnumName() function from the TYPINFO unit returns the name of a enumerated type constant as a string. It requires two parameters, a pointer to the enumerated type containing the constant and an integer value corresponding to the constant's value. This article demonstrates GetEnumName() in some detail. It shows how to:
Returning the name of an enumerated constantTo illustrate, consider the following code sample. It populates a memo with the names of the default AVI files supported by the TAnimate component: uses
ComCtrls,
TypInfo;
// ...
procedure TForm1.Button1Click(Sender: TObject);
var
si : SmallInt;
str : String;
begin
with Memo1.Lines do
begin
Clear;
for si := ord( low( TCommonAVI ) ) to
ord( high( TCommonAVI ) ) do
begin
str := getEnumName( typeInfo( tCommonAVI ), si );
add( format( '%2d = %s', [ si, str ] ) );
end;
end;
end;Using GetEnumName to create clearer documentationGetEnumName() also provides interesting opportunities for creating clearer documentation routines. For example, consider the following procedure taken from a DataModule from one of our recent applications:uses
TypInfo;
// ...
function TdmMainData.getStructures: TStrings;
var
siCom, // Component loop counter
siFld : SmallInt; // Field loop counter
str : String; // Output string
begin
with Result do
for siCom := 0 to ComponentCount - 1 do
if ( Components[ siCom ] is TAdoDataSet ) then
with ( Components[ siCom ] as tAdoDataSet ) do
begin
add( upperCase( CommandText ) + '.DBF:' );
for siFld := 0 to FieldCount - 1 do
with Fields[ siFld ] do
begin
str := GetEnumName( TypeInfo( TFieldType ),
Ord( DataType ) );
add( format( '%2d. %s (%s, %d)',
[ siFld + 1, FieldName,
str, DataSize ] ) );
end;
add( '' ); // White space
end;
end;This particular example generates a simple report listing the names of the tables in the data module and their field structures. GetEnumName() reports the data type of each field using the associate tFieldType constant, rather than its ordinal value. As a result, the final report is easier to read for the average Delphi programmer. To use the getStructures() example in an application, use something similar to the following: uses maindata; // ... procedure TfrmMainForm.Button1Click(Sender: TObject); begin Memo1.Lines.Clear; Memo1.Lines.addStrings( dmMainData.getStructures ); end; Safer programming with GetEnumName()If you maintain your applications over time, it's easy to forget to update your code to handle new values added to your enumerated types. The resulting bugs can be hard to detect because your code doesn't respond to supported values. With GetEnumName(), you can add exception handlers that detect this sort of problem and provide detailed troubleshooting information. For example, suppose you're using an enumerated set to control the state of a button in a dialog box, as shown in the following code sample: type tInfoButtonState = ( ibsHidden, ibsDisabled, ibsEnabled );
// ...
procedure TfrmInfoForm.setButtonState( const aState: tInfoButtonState );
procedure setButton( const IsVisible, isEnabled : Boolean );
begin
with sbnCancel do
begin
Visible := isVisible;
Enabled := isEnabled;
if not isVisible then
stxMessage.Width := Left + Width;
endIf
end;
begin
case aState of
ibsHidden : setButton( FALSE, FALSE );
ibsDisabled : setButton( TRUE, FALSE );
ibsEnabled : setButton( TRUE, TRUE );
end; // case
end;As you can probably, this code uses an enumerated set to control a SpeedButton's Visible and Enabled properties. While it works as written, suppose you later decide to turn the button into a toggle button (by setting its GroupIndex property to a non-zero value). But, you're in a hurry. You add ibsPressed and ibsUnpressed to the tInfoButtonState, but forget to update the setButtonState method to handle the new values. When you run your code, you find that nothing happens when you call SetButtonState() with one of your new values. You spend valuable time reviewing your code, trying desparately to locate the problem. (Granted, the example seems forced when presented like this, however, similar situations arise more frequently than you might imagine.) The main problem with this code is not that values have been added to the underlying type, but that the processing code (the switch statement) doesn't provide a "safety net" for unhandled values. Else clauses prevent this by "catching" unhandled conditions and let you provide detailed troublshooting information, as shown in the this revision: type tInfoButtonState = ( ibsHidden, ibsDisabled, ibsEnabled,
ibsPressed, ibsUnPressed );
// ...
procedure TfrmInfoForm.setButtonState( const aState: tInfoButtonState );
procedure setButton( const IsVisible, isEnabled : Boolean );
begin
with sbnCancel do
begin
Visible := isVisible;
Enabled := isEnabled;
if not isVisible then
stxMessage.Width := Left + Width;
endIf
end;
var
strConst,
strError : String;
begin
case aState of
ibsHidden : setButton( FALSE, FALSE );
ibsDisabled : setButton( TRUE, FALSE );
ibsEnabled : setButton( TRUE, TRUE );
else
begin
strConst := getEnumName(
typeInfo( tInfoButtonState ),
Ord( aState ) );
strError := format(
'Can''t Change Button State.' + #10#10 +
'Reason: %s''s setButtonState() method ' + #10 +
'does not support the %s state.' + #10#10 +
'Please contact Support for assistance.',
[ Self.ClassName, strConst ] );
raise Exception.create( strError );
end;
end; // case
end;This version raises an Exception when unsupported values are detected; the details of the error message use GetEnumName() to provide the specific name of the unhandled value. This provides more complete troubleshooting information and helps keep your debugging time focused. (You'll also note that the resulting exception is phrased professionally, just in case a problem gets past testing and appears to an end-user.) Common Errors and Their SolutionsThis section outlines errors you may encounter when trying to use getEnumName(). While we can't possibly predict all possible situations, we hope this helps get you started.
SummaryWhile there are several ways to extend and improve the examples in this article, we they begin to illustrate some of the possibilities for Delphi's RTTI. If you have a version of Delphi that includes the VCL source code, take some time to read the TYPINFO unit for additional information and ideas. P.S. We know we've used a lot of extraneous variables in this article's examples; however, we felt they helped clarify the examples. If you disagree, please feel free to let us know via Feedback. |
|||
|
||||||||
|
Copyright © 2000-2004, techtricks.com; All Rights Reserved. Acknowledgements, Disclaimers, Terms and Conditions. |
||||||||
|
Article last updated on 01 June 2003
|
||
|
|
||
|
[- End -]
|