Cut, copy and paste: one method for different TEdit-controls
Question
The Delphi manual illustrates calling the cut, copy or paste methods of an
edit control using either a menu choice or the usual hotkeys. The example
goes like
If Sender = Control then
Control.CutToClipboard;
For forms with several edit controls, the suggestion is to use a case
statement to determine the sender and then access the method of the sender
as above. Is there another way that can be used to access any control in
one statement?
Answer
A:
The trouble is that sender is TObject. TObject does not have a cut to
clipboar method. If you think that TObject is a type which does have
such a method, you can convert it. For example,
procedure objectCutToClipboard(sender as TObject);
var
myEdit : TEdit;
begin
if sender is TEdit then myEdit := sender as TEdit;
myEdit.cutToClipboard;
end;
You can also do this:
procedure objectCutToClipboard(sender as TObject);
begin
if sender is TEdit then
(myEdit as TEdit).cutToClipboard;
end;
The first method is better if you will be using sender several times;
that way you do not need to convert it to TEdit each time.
Try this:
(1) On a form place several edit boxes; names can be anything. Put some
text in each edit box.
(2) Add two more edit boxes named nEdit and rEdit.
(3) Add a list box named ListBox1 (default);
(4) Put this in the MOUSEUP routine for the first Edit box.
procedure TForm1.Edit1MouseUp(sender: TObject; Button: TMouseButton;
Shift: TShiftState; x,y: integer);
var
astr,aName: string;
anEdit: TEdit;
begin
if button <> mbRight then exit;
if sender is TEdit then
begin
anEdit := sender as TEdit;
clipboard.clear;
anEdit.selectAll;
anEdit.cutToClipboard;
rEdit.text := '';
rEdit.pasteFromClipboard;
nEdit.text := anEdit.name;
ListBox1.items.add(nEdit.text + ' : ' + rEdit.text);
end;
end;
(5) Set the MOUSEUP routine for ALL of the edit boxes except for rEdit
and nEdit to the routine for the first Edit box. (Just click on the down
Arrow and choose it.)
Now a right click in any one of those Edit boxes will be processed by the
same routine. The right click will select all of the text in the listbox,
paste it into rEdit, get the name of the Edit that you clicked in, place
the name into nEdit, and then place a carefully constructed combination
of the two into the List Box.
(6) Now, add this routine to the MOUSEUP routine ( or click ) of List Box
1. This routine shows you how to reverse the process and put the text
back into the correct box.
procedure TForm1.ListBox1MouseUp(sender: TObject; Button: TMouseButton;
Shift: TShiftState; x,y: integer);
var
astr,lStr, cmptStr: string;
anEdit: TObject; {Must be TObject, not TEdit!}
ii,aNo,l: integer;
begin
{get the selected string }
ii := ListBox1.itemIndex;
lstr := ListBox1.items[ii];
{split the string into a control name and the text }
l := length(lstr);
aNo := pos(':', lstr);
cmptStr := copy(lstr,0,aNo-2);
aStr := copy(lstr,aNo+2, l - (aNo + 1));
nEdit.text := cmptStr;
rEdit.text := astr;
{ These two lines are the really important ones! }
anEdit := findComponent(cmptStr);
(anEdit as TEdit).text := astr;
end;
If I have typed this all correctly, a click on any listBox line should
put the text back into the listbox from whence it came.
Some notes:
You need to use MouseUp and NOT click in the Edit routine because you
need to filter for the right button and click does not give you the info.
Unfortunately, a right click in a list box does not select a line. Item
index remains -1 and will give you an error.
FindComponent(aStr) returns a tObject; hence the need to type anEdit as
TObject and cast it as TEdit later.
If you are going to use the (tObject as TEdit).something method, make
sure that you use the parentheses.
I hope that this helps you out. I really had to spend a lot of time
struggling with it some time ago to figure it out.
Sort a TStringList by Numerical Value
Question
I cannot use the 'Sort' method in TStringList as I would like
to sort by Integer.
My TStringList is filled with numbers such as:
20
12
1
23
54
32
(of course, they're converted to string before being added to TStringList)
What is a fast algorithm to achieve this sort? Thanks.
I normally have less than 50 items in my TStringList, if that is a factor.
Answer
A:
I think the implementation that I mailed out to a few people sorts an array
of integers, and so there would be no problem using it to sort a a string list
with integers. You'd end up doing a *lot* of conversions using StrToInt, which
is wasteful, so I would recommend that you create a
type
PInteger = ^Integer type,
store all of the StrToInt values in the TStringList.Objects array, and then when
you use the sort, do your comparisons based on
PInteger(SL.Objects[Idx])^
The quicksort that TStringList uses (see CLASSES.PAS) uses a very simple
partition function, which is completely unaware of the data it's sorting.
It's using the midpoint index to begin to decide where to start partitioning,
which is just as reliable as picking a random number when deciding how to sort.
If, for example, you had a BIG list of items that was already sorted in the
reverse direction, and you used this quicksort on it, and would call itself
recursively once for every element in the list! Now, when you take into account
that you're pushing a few items on the stack (the return address as well as the
parameters as well as the registers you are saving) it might not take too long
for your 16K of stack space to get eaten up (16,384 bytes divided by about maybe
32 bytes (and that's being pretty optimistic!) is about 2048 items before you
run the risk of killing the stack!). The MaxListSize in CLASSES is 16380 (65520
div sizeof (Pointer)), so it's certainly possible to cause this problem.
I do want you guys to know that TStringList.Sort is declared as virtual, so if
you wanted to override it, you certainly could in a class derived from
TStringList.
I also want you to know that the odds of anyone having to sort this much data
(2000 items) seems pretty remote (correct me, anyone, if you've ever sorted more
than 2000 strings in an application). The most reliable sort with the same
running time as QuickSort is a HeapSort. They both run in O(N lg N) time,
whereas sorts like the InsertionSort (which someone mentioned) and BubbleSort
(which someone else mentioned) run in O(N^2) time, on the average.
The biggest differences between HeapSort and QuickSort, in terms of their run
time and storage are:
1) HeapSort only calls itself recursively at most lg N times, where as QuickSort
could call itself recursively N times (big difference, like 10 vs 1024, or 32 vs
2^32)
2) The worst case upper bound time on HeapSort is only O(N lg N), whereas in the
worst case for QuickSort, the running time is O(N^2).
Program follows:
{***********************************************************}
program H;
uses WinCrt, SysUtils;
const
min = 10;
max = 13;
maxHeap = 1 shl max;
type
heap = array [1..maxHeap] of integer;
heapBase = ^heap;
var
currentSize, heapSize: integer;
A: heapBase;
procedure SwapInts (var a, b: integer);
var
t: integer;
begin
t := a;
a := b;
b := t
end;
procedure InitHeap (size: integer);
var
i: integer;
begin
heapSize := size;
currentSize := size;
Randomize;
for i := 1 to size do
A^[i] := Random(size) + 1;
end;
procedure Heapify (i: integer);
var
left, right, largest: integer;
begin
largest := i;
left := 2 * i;
right := left + 1;
if left <= heapSize then
if A^[left] > A^[i] then
largest := left;
if right <= heapSize then
if A^[right] > A^[largest] then
largest := right;
if largest <> i then
begin
SwapInts (A^[largest], A^[i]);
Heapify (largest)
end
end;
procedure BuildHeap;
var
i: integer;
begin
for i := heapSize div 2 downto 1 do
Heapify (i)
end;
procedure HeapSort;
var
i: integer;
begin
BuildHeap;
for i := currentSize downto 2 do
begin
SwapInts (A^[i], A^[1]);
dec (heapSize);
Heapify (1)
end
end;
type
TAvgTimes = array [min..max] of TDateTime;
var
sTime, eTime, tTime: TDateTime;
i, idx, size: integer;
avgTimes: TAvgTimes;
begin
tTime := 0;
i := min;
size := 1 shl min;
new (A);
while i <= max do
begin
for idx := 1 to 10 do
begin
InitHeap (size);
sTime := Time;
HeapSort;
eTime := Time;
tTime := tTime + (eTime - sTime)
end;
avgTimes[i] := tTime / 10.0;
inc (i);
size := size shl 1;
end;
end.
Sendkey function
Question
Is there anybody have information on the function especially how to program
it in Delphi ?
Answer
It's attached one freeware sendkeys component from Makoto Muramatsu
( and )
unit Sendkey;
{
This is a procedure named "SendKeys".
This function like the same named statment of Visual Basic.
It provide the like features by VB's function.
This version not use "wait" flg.
CopyRights 1995, Makoto Muramatsu
Para ejecuatar la funcion:
SendKeys(VentDestino,Teclas,False);
VentDestino: 0: Ventana acticava actualmente,
si el destino es una pantalla Delphi se puede activar
la ventana de destino y con la funci=F3n GetActiveWindow
recoger el valor de la ventana, tambien se puede usar
FindWindow(NombreVentana,nil) para buscar el numero de
ventana de un determinado programa.
Teclas: Son las teclas tal cual, para mandar un Control seel
antepone ^ (es decir Ctrl+ C seria ^C), para simular
Alt+Tecla es %Tecla ; para Mayusculas+Tecla es +Tecla,
adem=E1s se pueden simular las teclas especiales
poniendo los siguientes textos entre llaves:
BS, BACKSPACE, BKSP'
BREAK
CAPSLOCK
CLEAR
DEL, DELETE
DOWN
END
ENTER
ESC, ESCAPE
HELP
HOME
INSERT
LEFT
NUMLOCK
PGDN
PGUP
PRTSC
RIGHT
SCROLLLOCK
TAB
UP
F1
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
F13
F14
F15
F16
F17
F18
F19
F20
F21
F22
F23
F24
1.996 Juan Davi Evora H=E4nggi
}
interface
uses WinTypes;
procedure SendKeys( h: HWND; const keys: string; wait: boolean );
implementation
uses WinProcs, Messages, SysUtils, Forms, Dialogs ;
type
TWindowObj = class( TObject )
private
windowHandle : HWND;
TargetClass : PChar;
NameLength : Integer;
Buffer : PChar;
public
constructor Create;
destructor Destroy;
procedure SetTargetClass( className : string );
procedure SetWindowHandle( hWnd: HWND );
function GetWindowHandle: hWnd;
function Equal( handle: HWND ): boolean;
end;
const
OPENBRACE = '{';
CLOSEBRACE = '}';
PLUS = '+';
CARET = '^';
PERCENT = '%';
SPACE = ' ';
TILDE = '~';
SHIFTKEY = $10;
CTRLKEY = $11;
ALTKEY = $12;
ENTERKEY = $13;
OPENPARENTHESES = '(';
CLOSEPARENTHESES = ')';
NULL = #0;
TargetControlClass = 'Edit';
{================ GetTextWindow ===============================}
function EnumChildProc( hWnd: HWND; lParam: LongInt ):Bool;export;
var
continueFlg : boolean;
HObj : TWindowObj;
begin
HObj := TWindowObj( lParam );
if HObj.Equal( hWnd ) then begin
HObj.SetWindowHandle( hWnd );
continueFlg := false;
end;
result := continueFlg; { Stop Enumerate}
end;
function GetFocusWindow( h: HWnd ): hWnd;
{ GetFocus and if return 0 then search Edit Control in Children of the window}
var
EnumFunc : TFarProc;
Param : LongInt;
proc: TFarProc;
ok : Boolean;
hObj : TWindowObj;
targetWindow : HWnd;
begin
targetWindow := GetFocus;
if targetWindow <> 0 then begin
result := targetWindow;
exit;
end;
h := GetActiveWindow;
Proc := @EnumChildProc;
EnumFunc := MakeProcInstance( proc, HInstance );
If Not Assigned(EnumFunc ) then begin
MessageDlg( 'MakeprocInstanceFail', mtError, [mbOK],0 );
exit;
end;
hObj := TWindowObj.Create;
hObj.SetTargetClass(TargetControlClass);
Param := LongInt( hObj );
result := 0;
try
ok := EnumChildWindows(h, EnumFunc, Param );
targetWindow := hObj.GetWindowHandle;
finally
FreeProcInstance( EnumFunc );
hObj.Free;
end;
result := h;
if targetWindow <> 0 then begin
if IsWindowEnabled( targetWindow ) then begin
result := targetWindow;
end;
end;
end;
{================ TWindowObj ===============================}
{transfer User Data from EnumChildWindow to EnumChildProc }
constructor TWindowObj.Create;
begin
TargetClass := nil;
end;
destructor TWindowObj.Destroy;
begin
if Assigned( TargetClass ) then begin
StrDispose( TargetClass ) ;
end;
if Assigned( Buffer ) then begin
StrDispose( Buffer ) ;
end;
end;
function TWindowObj.Equal(handle: HWND ): boolean;
var
classNameLength : integer;
begin
result := false;
classNameLength := GetClassname( handle, Buffer, NameLength + 1 );
if classNameLength = 0 then exit;
if StrLIComp( TargetClass, Buffer, NameLength ) = 0 then begin
result := true;
end;
end;
procedure TWindowObj.SetTargetClass( ClassName: String );
begin
if Assigned( TargetClass ) then begin
StrDispose( TargetClass ) ;
end;
if Assigned( Buffer ) then begin
StrDispose( Buffer ) ;
end;
NameLength := Length( ClassName );
TargetClass := StrAlloc( NameLength + 1 );
StrPCopy( TargetClass, ClassName );
Buffer := StrAlloc( NameLength + 1 );
end;
procedure TWindowObj.SetWindowHandle( hWnd: HWND );
begin
windowHandle := hWnd;
end;
function TWindowObj.GetWindowHandle: hWnd;
begin
result := windowHandle;
end;
{============= SendKeys =============================}
procedure SendOneKey( window: HWND; virtualKey: WORD; repeatCounter: Integer;
shift: BOOLEAN; ctrl: BOOLEAN; menu: BOOLEAN; wait: BOOLEAN);
{ Send One VirtualKey, to other Window }
var
lparam: LongInt;
counter: integer;
keyboardState: TKeyBoardState;
test: BYTE;
begin
window := GetFocusWindow( window );
for counter := 0 to repeatCounter - 1 do begin
lparam := $00000001;
if menu then begin
lparam := lparam or $20000000;
end;
if shift or ctrl or menu then begin
{ Set KeyboardState }
GetKeyBoardState( keyboardState );
if menu then begin
if VirtualKey = 220 then { Si es '\' no es SYSKEY}
PostMessage( window, WM_KEYDOWN, ALTKEY, lparam )
else
PostMessage( window, WM_SYSKEYDOWN, ALTKEY, lparam );
keyboardState[ALTKEY] := $81;
end;
if shift then begin
PostMessage( window, WM_KEYDOWN, SHIFTKEY, lparam );
keyboardState[SHIFTKEY] := $81;
end;
if ctrl then begin
PostMessage( window, WM_KEYDOWN, CTRLKEY, lparam );
keyboardState[CTRLKEY] := $81;
end;
SetKeyBoardState( keyboardState );
end;
if menu and (VirtualKey <> 220) then begin
PostMessage( window, WM_SYSKEYDOWN, virtualKey, lparam );
end
else begin
PostMessage( window, WM_KEYDOWN, virtualKey, lparam );
end;
Application.ProcessMessages;
lparam := lparam or $D0000000;
if menu and (VirtualKey <> 220) then begin
PostMessage( window, WM_SYSKEYUP, virtualKey, lparam );
end
else begin
PostMessage( window, WM_KEYUP, virtualKey, lparam );
end;
if shift or ctrl or menu then begin
{unSet KeyBoardState }
GetKeyBoardState( keyboardState );
if ctrl then begin
PostMessage( window, WM_KEYUP, CTRLKEY, lparam );
keyboardState[CTRLKEY] := $00;
end;
if shift then begin
PostMessage( window, WM_KEYUP, SHIFTKEY, lparam );
keyboardState[SHIFTKEY] := $00;
end;
if menu then begin
lparam := lparam and $DFFFFFFF;
if (VirtualKey = 220) then
PostMessage( window, WM_KEYUP, ALTKEY, lparam )
else
PostMessage( window, WM_SYSKEYUP, ALTKEY, lparam );
keyboardState[ALTKEY] := $00;
end;
SetKeyBoardState( keyboardState );
end;
end;
end;
procedure SendOneChar( window: HWND; oneChar: Char; wait: BOOLEAN);
{ Send One Character to target Window }
var
lparam: LongInt;
counter: integer;
key : WORD;
begin
window := GetFocusWindow( window );
lparam := $00000001;
key := Word( oneChar );
PostMessage( window, WM_CHAR, key, lparam );
Application.ProcessMessages;
end;
function RecognizeChar( s : string ): BYTE;
{ Recognize Virtual Key by KEYWORD }
begin
if (CompareText( s, 'BS') = 0) OR
(CompareText(s, 'BACKSPACE') = 0) or
( CompareText(s,'BKSP') = 0 ) then begin
result := $08;
end
else if CompareText(s, 'BREAK') = 0 then begin
result := $13;
end
else if CompareText(s, 'CAPSLOCK') = 0 then begin
result := $14;
end
else if CompareText(s, 'CLEAR') = 0 then begin
result := $0C;
end
else if (CompareText(s, 'DEL') = 0 ) or
(CompareText(s ,'DELETE') = 0) then begin
result := $2E;
end
else if CompareText(s, 'DOWN') = 0 then begin
result := $28;
end
else if CompareText(s, 'END') = 0 then begin
result := $23;
end
else if CompareText(s, 'ENTER') = 0 then begin
result := $0D;
end
else if (CompareText(s, 'ESC') = 0) OR
( CompareText(s, 'ESCAPE') = 0 ) then begin
result := $1B;
end
else if CompareText(s, 'HELP') = 0 then begin
result := $2F;
end
else if CompareText(s, 'HOME') = 0 then begin
result := $24;
end
else if CompareText(s, 'INSERT') = 0 then begin
result := $2D;
end
else if CompareText(s, 'LEFT') = 0 then begin
result := $25;
end
else if CompareText(s, 'NUMLOCK') = 0 then begin
result := $90;
end
else if CompareText(s, 'PGDN') = 0 then begin
result := $22;
end
else if CompareText(s, 'PGUP') = 0 then begin
result := $21;
end
else if CompareText(s, 'PRTSC') = 0 then begin
result := $2C;
end
else if CompareText(s, 'RIGHT') = 0 then begin
result := $27;
end
else if CompareText(s, 'SCROLLLOCK') = 0 then begin
result := $91;
end
else if CompareText(s, 'TAB') = 0 then begin
result := $09;
end
else if CompareText(s, 'UP') = 0 then begin
result := $26;
end
else if CompareText(s, 'F1') = 0 then begin
result := $70;
end
else if CompareText(s, 'F2') = 0 then begin
result := $71;
end
else if CompareText(s, 'F3') = 0 then begin
result := $72;
end
else if CompareText(s, 'F4') = 0 then begin
result := $73;
end
else if CompareText(s, 'F5') = 0 then begin
result := $74;
end
else if CompareText(s, 'F6') = 0 then begin
result := $75;
end
else if CompareText(s, 'F7') = 0 then begin
result := $76;
end
else if CompareText(s, 'F8') = 0 then begin
result := $77;
end
else if CompareText(s, 'F9') = 0 then begin
result := $78;
end
else if CompareText(s, 'F10') = 0 then begin
result := $79;
end
else if CompareText(s, 'F11') = 0 then begin
result := $7A;
end
else if CompareText(s, 'F12') = 0 then begin
result := $7B;
end
else if CompareText(s, 'F13') = 0 then begin
result := $7C;
end
else if CompareText(s, 'F14') = 0 then begin
result := $7D;
end
else if CompareText(s, 'F15') = 0 then begin
result := $7E;
end
else if CompareText(s, 'F16') = 0 then begin
result := $7F;
end
else if CompareText(s, 'F17') = 0 then begin
result := $80;
end
else if CompareText(s, 'F18') = 0 then begin
result := $81;
end
else if CompareText(s, 'F19' ) = 0 then begin
result := $82;
end
else if CompareText(s, 'F20') = 0 then begin
result := $83;
end
else if CompareText(s, 'F21') = 0 then begin
result := $84;
end
else if CompareText(s, 'F22') = 0 then begin
result := $85;
end
else if CompareText(s, 'F23') = 0 then begin
result := $86;
end
else if CompareText(s, 'F24') = 0 then begin
result := $87;
end
else begin
result := 0;
end;
end;
function CharToVirtualKey( source: Char; var shift: boolean; var ctrl:
boolean; var menu: boolean): WORD;
var
resultCode: WORD;
upperWord : WORD;
begin
resultCode := VkKeyScan( Word(source) );
upperWord := resultCode shr 8;
case upperWord of
1,3,4,5: shift := true;
6 : begin
ctrl := true;
menu := true;
end;
7 : begin
shift := true;
ctrl := true;
menu := true;
end;
end;
result := resultCode and $00ff;
end;
function GetSpecialChar(specialChar: PChar; var repeatCount: Integer;
var shift: boolean; var ctrl: boolean; var menu: boolean ): WORD;
{ In Brace String Parser}
var
p : PChar;
s : string;
virtualKey : BYTE;
begin
p := StrScan( specialChar, SPACE );
if p <> nil then begin
p^ := NULL;
Inc(p);
s := StrPas( p );
repeatCount := StrtoInt( s );
end
else begin
repeatCount := 1;
end;
s := StrPas( specialChar );
virtualKey := RecognizeChar( s );
if virtualKey = 0 then begin
result := CharToVirtualKey(specialChar^, shift, ctrl, menu);
end
else begin
result := virtualKey;
end;
end;
procedure Parser( window: HWND; chars: PChar; wait:Boolean);
{Parse String Line and Send keys }
var
p : PChar;
specialChar: PChar;
shift, ctrl, menu: Boolean;
parenthese : Boolean;
repeatCounter : Integer;
oneChar : Char;
vertualKey : Word;
procedure ClearAddKey;
begin
shift := false;
ctrl := false;
menu := false;
end;
begin
p := chars;
ClearAddKey;
parenthese := false;
while p^ <> NULL do begin
if p^ = OPENBRACE then begin
{Control Code }
Inc( p );
specialChar := p;
while p^ <> NULL do begin
if p^ = CLOSEBRACE then begin
if (p + 1)^ = CLOSEBRACE then begin
Inc(p);
end;
break;
end;
Inc(p);
end;
if p^ = NULL then begin
MessageDlg('Illegal string ', mtError, [mbOK], 0 );
break;
end;
p^ := NULL;
vertualKey := GetSpecialChar(specialChar, repeatCounter,
shift, ctrl, menu);
SendOneKey(window, vertualKey, repeatCounter, shift, ctrl,
menu, wait);
if not parenthese then begin
ClearAddKey;
end;
end
else if p^ = PLUS then begin
shift := true;
end
else if p^ = CARET then begin
ctrl := true;
end
else if p^ = PERCENT then begin
menu := true;
end
else if p^ = TILDE then begin
SendOneKey( window, ENTERKEY, 1, shift, ctrl, menu, wait);
if not parenthese then begin
ClearAddKey;
end;
end
else if (shift or ctrl or menu ) and ( p^ = OPENPARENTHESES)
then begin
parenthese := true;
end
else if parenthese and ( p^ = CLOSEPARENTHESES ) then begin
parenthese := false;
end
else begin
if ($80 and BYTE(p^)) > 0 then begin
{ 2 Bytes Char}
SendOneChar(window, p^, wait);
Inc(p);
SendOneChar(window, p^, wait );
end
else begin
vertualKey := CharToVirtualKey( p^,shift,ctrl,menu);
SendOneKey(window, vertualKey, 1, shift, ctrl, menu, wait);
end;
if not parenthese then begin
ClearAddKey;
end;
end;
Inc(p);
end;
end;
procedure SendKeys( h: HWND; const keys: string; wait:Boolean );
{ SendKeys send strings to Window by specific HWND.
Before sending keys, activate the window.
if h = 0 then send string to current activate Window
sorry, this version not use wait.}
var
window: HWND;
focusControl: HWND;
chars: PChar;
begin
{ handle check}
if h = 0 then begin
window := GetActiveWindow;
end
else begin
window := h;
SetActiveWindow( window );
end;
chars := StrAlloc( length( keys ) + 1 );
StrPCopy( chars, keys );
Parser( window, chars, wait );
StrDispose( chars );
end;
end.
Case Of Statement
Question
Can someone help me with "CASE" function like this:
The user have an inputbox where they can type digits
from 1 to 100, now I would like to check what digit the
typed and start some action depending on what the typed.
Somthing like this:
Case I OF
'10'..'20' : Showmessage('Test 10-20');
'21'..'30' : Showmessage('Test 21-30');
and so on...
What should "I" be and integer or string or what...
Answer
A:
Longint is out in Borlandish Pascal; according to the Delphi on-line
help for "Case":
The selector must be a byte-sized or word-sized ordinal type, so
strings and the integer type Longint are invalid selector types.
Note that this DOES permit "user defined" enumerated types to be case
selectors (booleans will also work). So the following is valid:
type
TMyType = ( mt1, mt2, mt3 ) ;
var
MyType : TMyType ;
begin
{ ... code assigning a value to MyType }
case MyType of
mt1 : DoMT1Stuff ;
mt2 : DoMT2Stuff ;
mt3 : DoMT3Stuff ;
end ;
This fact, along with Delphi's Run-Time Type Information facilities,
actually allows you to effectively use strings as case selectors as
well. If anyone's interested in that technique, they can see how it's
done in my entry for the "Tricks and Tips" column in the May issue of
_The Delphi Magazine_; or (if there's sufficient interest) I can post a
write-up to the list.
Anyhoo, the original poster has a couple of options for doing what he
described; probably the most straightforward way is to convert the
numeric string into an integer value:
var
I : string ;
begin
{ ... code that assigns I a string representation of a numeric value }
Case StrToInt( I ) OF
10..20 : Showmessage('Test 10-20');
21..30 : Showmessage('Test 21-30');
end ;
Or, alternatively:
var
I : integer ;
begin
I := StrToInt( { ...code that gets the string value from the user } ) ;
Case I of
10..20 : Showmessage('Test 10-20');
21..30 : Showmessage('Test 21-30');
end ;
The individual cases in the body of the CASE must be INTEGRAL type
constants.
Right, or constants of an enumerated type (boolean or user defined);
from the on-line help again:
All case constants must be unique and of an ordinal type compatible
with the selector type.
They cannot be variables (unfortunately). If you want to compare strings,
you have to do this with IF..THEN..ELSE statements.
AFAIK, this is a limitation of "case" type control structures in all
modern procedural languages (e.g. C, Basic, Ada), and is not special to
Pascal. If the expected strings are fairly well defined beforehand, the
RTTI method mentioned above could serve as an alternative to multiple
if-then-else statements.
A:
How to use Delphi's Run Time type information to use strings as case
selectors:
Delphi's RTTI facilities aren't well documented, sometimes you have to
dig a bit; but I have found that it's possible to convert a string into
an enumerated type constant via the GetEnumValue() function that is
(briefly) documented in the TYPINFO.INT file which is found in the
\DELPHI\DOCS directory/folder of a standard install of Delphi 1.0 (you
2.0 folks will have to look around if it's not in the same place there,
as I don't have 2.0 yet to check; and this technique should be used in
2.0 with the caveat that I haven't tried it to know if it works).
Anyhoo, let's suppose we've defined a enumerated type like so:
....
type
TMyEnumType = ( metItem1, metItem2, metItem3 ) ;
....
I can use string input of some kind (from an editbox, a listbox, a file,
wherever) to select on this type in a case statement like so:
....
{ be sure to add the TypInfo unit to your uses clause! }
var
S : string ;
MyType : TMyEnumType ;
begin
{
first get a string that duplicates an enumerated constant;
e.g. 'metItem1' from someplace
}
GetString( S ) ;
{
now convert that string into a constant of TMyEnumType
}
MyType := TMyEnumType( GetEnumValue( TypeInfo( TMyEnumType ), S ) ;
{
Now use MyType as a case selector
}
case MyType of
metItem1 : DoItem1Stuff ;
metItem2 : DoItem2Stuff ;
metItem3 : DoItem3Stuff ;
end ;
....
So what's going on here? I'll break down the GetEnumValue() expression
by parts:
TMyEnumType( GetEnumValue( TypeInfo( TMyEnumType ), S ) ;
The GetEnumValue() call takes two parameters, the first is a pointer to
the RTTI record for the type in question, the second is merely a Pascal
string. The TypeInfo() function is a system routine (along the lines of
TypeOf() and SizeOf()) that expressly returns the RTTI pointer for a
type. GetEnumValue() returns an integer that is the ordinal value of
the particular constant of the set; it returns -1 if it can't resolve
the string to a constant of that type (nice! often low level routines
like this throw an exception or something instead). Finally, the
returned integer is converted to an enumerated constant by typecasting
it to a TMyEnumType.
Of course, you can manipulate the string before passing it to
GetEnumValue(), so the strings don't have to start out LOOKING like
Enumeration constants; say you had a list box with items like so:
Item 1
Item 2
Item 3
Then (assuming you have a routine Strip() that removes spaces from a
string) you could do something like this:
GetString( S ) ;
S := 'met' + Strip( S ) ;
then pass it to GetEnumValue.
BTW, here's some New Orleans Style Lagniappe ("something extra"):
there's an inverse RTTI function GetEnumName() turns an Enumeration
constant into its string representation, you use it like so:
var
S : string ;
begin
S := GetEnumName( TypeInfo( TMyEnumType ), Ord( metItem1 ))^ ;
which should return 'metItem1' into S. Note that the pointer
dereference operator at the end of the GetEnumName() call is NOT a typo,
GetEnumName() returns a PString, which has to be dereferenced to assign
it into a string.
Hope this is useful for someone, and not too annoying a waste of
bandwidth for everyone else; I'd appreciate it if someone would try
these under 2.0 and let me know how it works.