2004. október 31., vasárnap
How to display both Latin and Greek characters in a TRichEdit
Problem/Question/Abstract:
I am trying to display some results in a TRichEdit control. The text is a mixture of Latin letters and Greek (science) letters. Obviously, I have to change the font to 'Symbol' when I want to display the greek letter, otherwise it will be the default font. So, how do I do that? For example how do I display in RichEdit box the following:
RichEdit1.Lines.Add('Standard deviation /* here I want to insert the Greek letter 'sigma' */ is '+FloatToStr(sigma));
Answer:
The key is to not use Lines.Add to add the text, the Lines property is not "formatting-aware". You do it this way:
{ ... }
const
norm_font = 'Times New Roman';
norm_charset = DEFAULT_CHARSET;
symb_font = 'Symbol';
symb_charset = SYMBOL_CHARSET;
{ ... }
with richedit1 do
begin
selstart := gettextlen; {set caret to end}
selattributes.Name := norm_font;
selattributes.charset := norm_charset;
seltext := 'Standard deviation ';
selattributes.Name := symb_font;
selattributes.charset := symb_charset;
seltext := 'S';
selattributes.Name := norm_font;
selattributes.charset := norm_charset;
seltext := ' is ' + FloatToStr(sigma);
{ etc. }
end;
As you see this is quite cumbersome but the alternative is even more so: constructing a *complete* rich text file (with font table!) for the text to insert and use EM_STREAMIN to get it into the text.
2004. október 30., szombat
Making a reliable drawing procedure (No painting, but random numbers!)
Problem/Question/Abstract:
This is very simple, you say: Use a set to store drawn fields!
Yes, you can do if you don't have to many fields to draw. If you pull out more than 255 fields - then the set will say: No more room here! What will you do now?
(And for you who know-how to use arrays to such tasks, don’t read further. I doubt I can tell you any new)
Answer:
I’m not going to make a whole program for you – that’s quite easy, so you manage that yourself.
(Note that the routine is translated from Norwegian – some “funny” names can occur!)
The datatype required:
|
TDrawList = array of ShortString;
We make a dynamic array to make the procedure just that – dynamic. This makes the procedure work just as well with ten records as with ten thousand records. (Not tried – only a theory!) I’m using ShortString, but of course, you can use other datatypes, just remember to change down at the TempDrawList too.
The function-head – the variables: (The name “Draw” was occupied.)
function Drawing(ARecordCount: Integer; ARecords: TDrawList; ADrawCount: Integer):
TDrawList;
var
I, Nr: Integer;
TempDrawList: array of record
Value: ShortString;
CanBeDrawn: Boolean;
end;
ARecordCount: This is the number of records in…
ARecords: This is, as you may understand, all those who CAN be pulled out.
ADrawCount: The number of lucky guys.
I (the variable) are only used in For-statements, while Nr are given a value by the Random function.
TempDrawList: The “set”. This dynamic array knows who’s pulled out and who’s not, stored in the CanBeDrawn field.
The preparations:
SetLength(TempDrawList, ARecordCount);
SetLength(ARecords, ARecordCount);
SetLength(Result, ADrawCount);
for I := 0 to ARecordCount - 1 do // (1 to x = 0 to x-1, and we need to start with 0)
begin
TempDrawList[I].Value := ARecords[I];
// Loading the records from the argument to the variable.
TempDrawList[I].CanBeDrawn := True; // Making sure that all records can be drawn.
end;
Randomize;
First, I allocate place in the memory for the arrays. I do not intend to explain the SetLength procedure. If you need help, consult the Delphi help file.
The next five lines are preparing the TempDrawList for the rest of the procedure, by loading in the records from the argument and setting all “CanBeDrawn”-s to “True”. (The name CanBeDrawn should really be self-explaining!)
At last, I call Randomize. Now the fun begins!
The rest:
for I := 0 to ADrawCount - 1 do // You know what I just wrote: 1 to x = 0 to x-1!
begin
repeat
Nr := Random(ARecordCount);
until TempDrawList[Nr].CanBeDrawn = True;
Result[I] := TempDrawList[Nr].Value;
TempDrawList[Nr].CanBeDrawn := False;
end;
To explain step for step:
ADrawCount times do this:
Draw a random number from 0 to AReocordCount, and draw until the CanBeDrawn field in the TempDrawList at [Nr] is True.
Then, put the Value in the TempDrawList[Nr] in Result[I]. Result is a TDrawList, prepared above. “I” is the For counter. (And Nr is a random number, not pulled out before)
The last thing to do is to make sure that it cannot be drawn again.
TempDrawList[Nr].CanBeDrawn := False.
End;
That’s it. Easy, isn’t it?
(Yes, you’ll say. Easy enough, but… how do I USE this function?)
Using this function:
There are two ways to use it: To make a random list, or to make a random selection from a list:
program UseFunction;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TDrawList = array of ShortString;
var
Selection, TheList: TDrawList;
I: Integer;
function Drawing(ARecordCount: Integer; ARecords: TDrawList; ADrawCount: Integer):
TDrawList; // The complete function, though without comments.
var
I, Nr: Integer;
TempDrawList: array of record
Value: ShortString;
CanBeDrawn: Boolean;
end;
begin
SetLength(TempDrawList, ARecordCount);
SetLength(ARecords, ARecordCount);
SetLength(Result, ADrawCount);
for I := 0 to ARecordCount - 1 do
begin
TempDrawList[I].Value := ARecords[I];
TempDrawList[I].CanBeDrawn := True;
end;
Randomize;
for I := 0 to ADrawCount - 1 do
begin
repeat
Nr := Random(ARecordCount);
until TempDrawList[Nr].CanBeDrawn = True;
Result[I] := TempDrawList[Nr].Value;
TempDrawList[Nr].CanBeDrawn := False;
end;
end;
begin
SetLength(Selection, 3);
SetLength(TheList, 5);
for I := 0 to High(TheList) do
TheList[I] := IntToStr(I);
Selection := Drawing(Length(TheList), TheList, (Length(Selection));
for I := 0 to High(Selection) do
WriteLn(IntToStr(Selection[I]));
ReadLn;
end.
If you change it to this:
SetLength(Selection, 5);
then the result will be a resorted (?) list.
2004. október 29., péntek
Delphi FLIC component - a starting point
Problem/Question/Abstract:
Delphi FLIC component - a starting point
Answer:
I have included two files:
aaplay1.inc - an include file with the interface defs for aaplay.dll
aaplay1.pas - a very skeletal flic-playing component
You need a copy of aaplay.dll from Autodesk.
{aaplay1.inc}
{Header file containing constant and type definitions for aaplay1.pas}
const
NULL = 0;
NULLPTR = ^0;
{Valid flags in wMode: integer; Used in aaLoad, aaReLoad. The low order eight bits of wMode is used in aa_flags.}
AA_MEMORYLOAD = $1; { Load into memory }
AA_HIDEWINDOW = $2; { Hide the animation window }
AA_NOPALETTE = $4; { Prevent palette animation }
AA_RESERVEPALETTE = $8; { Reserve entire palette at start }
AA_LOOPFRAME = $10; { Loaded with a loop frame }
AA_FULLSCREEN = $20; { Use full screen for playing }
AA_STOPNOTIFY = $40; { Prevent any notification messages }
AA_STOPSTATUS = $80; { Prevent status messages }
AA_NOFAIL = $100; { Reduce load type on fail }
AA_DONTPAINT = $200; { Don't paByVal animation when loading }
AA_BUILDSCRIPT = $400; { lpzFileName is script not name }
AA_ALLMODES = $FF; {Valid flags for the sound mode - wMode: integer; Used in aaSound}
AA_SNDFREEZE = $1; { Freeze frames until sound begins }
AA_SNDDEVICEID = $100; { device is an ID not a name }
AA_SNDBUILDALIAS = $200; { create sound device alias }
{aaNotify allows an application to be notified at specific frames when an animation is playing.
lPosition is the position at which the notification is to take place. The wParam for this message is
hAa, and the lParam is copied from this call. Returns TRUE if the notification is set. This value for
the loops will cause the animation to end when the sound has finished. If no sound is playing, the
animation will loop forever.}
AA_LOOPSOUND = $FFFF;
{An automatic notify is sent when an animation in a script is reloaded. The lParam
of this message is defined below}
AA_ANIMATIONLOADED = 0;
{The parameter types Used with aaGetParm and aaSetParm.}
AA_STATUS = 1; { Get current status }
AA_FILETYPE = 2; { Get Type of animation on disk }
AA_MODE = 3; { Get/Set Animation Flags }
AA_WINDOW = 4; { Set/Get animation window }
AA_SPEED = 5; { Set/Get current speed }
AA_DESIGNSPEED = 6; { Get design speed }
AA_FRAMES = 7; { Get Number of frames }
AA_POSITION = 8; { Set/Get current frame position }
AA_LOOPS = 9; { Set/Get number of loops }
AA_X = 10; { Set/Get Pos of display window }
AA_Y = 11; { Set/Get Pos of display window }
AA_CX = 12; { Set/Get extents of display window }
AA_CY = 13; { Set/Get extents of display window }
AA_ORGX = 14; { Set/Get Origin of display window }
AA_ORGY = 15; { Set/Get Origin of display window }
AA_WIDTH = 16; { Get Width of animation }
AA_HEIGHT = 17; { Get Height of animation }
AA_RPTSOUND = 18; { Set/Get sound repeats }
AA_PAUSE = 19; { Set/Get pause time }
AA_DELAYSND = 20; { Set/Get sound delay time }
AA_TRANSIN = 21; { Set/Get Transition In type }
AA_TRANSOUT = 22; { Set/Get Transition Out type }
AA_TIMEIN = 23; { Set/Get Transition In time }
AA_TIMEOUT = 24; { Set/Get Transition Out Time }
AA_CALLBACK = 25; { Set/Get CallBack window }
AA_ANIMWND = 26; { Get Animation Window Handle }
AA_MODFLAG = 100; { Set/Get Script is modified flag }
AA_SCRIPTNAME = 101; { Set/Get Script name }
AA_ANIMATION = 102; { Get/Set Script Animation }
AA_ANIMATIONCOUNT = 103; { Get Script Animation Count }
AA_SCRIPTCONTENTS = 104; { Get Script Contents }
AA_LASTERROR = 1001; { Get last error code }
AA_LASTERRORMESSAGE = 1002; { Get/Set last error messsage }
{The parameter types Used with aaSetParmIndirect}
AA_SETMODE = $1; { Get/Set Animation Flags }
AA_SETWINDOW = $2; { Set/Get animation window }
AA_SETSPEED = $4; { Set/Get current speed }
AA_SETPOSITION = $8; { Set/Get current frame position }
AA_SETLOOPS = $10; { Set/Get number of loops }
AA_SETX = $20; { Set/Get left of display window }
AA_SETY = $40; { Set/Get left of display window }
AA_SETCX = $80; { Set/Get top of display window }
AA_SETCY = $100; { Set/Get top of display window }
AA_SETORGX = $200; { Set/Get width of display window }
AA_SETORGY = $400; { Set/Get width of display window }
AA_SETRPTSOUND = $800; { Set/Get sound repeats }
AA_SETPAUSE = $1000; { Set/Get pause time }
AA_SETDELAYSND = $2000; { Set/Get sound delay time }
AA_SETTRANSIN = $4000; { Set/Get Transition In type }
AA_SETTRANSOUT = $8000; { Set/Get Transition Out type }
AA_SETTIMEIN = $10000; { Set/Get Transition In time }
AA_SETTIMEOUT = $20000; { Set/Get Transition Out Time }
AA_SETCALLBACK = $40000; { Set/Get Callback window }
AA_ALL = $FFFFFFFF; { Get/Set all parameters }
{Status values for an animation}
AA_STOPPED = 1; { Loaded but not playing }
AA_QUEUED = 2; { Animation is waiting to play }
AA_PLAYING = 3; { Animation is playing }
AA_PAUSED = 4; { Animation is paused }
AA_DONE = 5; { Animation has ended playing and is awaiting an aaStop call }
{File type definitions}
AA_FLI = $1; { Autodesk Animator Fli format }
AA_DIB = $2; { Windows DIB format }
AA_NUMTYPES = $2; { Number of types }
AA_SCRIPT = $3; { Script without an animation }
{Transition types}
AA_CUT = 0; { Just stop one and start another }
AA_FADEBLACK = $1; { Fade In/Out from to black }
AA_FADEWHITE = $2; { Fade In/Out from to white }
{Error codes returned by aaGetParm(xxx, AA_LASTERROR)}
AA_ERR_NOERROR = 0; { Unknown error }
AA_ERR_NOMEMORY = $100; { 256 - Out of memory error }
AA_ERR_BADHANDLE = $101; { 257 - Bad handle }
AA_ERR_NOTIMERS = $102; { 258 - Cannot start timer }
AA_ERR_BADSOUND = $103; { 259 - Bad sound spec }
AA_ERR_NOSCRIPT = $104; { 260 - Requires a script }
AA_ERR_WRITEERR = $105; { 261 - Write error for script }
AA_ERR_BADANIMATION = $106; { 262 - Cannot open animation }
AA_ERR_BADWINDOWHANDLE = $200; { 512 - Bad Window Handle }
AA_ERR_WINDOWCREATE = $201; { 513 - Cannot create window }
AA_ERR_DLGERROR = $202; { 514 - Dialog error }
AA_ERR_INVALIDSTATUS = $300; { 768 - Invalid status }
AA_ERR_BADDIBFORMAT = $301; { 769 - Bad dib file }
AA_ERR_BADFLIFORMAT = $302; { 770 - Bad fli file }
AA_ERR_UNRECOGNIZEDFORMAT = $303; { 771 - Unrecognized format }
AA_ERR_NOSOUND = $304; { 772 - Sound not supported }
AA_ERR_NOTVALIDFORSCRIPTS = $305; { 773 - Not valid for scripts }
AA_ERR_INVALIDFILE = $306; { 774 - Bad file handle }
AA_ERR_NOSCRIPTS = $307; { 775 - No Script files }
AA_ERR_SPEED = $400; { 1024 - Invalid speed }
AA_ERR_LOOPS = $401; { 1025 - invalid loops }
AA_ERR_RPTSOUND = $402; { 1026 - invalid repeat sound }
AA_ERR_PAUSE = $403; { 1027 - invalid pause }
AA_ERR_TRANSIN = $404; { 1028 - invalid transition }
AA_ERR_TIMEIN = $405; { 1029 - invalid transition }
AA_ERR_TRANSOUT = $406; { 1030 - invalid transition time }
AA_ERR_TIMEOUT = $407; { 1031 - invalid transition time }
AA_ERR_DELAYSND = $408; { 1032 - invalid sound delay }
AA_ERR_INVALIDTYPE = $409; { 1033 - invalid parameter type }
AA_ERR_DUPLICATENOTIFY = $500; { 1280 - duplicate notify }
AA_ERR_NOSWITCH = $600; { 1536 - no switch in script }
AA_ERR_PARSELOOPS = $601; { 1537 - Bad loops in script }
AA_ERR_PARSESPEED = $602; { 1538 - Bad speed in script }
AA_ERR_BADRPTSOUND = $603; { 1539 - Bad repeat sound in script }
AA_ERR_PARSEPAUSE = $604; { 1540 - Bad pause in script }
AA_ERR_PARSETRANS = $605; { 1541 - Bad tranisition in script }
AA_ERR_PARSEDELAYSND = $606; { 1542 - Bad delay sound in script }
AA_ERR_TOOMANYLINKS = $607; { 1543 - Too many links }
{dwFlags: integer; can be any of the following Used in aaGetFile.}
AA_GETFILE_MUSTEXIST = $1;
AA_GETFILE_NOSHOWSPEC = $2;
AA_GETFILE_SAVE = $4;
AA_GETFILE_OPEN = $8;
AA_GETFILE_USEDIR = $10;
AA_GETFILE_USEFILE = $20;
AA_GETFILE_SOUND = $40;
AA_GETFILE_SCRIPT = $80;
AA_GETFILE_ANIMATION = $100;
{wMode: integer; Values Used in aaSave}
AA_SAVE_IFMODIFIED = $1;
AA_SAVE_AS = $2;
AA_SAVE_CANCEL = $4;
{Capabitlities Used in aaGetCaps}
AA_CAP_TIMER = 1;
AA_CAP_SOUND = 2;
AA_CAP_SCRIPT = 3;
{Animation status messages Use RegisterWindowMessage to get the real message numbers.}
AA_NOTIFY = 'AAPLAY Notify'; { notification message }
AA_STOP = 'AAPLAY Stop'; { stop message }
{These are sent in the low word of lParam with the AA_ERROR message.
They indicate the error that occured}
AA_BADPLAY = 1; { Error attempting to play }
AA_BADNOTIFY = 2; { Error attempting to notify }
AA_BADSCRIPT = 3; { Error attempting to animation in script }
unit aaplay1;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, MPlayer;
{$I AAPLAY1.INC}
const
AAPARMS_SIZE = 54;
type
AAHandle = word; { Handle to an animation }
AASPEED = word; { Type that holds speed for animation }
AATRN = word; { Type that holds transition for animation }
dword = longint;
{The parameter structure to be used for the animation.}
AAPARMS = record
AA_STATUS: byte; { Current status of animation }
AA_FILETYPE: byte; { Type of file on disk }
AA_MODE: byte; { Some flags }
AA_bitpix: byte; { bits per pixel }
AA_HWnd: HWnd; { Handle of window for status messages }
AA_X: integer; { Left of display window }
AA_Y: integer; { Top of display window }
AA_CX: integer; { Width of display window }
AA_CY: integer; { Height of display window }
AA_ORGX: integer; { PoByVal in the animation displayed }
AA_ORGY: integer; { in the upper left corner }
AA_SPEED: AASPEED; { Speed of animation in msec per frame }
AA_DESIGNSPEED: AASPEED; { Designed milliseconds per frame }
AA_WIDTH: word; { Width of animation in pixels }
AA_HEIGHT: word; { Height of animation in pixels }
AA_FRAMES: word; { Number of frames in animation }
AA_POSITION: dword; { Current frame position }
AA_LOOPS: dword; { End of animation position }
AA_RPTSOUND: word; { Number of times to repeat sound }
AA_PAUSE: word; { Number of milliseconds to hold l:t frame }
AA_DELAYSND: longint; { Delay Sound in milliseconds }
AA_TRANSIN: byte; { Transition at start of animation }
AA_TRANSOUT: byte; { Transition at end of animation }
AA_TIMEIN: word; { Length of transition in, milliseconds }
AA_TIMEOUT: word; { Length of transition out, milliseconds }
AA_CALLBACK: HWnd; { message callback window }
AA_ANIMWND: Hwnd; { Animation Window Handle }
end;
AAPARMSPtr = ^AAPARMS;
type
TAAPlayer = class(TMediaPlayer)
procedure OpenAA;
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
AAParameters: AAPARMS;
FlicHandle: AAHandle;
PlayWinHandle: THandle;
StatusWinHandle: THandle;
CallbackWinHandle: THandle;
published
{ Published declarations }
end;
procedure Register;
{ External calls to AAPLAY.DLL }
function aaOpen: boolean;
procedure aaClose;
function aaGetCaps(wType: word): word;
function aaLoad(lpzFileName: PChar; WinHnd: HWnd; wMode: word; x, y, wid,
hght, orgx, orgy: integer): AAHandle;
function aaReLoad(hAa: AAHandle; lpzFileName: PChar; wMode, wMask: word): boolean;
function aaUnload(hAa: AAHandle): boolean;
function aaPlay(hAa: AAHandle): boolean;
function aaNotify(hAa: AAHandle; lPosition, lParam: longint): boolean;
function aaCancel(hAa: AAHandle; lLoPos, lHiPos: longint): word;
function aaStop(hAa: AAHandle): boolean;
function aaPause(hAa: AAHandle): boolean;
function aaPrompt(hAa: AAHandle; lpName: PChar): boolean;
function aaGetParm(hAa: AAHandle; wType: word): longint;
function aaGetParmIndirect(hAa: AAHandle; lpAp: AAPARMSPtr; wSize: word): boolean;
function aaSetParm(hAa: AAHandle; wType: word; wValue1, lValue2: longint): AAHandle;
function aaSetParmIndirect(hAa: AAHandle; dwType: longint; lpAp: AAPARMSPtr;
wMask: word): boolean;
function aaShow(hAa: AAHandle; bShow: boolean): boolean;
function aaSound(hAa: AAHandle; device, ffile: PChar; wMode: word): boolean;
function aaGetFile(dwFlags: word; lpszPath: PChar; wBufLen: word;
lpszDriver: PChar; wDrvLen: word): integer;
function aaSave(hAa: AAHandle; wMode: word): integer;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TAAPlayer]);
end;
procedure TAAPlayer.OpenAA;
var
FileSuffix, tempstr: string[12];
a, b: integer;
begin
tempstr := ExtractFilename(AAPlayer.Filename);
a := StrPos(tempstr, '.');
if (a > 0) then
begin
b := a;
while (b <= StrLen(tmpstr)) do
begin
FileSuffix := FileSuffix + StrUpper(tempstr[b]);
b := b + 1;
end;
if ((FileSuffix = '.FLC') or (FileSuffix = '.FLI')) then
begin
AutoEnable := False;
EnabledButtons := [btRecord, btEject];
end;
end;
end;
{ External calls to 'AAPLAY.DLL' }
{$F+}
function aaOpen: boolean;
external 'AAPLAY';
procedure aaClose;
external 'AAPLAY';
{AAOpen and AAClose are not really needed, except there is a bug in Windows
which prevents Libraries from being freed in the Windows Exit Proc (WEP). So we
use AAClose to free the Libraries when the last task closes the AAPlay DLL.}
function aaGetCaps(wType: word): word;
external 'AAPLAY';
{Get Capabitities}
function aaLoad(lpzFileName: PChar; WinHnd: HWnd; wMode: word;
x, y, wid, hght, orgx, orgy: integer): AAHandle;
external 'AAPLAY';
{aaLoad loads an animation. The file name is in lpzFileName and the loading mode is in wMode.}
function aaReLoad(hAa: AAHandle; lpzFileName: PChar; wMode, wMask: word): boolean;
external 'AAPLAY';
{aaReLoad will load a new animation file into an old animation handle.
Notifications are lost, but the palette and window are retained.}
function aaUnload(hAa: AAHandle): boolean;
external 'AAPLAY';
{aaUnload unloads a loaded animation. FALSE is returned if hAa is not the handle
of a loaded animation.}
function aaPlay(hAa: AAHandle): boolean;
external 'AAPLAY';
{aaPlay plays a loaded animation. TRUE is returned if the animation is not stopped
when aaPlay returns.}
function aaNotify(hAa: AAHandle; lPosition, lParam: longint): boolean;
external 'AAPLAY';
{aaNotify allows an application to be notified at specific frames when an animation
is playing. lPosition is the position at which the notification is to take place. The
wParam for this message is hAa, and the lParam is copied from this call. Returns
TRUE if the notification is set.}
function aaCancel(hAa: AAHandle; lLoPos, lHiPos: longint): word;
external 'AAPLAY';
{aaCancel allows an application to cancel notifications set by aaNotify. lLoPos and
lHiPos give lower and upper limits on positions. Returns the number of notifications
canceled.}
function aaStop(hAa: AAHandle): boolean;
external 'AAPLAY';
{aaStop will stop a playing animation. TRUE is returned if the animation is stopped
when aaStop returns.}
function aaPause(hAa: AAHandle): boolean;
external 'AAPLAY';
{aaPause will pause an animation. TRUE is returned if the animation is paused when
aaPause returns. To resume a paused animation, use aaPlay.}
function aaPrompt(hAa: AAHandle; lpName: PChar): boolean;
external 'AAPLAY';
{aaPrompt will produce a dialog to prompt for user input. When input is accepted, the
handle is changed to reflect the new parameters. The old handle is not destroyed until
the new handle has been created. aaPrompt returns NULL if the new handle cannot be
created, otherwise the new handle is returned.}
function aaGetParm(hAa: AAHandle; wType: word): longint;
external 'AAPLAY';
{aaGetParm will return information on an animation. Some of the information can be set
with aaSetParm, and other information is state information maintained by AAPLAY.}
function aaGetParmIndirect(hAa: AAHandle; lpAp: AAPARMSPtr; wSize: word): boolean;
external 'AAPLAY';
{aaGetParmIndirect returns the same information as aaGetParm, in a structure for easy
access by Visual Basic applications.}
function aaSetParm(hAa: AAHandle; wType: word; wValue1, lValue2: longint): AAHandle;
external 'AAPLAY';
{aaSetParm will set information on an animation}
function aaSetParmIndirect(hAa: AAHandle; dwType: longint;
lpAp: AAPARMSPtr; wMask: word): boolean;
external 'AAPLAY';
{aaSetParmIndirect will set animation parameters from a structure.}
function aaShow(hAa: AAHandle; bShow: boolean): boolean;
external 'AAPLAY';
{aaShow will show a single frame of an animation in a window. The mode determines
how the animation is drawn. The window used is set using either aaSetParm or
aaSetParmIndirect. aaShow returns TRUE if the animation was successfully drawn.}
function aaSound(hAa: AAHandle; device, ffile: PChar; wMode: word): boolean;
external 'AAPLAY';
{aaSound will open or close a sound for the animation. The sound is opened if the file
arguement is not null and not blank%, otherwise the sound is closed. If device is null,
the format of the file is used to select an appropriate device.}
function aaGetFile(dwFlags: word; lpszPath: PChar; wBufLen: word;
lpszDriver: PChar; wDrvLen: word): integer;
external 'AAPLAY';
{Opens a dialog box querying the user for a file in the standard windows file open box
style. dwFlags determines how the dialog box will appear. It may be the following:
AA_GETFILE_MUSTEXIST
Selected file must satisfy conditions of OpenFile() flags, else dialog beeps.
AA_GETFILE_NOSHOWSPEC
Do not show the search spec in the edit box. Default IS to show the spec.
AA_GETFILE_SAVE
Ok button will show "Save".
AA_GETFILE_OPEN
Ok button will show "Open".
AA_GETFILE_USEFILE
Set the filename to the file in lpszPath
AA_GETFILE_UDEDIR
Change to the directory in lpszPath
AA_GETFILE_SOUND
Get sound file and driver
AA_GETFILE_SCRIPT
Get script file
AA_GETFILE_ANIMATION
Get Animation File (no scripts)
lpszPath is a LPSTR to a string buffer into which the final fully qualified pathname
will be written.
wBufLen is the length of this buffer.
lpszDriver is a LPSTR to a string buffer into which a sound device selection is placed.
wDrvLen is the length of this buffer.
Return value is: 0 if cancel was hit, -1 if OpenFile() failed but AA_GETFILE_MUSTEXIST
was not specified. DOS file handle of selected file, otherwise. This handle is not open when
aaOpenFile returns.}
function aaSave(hAa: AAHandle; wMode: word): integer;
external 'AAPLAY';
{Save a script}
{$F-}
{ End of external calls to 'AAPLAY.DLL' }
end.
2004. október 28., csütörtök
Create the MS Access table in run-time
Problem/Question/Abstract:
How can I create a table in MS Access database from own application?
Answer:
In this tip I want to describe how you can in run-time create a table in MS Access database using DAO.
1. declare the variables:
var
access, db, td, recordset: Variant;
2. declare the array of consts with data type mappings (between Delphi field types and DAO field types)
arrMDBTypes: array[TFieldType] of Integer =
({dbText} 10 {ftUnknown},
{dbText} 10 {ftString},
{dbInteger} 3 {ftSmallint},
{dbLong} 4 {ftInteger},
{dbInteger} 3 {ftWord},
{dbBoolean} 1 {ftBoolean},
{dbDouble} 7 {ftFloat},
{dbCurrency} 5 {ftCurrency},
{dbDouble} 7 {ftBCD},
{dbDate} 8 {ftDate},
{dbDate} 8 {ftTime},
{dbDate} 8 {ftDateTime},
{dbLongBinary} 11 {ftBytes},
{dbLongBinary} 11 {ftVarBytes},
{dbInteger} 3 {ftAutoInc},
{dbLongBinary} 11 {ftBlob},
{dbMemo} 12 {ftMemo},
{dbLongBinary} 11 {ftGraphic},
{dbMemo} 12 {ftFmtMemo},
{dbLongBinary} 11 {ftParadoxOle},
{dbLongBinary} 11 {ftDBaseOle},
{dbBinary} 9 {ftTypedBinary},
{dbText} 10 {ftCursor}
{$IFDEF VER120}
,
{dbText} 10 {ftFixedChar},
{dbText} 10 {ftWideString},
{dbBigInt} 16 {ftLargeint},
{dbText} 10 {ftADT},
{dbText} 10 {ftArray},
{dbText} 10 {ftReference},
{dbText} 10 {ftDataSet}
{$ELSE}
{$IFDEF VER125}
,
{dbText} 10 {ftFixedChar},
{dbText} 10 {ftWideString},
{dbBigInt} 16 {ftLargeint},
{dbText} 10 {ftADT},
{dbText} 10 {ftArray},
{dbText} 10 {ftReference},
{dbText} 10 {ftDataSet}
{$ELSE}
{$IFDEF VER130}
,
{dbText} 10 {ftFixedChar},
{dbText} 10 {ftWideString},
{dbBigInt} 16 {ftLargeint},
{dbText} 10 {ftADT},
{dbText} 10 {ftArray},
{dbText} 10 {ftReference},
{dbText} 10 {ftDataSet},
{dbLongBinary} 11 {ftOraBlob},
{dbLongBinary} 11 {ftOraClob},
{dbText} 10 {ftVariant},
{dbText} 10 {ftInterface},
{dbText} 10 {ftIDispatch},
{dbGUID} 15 {ftGuid}
{$ENDIF}
{$ENDIF}
{$ENDIF}
);
3. load a DAO:
try
access := GetActiveOleObject('DAO.DBEngine.35');
except
access := CreateOleObject('DAO.DBEngine.35');
end;
4. open a database
try
db := access.OpenDatabase(yourDatabaseName);
except
exit
end;
5. create a new table in opened database
td := db.CreateTableDef(yourTableName, 0, '', '');
6. add a field descriptions in table
td.Fields.Append(td.CreateField(strFieldName, arrMDBTypes[intDataType], Size));
for example
td.Fields.Append(td.CreateField('ID', arrMDBTypes[intDataType], Size));
td.Fields.Append(td.CreateField('NAME', arrMDBTypes[intDataType], Size));
7. add a table definition in table list
db.TableDefs.Append(td);
8. open the created table in database
recordset := db.OpenTable(yourTableName, 0);
9. append the new record in opened table
recordset.AddNew;
10. change the field values
curField := recordset.Fields[0].Value := 1;
curField := recordset.Fields[1].Value := 'First record';
11. post the new record
recordset.Update(dbUpdateRegular, False);
where
const
dbUpdateRegular = 1;
12. close a recordset
13. close a database
db.Close;
14. destroy a DAO
access := UnAssigned;
2004. október 27., szerda
Synchronize the movement of two forms
Problem/Question/Abstract:
How can I reposition a form relative to another form, which is being dragged by the mouse? I am thinking of a kind of movement synchronization. TControl.WMMove is unfortunately declared private.
Answer:
The following is a primitive example, but it should get you started:
unit FollowForm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Forms, Controls, Buttons,
StdCtrls, ExtCtrls;
type
TFrmFollow = class(TForm)
BtnValidate: TBitBtn;
BtnSave: TBitBtn;
BtnPreview: TBitBtn;
BtnPrint: TBitBtn;
BtnExit: TBitBtn;
BtnHelp: TBitBtn;
procedure BtnExitClick(Sender: TObject);
private
FOldOwnerWindowProc: TWndMethod; {WindowProc for FOwnerForm}
FOwnerForm: TForm;
{Window subclassing methods:}
procedure HookForm;
procedure UnhookForm;
procedure WndProcForm(var AMsg: TMessage);
protected
procedure CreateWnd;
override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
var
FrmFollow: TFrmFollow;
implementation
{$R *.DFM}
resourcestring
SRGSBadUseOfFF = 'FollowForm can only be owned by another form';
constructor TFrmFollow.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
if AOwner <> nil then
begin
if AOwner is TForm then
FOwnerForm := TForm(AOwner)
else
{Owner is not a form}
raise Exception.CreateRes(@SRGSBadUseOfFF);
end;
end;
procedure TFrmFollow.CreateWnd;
begin
inherited;
if csDesigning in ComponentState then
Exit; {Don't need to hook when designing}
if Enabled and Assigned(FOwnerForm) then
HookForm; {Hook the main form's Window}
end;
destructor TFrmFollow.Destroy;
begin
if not (csDesigning in ComponentState) then
UnhookForm; {Stop interfering ...}
inherited Destroy;
end;
procedure TFrmFollow.HookForm;
begin
{Hook the windows procedure of my owner only if I have an owner, the Owner's
window handle has been created and we are not in design mode.}
FOldOwnerWindowProc := nil;
if Assigned(FOwnerForm) and FOwnerForm.HandleAllocated then
begin
if not (csDesigning in ComponentState) then
begin
FOldOwnerWindowProc := FOwnerForm.WindowProc;
FOwnerForm.WindowProc := WndProcForm;
end;
end;
end;
procedure TFrmFollow.UnhookForm;
begin
{If we are "hooked" then undo what Hookform did}
if Assigned(FOldOwnerWindowProc) then
begin
if (FOwnerForm <> nil) and (FOwnerForm.HandleAllocated) then
begin
FOwnerForm.WindowProc := FOldOwnerWindowProc;
end;
FOldOwnerWindowProc := nil;
FOwnerForm := nil;
end;
end;
{WndProcForm is our replacement for our WindowProc. We grab any Windows
messages that we need here.}
procedure TFrmFollow.WndProcForm(var AMsg: TMessage);
var
cmdType: Word;
xPos: Word;
yPos: Word;
begin
if Enabled then
begin
case AMsg.Msg of
WM_MOVE:
begin
xPos := FOwnerForm.Left;
yPos := FOwnerForm.Top;
Caption := Format('%d:%d', [xPos, yPos]);
SetBounds(xPos + 12, yPos + 12, Width, Height);
BringToFront;
end;
WM_SIZE, WM_EXITSIZEMOVE:
begin
BringToFront;
end;
WM_SYSCOMMAND:
begin
cmdType := AMsg.WParam and $FFF0;
case cmdType of
SC_MAXIMIZE, SC_SIZE:
begin
xPos := FOwnerForm.Left;
yPos := FOwnerForm.Top;
Caption := Format('%d:%d', [xPos, yPos]);
SetBounds(xPos, yPos, Width, Height);
BringToFront;
end;
end;
end;
end;
end;
{Call the default windows procedure}
FOldOwnerWindowProc(AMsg);
end;
procedure TFrmFollow.BtnExitClick(Sender: TObject);
begin
Close;
end;
end.
2004. október 26., kedd
How to determine the TImage graphic type at runtime
Problem/Question/Abstract:
Can someone tell me how I get to know the TImage graphic type at runtime? When I know the graphic type, I can choose different ways to save the image.picture to a blob field.
Answer:
Use RTTI like this:
if (Image.Picture.Graphic <> nil) then
begin
if (Image.Picture.Graphic is TBitmap) then
{... it's a bitmap ...}
else if (Image.Picture.Graphic is TIcon) then
{... it's an icon ...}
else if (Image.Picture.Graphic is TMetaFile) then
{... it's a meta file ...}
else if (Image.Picture.Graphic is TJPEGImage) then
{... it's a JPEG ...}
else {... etc.}
end;
2004. október 25., hétfő
Pack a Paradox or dBase table programatically
Problem/Question/Abstract:
How to pack a Paradox or dBase table programmatically
Answer:
Solve 1:
function dgPackParadoxTable(Tbl: TTable; Db: TDatabase): DBIResult;
{Packs a Paradox table by calling the BDE DbiDoRestructure function. The TTable passed as the first parameter must be closed. The TDatabase passed as the second parameter must be connected.}
var
TblDesc: CRTblDesc;
begin
Result := DBIERR_NA;
FillChar(TblDesc, SizeOf(CRTblDesc), 0);
StrPCopy(TblDesc.szTblName, Tbl.TableName);
TblDesc.bPack := True;
Result := DbiDoRestructure(Db.Handle, 1, @TblDesc, nil, nil, nil, False);
end;
Solve 2:
If you use the DBGrid and DBNavigator to delete records in a table which has unique fields, you will find that the table grows relentlessly and you can't re-enter the same data without packing the table first.
The following routine will pack and reindex a DBase and Paradox table, taking from a few seconds to a few minutes. Tested with a 650K DBaseIV file. Much quicker after the first call.
Just add the code below to the relevant sections. Call the function with your table's name, then wait while it grinds away. Returns True if the table is packed successfully.
uses
WinProcs, Classes, SysUtils, StdCtrls, Forms, Controls, DB, DBIProcs, DBITypes,
DBIErrs, DBTables;
{Add to declarations}
function PackTable(tbl: TTable): Boolean;
{Add to Implementation}
function PackTable(tbl: TTable): Boolean;
{Packs a DBaseIV (and Paradox?) table}
var
crtd: CRTblDesc;
db: TDataBase;
begin
try
Screen.Cursor := crHourglass;
Result := True;
with tbl do
begin
db := DataBase;
if Active then
Active := False;
if not db.Connected then
db.Connected := True;
FillChar(crtd, SizeOf(CRTblDesc), 0);
StrPCopy(crtd.szTblName, TableName);
crtd.bPack := True;
if DbiDoRestructure(db.Handle, 1, @crtd, nil, nil, nil, False) <> DBIERR_NONE then
Result := False;
Open;
end;
except
on Exception do {any exception}
Result := False;
end;
Screen.Cursor := crDefault;
end;
Solve 3:
Wouldn't it be great for the TTable component to have a method that does this? Just open up a TTable, connected it to a table on disk, call the method, and wham! The table's packed (Hmm.... I just might have to look into that). But short of that, you have to make direct calls to the BDE to accomplish this task. For dBase tables, it's easy. There's a single function called dbiPackTable that'll pack any dBase file. For Paradox files, you have to jump through a couple of hoops first, then call dbiDoRestructure because Paradox tables can only be packed within the context of a table restructure (Note: If you've restructured a table in Paradox or the Database Desktop, you'll notice a checkbox at the bottom of the restructure dialog called "Pack Table").
Below is a simple procedure for packing tables. I took most of the code right out of the online help (yes, there's really good stuff in there if you know where look), and made some alterations. The difference between what I did and what the help file lists is that instead of the formal parameter being a TTable, I require a fully qualified file name (path/name). This allows for greater flexibility - the procedure opens up its own TTable and works on it instead you having to create one yourself. I guess it might all boil down to semantics, but I still like my way better (so there!). Check out the code below:
procedure PackTable(TblName: string);
var
tbl: TTable;
cProps: CURProps;
hDb: hDBIDb;
TblDesc: CRTblDesc;
begin
tbl := TTable.Create(nil);
with tbl do
begin
Active := False;
DatabaseName := ExtractFilePath(TblName);
TableName := ExtractFileName(TblName);
Exclusive := True;
Open;
end;
// Added 23/7/2000 to make sure that the current path is the same as the table
//see note below
SetCurrentDir(ExtractFilePath(TblName));
// Make sure the table is open exclusively so we can get the db handle...
if not tbl.Active then
raise EDatabaseError.Create('Table must be opened to pack');
if not tbl.Exclusive then
raise EDatabaseError.Create('Table must be opened exclusively to pack');
// Get the table properties to determine table type...
Check(DbiGetCursorProps(tbl.Handle, cProps));
// If the table is a Paradox table, you must call DbiDoRestructure...
if (cProps.szTableType = szPARADOX) then
begin
// Blank out the structure...
FillChar(TblDesc, sizeof(TblDesc), 0);
// Get the database handle from the table's cursor handle...
Check(DbiGetObjFromObj(hDBIObj(tbl.Handle), objDATABASE, hDBIObj(hDb)));
// Put the table name in the table descriptor...
StrPCopy(TblDesc.szTblName, tbl.TableName);
// Put the table type in the table descriptor...
StrPCopy(TblDesc.szTblType, cProps.szTableType);
// Set the Pack option in the table descriptor to TRUE...
TblDesc.bPack := True;
// Close the table so the restructure can complete...
tbl.Close;
// Call DbiDoRestructure...
Check(DbiDoRestructure(hDb, 1, @TblDesc, nil, nil, nil, False));
end
else
{// If the table is a dBASE table, simply call DbiPackTable...} if
(cProps.szTableType = szDBASE) then
Check(DbiPackTable(tbl.DBHandle, tbl.Handle, nil, szDBASE, True))
else
// Pack only works on Paradox or dBASE; nothing else...
raise EDatabaseError.Create('You can only pack Paradox or dBase tables!');
with tbl do
begin
if Active then
Close;
Free;
end;
end;
See? Nothing fancy. What you should know is that all operations involving dbiDoRestructure revolve around a table descriptor type CRTblDesc. With this record type, you can set various field values, then execute dbiDoRestructure to make your changes. That's kind of the trick to making BDE calls in general. You typically work with some sort of record structure, then use that structure in one of the calls. I know I'm probably oversimplifying, but that's it in a nutshell. The point? Don't be scared of the BDE. More later!
I encourage you to look at the BDE online help under any dbi- subject. There are lots of code examples that will get you on your way.
NOTE: I received an email from Stewart Nightingale who said "When running PackTable straight after saving something to floppy it became obvious that packing a table must use the current directory for temporary tables and things - the current directory was "a:" and it came up with an error saying "No Disk in Drive a:". I put in the line "SetCurrentDir(ExtractFilePath(TblName));" at the top of the procedure so it would work properly ." I have added this line to the code sample shown above - use it if you wish, leave it out if you do not...........
2004. október 24., vasárnap
Create an elliptic form using regions
Problem/Question/Abstract:
How to create an elliptic form using regions
Answer:
Add something like the following to your form's OnCreate event handler:
procedure TForm1.FormCreate(Sender: TObject);
var
Region: HRGN;
begin
Region := CreateEllipticRgn(0, 0, 300, 300);
SetWindowRgn(Handle, Region, True);
end;
2004. október 23., szombat
How to store additional data in a TListBox along with each item it contains
Problem/Question/Abstract:
In my application I have a TListBox control and I need to store additional data along with each item it contains. The additional data will be an integer variable and a status variable. What is the best way of doing this? Is there some other control I can use?
Answer:
TListBox objects are pointers, but you can cast a 32-bit integer to a pointer and store its value directly. Therefore, if your "integer variable" and "status variable" can be crammed into 32 bits, you can do this:
procedure TForm1.Button1Click(Sender: TObject);
var
MyInt, MyStatus: smallint;
Combine: integer;
begin
MyInt := 1234;
MyStatus := 5678;
Combine := MyInt or (MyStatus shl 16);
with ListBox1.Items do
begin
{ store the string and data }
AddObject('Foobar', pointer(Combine));
{ retrieve the data }
MyInt := integer(Objects[0]) and $FFFF;
MyStatus := integer(Objects[0]) shr 16;
end;
end;
2004. október 22., péntek
How to write a Windows NT Service
Problem/Question/Abstract:
How can I turn a regular Delphi application into a service for a Windows NT4 server?
The meaning for this is that the program should run when the server is rebooted and even when the server is not logged on. So putting the program in startup folder is not enough.
Answer:
With Delphi 5, this has become very simple.
Just go to 'File' -> 'New' -> 'Service Application'.
It will create a regular project source with one main 'form', which is derived from TService.
The TService class encapsulates a Windows NT service in an NT service application. A service is accessed via the Service Control Manager. It is usually started during boot time or manually in the control panel 'Services' applet.
The code will look as shown in the example below and consult the online help about TService. You may want to handle the OnExecute event.
type
TService1 = class(TService)
private
{ private declarations }
public
function GetServiceController: TServiceController; override;
end;
2004. október 21., csütörtök
How to avoid flicker when moving or sizing a MDI child form
Problem/Question/Abstract:
I have an MDI application with many child forms. Their windowstate property is set to maximized. When a child form is created and shown, the form visibly resizes as it is shown. I would like the child form to open already maximized without the visible resizing. Any ideas?
Answer:
Preventing visible flicker when moving or sizing MDI children directly after their creation:
{ ... }
public
{ Public declarations }
constructor Create(aOwner: TComponent); override;
end;
implementation
{$R *.DFM}
constructor TMDIChild.Create(aOwner: TComponent);
var
crect: TRect;
chandle: HWND;
begin
{ Get handle of MDI client window }
chandle := application.mainform.clienthandle;
{ Block redrawing of this window and its children }
Lockwindowupdate(chandle);
{ Create this MDI child at default position, it will not be drawn yet
due to the update block }
inherited Create(aOwner);
{ Get the client windows rect and center this form in it }
Windows.GetClientrect(chandle, crect);
SetBounds((crect.right - width) div 2, (crect.bottom - height) div 2, width,
height);
{ Release the block, this allows the form to redraw at the new position }
LockWindowUpdate(0);
end;
2004. október 20., szerda
Enumerating user tables in an InterBase database
Problem/Question/Abstract:
Enumerating user tables in an InterBase database
Answer:
A list of user tables can be retrieved by querying system table rdb$relations.
The example below shows how to do this - it inserts the table names sorted alphabetically into a ListBox (lbSourceTables).
begin
ibcSourceList.SQL.Clear;
ibcSourceList.SQL.Add('select rdb$relation_name from rdb$relations');
ibcSourceList.SQL.Add('where rdb$system_flag = 0');
ibcSourceList.SQL.Add('order by rdb$relation_name');
ibcSourceList.Open;
while not ibcSourceList.Eof do
begin
lbSourceTables.Items.Add(ibcSourceList.Fields[0].AsString);
ibcSourceList.Next;
end;
ibcSourceList.Close;
end;
2004. október 19., kedd
Determining if there is a disk/diskette/CD in a removable-disk drive
Problem/Question/Abstract:
How can I know if there is a CD in the CD drive?
Answer:
The trick is done by calling the API GetDiskFreeSpace and returning its return value as a boolean. The following function takes the drive letter as a parameter (for example 'A', 'D', etc.) and returns True if there is a disk in the drive, or False if not.
var
DrivePath: array[0..3] of char = 'A:\';
function IsDiskIn(drive: char): boolean;
var
d1, d2, d3, d4: longword;
begin
DrivePath[0] := drive;
Result := GetDiskFreeSpace(DrivePath, d1, d2, d3, d4);
end;
In the implementation we use an initialized null-terminated string (DrivePath) that contains the root directory of drive A: and we substitute the drive letter with the one passed as parameter before calling GetDiskFreeSpace.
Sample call
procedure TForm1.Button1Click(Sender: TObject);
begin
if not IsDiskIn('A') then
ShowMessage('Drive A: Not Ready');
end;
Copyright (c) 2001 Ernesto De Spirito
Visit: http://www.latiumsoftware.com/delphi-newsletter.php
2004. október 18., hétfő
Define a border for your TMemo field
Problem/Question/Abstract:
Define a border for your TMemo field
Answer:
The following snippet defines a left border of a width of 20 pixels:
var
Rect: TRect;
begin
SendMessage(Memo1.Handle, EM_GETRECT, 0, LongInt(@Rect));
Rect.Left := 20;
SendMessage(Memo1.Handle, EM_SETRECT, 0, LongInt(@Rect));
Memo1.Refresh;
end;
2004. október 17., vasárnap
Inside Delphi's Classes and Interfaces Part I
Problem/Question/Abstract:
You've probably used classes & interfaces more than once in your delphi programs. Did you ever dtop to think how delphi implements this creatures ?
Answer:
A few words before we start :
First, I want to start this article by saying that all of the knowledge in this paper is derived from viewing the disassembler of Delphi5. Hence everything writen here is valid only for Delphi5 and might change by any upgrade / different version.
Second, inorder to fully understand what is writen in this article, you'll have to dive into some assembler code. I'll explain what the assembler code does, but be prepared, it might get messy.
And now to the real stuff. In delphi a class' instance is a simple pointer. That might seem odd to some people, since you've used instances in delphi many a time, and never had to treat them like pointers. That is correct, but only because boralnd was kind enough to wrap these pointers nicly up.
These pointers actually point to a complicated structor in memory, which we'll try and understand. First we'll look at some simple class' defenition :
TBoo1 = class
FDataA, FDataB: Integer;
end;
var
Boo1: TBoo1;
begin
Boo1 := TBoo1.Create;
end;
Now let's look at what Boo1 points to (Boo1 is a pointer, remember ?) :
(Boo1 points to the following values, each 4 bytes long)
a Pointer to TBoo1's VMT
FDataA
FDataB
Now let's examine a decendant of TBoo1 :
TBoo2 = class(TBoo1)
FDataC, FDataD: Integer;
end;
var
Boo2: TBoo2;
begin
Boo2 := TBoo2.Create;
end;
Boo2 will point to the following values in memory :
a Pointer to TBoo2's VMT
FDataA
FDataB
FDataC
FDataD
Notice that the values that Boo2 points to include some of the values that Boo1 points to. That's very easy to explain - TBoo2 inherites from TBoo1, therefor it must include all of the fields that TBoo1 has.
As a general case, we could state that each class instance points to the following values :
a pointer to the Class' VMT
a list of the Class' parent's fields
a list of the Class' fields
Now it's time to investigate interfaces. Before we can fully understand interfaces we must understand the way delphi makes a method call to a class' instance. What delphi actually does, is call a function with one more parameter than was declared, and that parameter is the instance itself. Let's look at an example :
TMoo = class
FData: Integer;
procedure Act(Value: Integer);
end;
procedure TMoo.Act(Value: Integer);
begin
if FData = Value then
FData := FData + 1
else
FData := Value;
end;
var
Moo: TMoo;
begin
Moo := TMoo.Create;
Moo.Act(15);
end;
How does delphi implement this ? Simple, 'TMoo.Act' is actually compiled into a procedure that accepts two(!) parameters. One is the defined parameter -'Value' of type integer. The other is an instance of class TMoo. Every time delphi calls 'Moo.Act' it does some preprocessing before hand, that is, it passes the instance of TMoo that is making the call. Basically you could say that any call to a method of an object is translated to a regular call to a function / procedure that accepts the object making the call as a parameter.
In the previos example, 'TMoo.Act' is actually compiled to something like this :
procedure TMoo_Act(Self: TMoo; Value: Integer);
begin
if Self.FData = Value then
Self.FData := FData + 1
else
Self.FData := Value;
end;
It's time to go back to interfaces. Consider the following code :
IKoo = interface
function Calculate(Value: Integer): Double;
end;
function Evaluate(Koo: IKoo; Value: Integer): Double;
begin
Result := Koo.Calculate(Value);
end;
TKooA = class(TInterfacedObject, IKoo)
function Calculate(Value: Integer): Double;
end;
TKooB = class(TInterfacedObject, IKoo)
procedure DoNothing;
function Calculate(Value: Integer): Double;
end;
Any class that supports IKoo can be passed as a variable to the function 'Evaluate'. When we pass an instance of TKooA to 'Evaluate' we need to call the first method of TKooA, but when we pass an instance of TKooB, we need to call the second method of TKooB ! How will delphi now which function to call at each time ?!
Inorder to understand the answer, we must review what an interface realy is (and how it is implemented in delphi). An interface is simply a list of methods that a class declares that it implements. That is, each method in the interface is implemented in the class. The way deplhi implements this is thus :
Each interface a class supports is actually a list of pointers to methods. Therefor, each time a method call is made to an interface, the interface actually diverts that call to one of it's pointers to method, thus giving the object that realy imlpements it the chance to act. I'll explain that via the 'Koo' example above :
Each time the function 'Evaluate' gets a parameter of type IKoo, it realy gets a list (with 4 items - IKoo inherites from IUnknown) of pointers to methods. If it got an IKoo interface that was implemented by TKooA, then the 4th item in the pointer-to-method list would point to 'TKooA.Calcualte'. Otherwise it would point to 'TKooB.Calcualte'. Therefor, when a call is made to 'IKoo.Calculate' what actually is called is what 'IKoo.Calcualte' points to (either 'TKooA.Calculate' or TKooB.Calculate'). Thus delphi implements interfaces.
And now to how delphi stores interfaces in memory. For each instance of a class that supports 'N' interfaces, we need 'N' different lists of pointer-to-method (for each interface we need a list of pointer-to-method). But these lists are the same in the scope of a single class, therefor inorder to save memory, we only hold 'N' pointers to these lists for each instance (instead of the lists themselves).
Consider the following code :
ILooA = interface
end;
ILooB = interface
end;
TLoo = class(TInterfacedObject, ILooA, ILooB)
FLooA, FLooB: Integer;
end;
This is how an instance of TLoo would look in memory :
a pointer to TLoo's VMT
FRefcount
IUnknown
FLooA
FLooB
ILooB
ILooA
In general, any class' instance would look like this :
a poitner to the class' VMT
the class' parent's structor (except for the pointer to the VMT)
first data member of the class
.
.
last data member of the class
last interface in the class' interface list
.
.
first interface in the class' interface list
As I said at the begining of this article, inorder to realy grasp the way delphi implements class & interfaces we must look at the assembler code delphi produces.
First we'll learn a bit of assembler inorder to understand to code that will follow. In assembler there is a thing called 'Register'. A register is a place on the CPU that can hold a 32 bit value. On a Pentium CPU there are 8 main registers (EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP). Most actions that are done in assembler are done on registers. Here are a few commands in assembler :
(Moves the value into the register)
MOV Register, Value
(Moves the value in Register2 into Register1)
Mov Register1, Register2
(Moves the value that Register2 points to into Register1. This is the same as the followin code : 'Register1 := Register2^;')
Mov Register1, [Register2]
(Moves the value that Register2 + Value points to into Register1. The same as :
'Register1 := Pointer(Integer(Register2) + Value)^;')
Mov Register1, [Register2 + Value]
Eaxmples :
Mov EAX, 10
MOV EBX, EAX
MOV EAX, [EBX + 6]
EBX will hold the value 10 and EAX will hold the value that is in the address $10.
Just inorder to make sure that you understood this part, I'll give an example of how delphi assignes a value to an instance's data member.
TGoo = class
FDataA, FDataB: Integer;
end;
var
Goo: TGoo;
begin
Goo := TGoo.Create;
Goo.FDataA := 5;
Goo.FDataB := 7;
end;
If you'd open delphi's disassembler you'd see the following code :
//Goo.FDataA := 5;
mov eax, [ebp - $08]
mov[eax + $04], $00000005
//Goo.FDataB := 7;
mov eax, [ebp - $08]
mov[eax + $08], $00000007
Why move the value pointed by 'ebp-$08' ? Simple, that's where the variable Goo is stored. Notice that accessing FDataA is the same as accessing the address at 'eax + $04' and that accessing FDataB is the same as accessing the address at 'eax + $08'. That's because the address 'eax' points to is the pointer to the VMT of TGoo, and (as I mentioned before) the following values in memory are the data members of TGoo.
Let's go back to interfaces. Look at the following code :
IRoo = interface
end;
TRoo = class(TInterfacedObject, IRoo)
end;
var
Roo: TRoo;
RooIntf: IRoo;
begin
Roo := TRoo.Create;
RooIntf := Roo;
RooIntf._AddRef;
end;
The following assembler code isn't exactly what delphi produces but it serves the same point :
// RooIntf := Roo;
// eax holds the value returned by TRoo.Create, that is, the variable Roo
// ecx holds the value that should later be assigned to RooIntf
mov ecx, eax
// This is the same as : 'ecx := ecx + $0C';
add ecx, $0C
// RooIntf._AddRef
// Push 'ecx' onto the CPU's stack
push ecx
mov ecx, [ecx]
// 'call' tells the CPU to jump to the address stored as a value in 'ecx'
call ecx
Let's look at the code that 'call ecx' brought us too :
// POP the value we pushed onto the stack into 'ecx'
pop ecx
// Same as : 'ecx := ecx - $0c;
sub ecx, $0C
// Call the method '_AddRef' with 'ecx' as a variable.
call TInterfacedObject._AddRef(ecx)
A Little explaination is due. Why did delphi add '$0C' to 'ecx' ? remember how Roo is stored in memory (a pointer to VMT, FRefCount (Of InterfacedObject), IUnknown (Of TInterfacedObject), IRoo). IRoo is the forth value in the list that 'ecx' points to. Each value is 4 bytes long, so IRoo is 12 (4*4) bytes after 'ecx', and '$0C' is 12 in exadecimel notation. So basically, adding '$0C' to 'ecx' just made 'ecx' point to the right value, that is, point to IRoo of Roo (an instance of TRoo).
Why do we push ecx into the stack ? That's cause we'll need to use it later, when calling the real '_AddRef' method. Remeber, 'ecx' is the value pointing to Roo + 12.
After that, we move into 'ecx' the value that 'ecx' pointed to. Remeber when I said that instead of holding the lists of pointer-to-method, delphi stores only the pointers to them (to save memory) ? That's why 'ecx' was actually a pointer, but now it holds the value it pointed to before.
The next command, is to call the method that 'ecx' holds. Now we'll look at that method. It's very short. The only thing it does is modify the value of 'ecx' (after poping it from the stack) so it is equal to the value of Roo (that is, it points to the variable Roo). Then the method 'TInterfacedObject._AddRef' is called with 'ecx' (Roo) as a parameter. This is the same as when I've writen that delphi actually complies a Class' method into a regualr function / procedure that accepts one extra parameter - the instance of the class.
What was that good for ? We added a value from a poitner then did this jump around in memory, then subtracted the same value from the pointer and called the function the pointer points too ! why bother ? we could simple call the function without adding and subtracting values !
This is where the power of indirection comes into the game. Notice
that the call to 'RooIntf._AddRef' didn't know that RooIntf was actually of an instance of TRoo. It just called the method that was there to call. The Implementation of this method is where the reassigning of the value of the pointer was made. That is, only the implementation that RooIntf points to (IRoo of TRoo) knew how much was added or substracted from the pointer pushed to the stack. If we had another varaible of type TRoo2, that also implemented IRoo, and we would have made the following assignment 'RooIntf := varaible of type TRoo2', and would call the method 'RooIntf._AddRef' then a different value would be subtracted from the value in the stack. Thus making the method call go to the right place in the TRoo2 class.
2004. október 15., péntek
How to trap mouse clicks on the Desktop when using a system wide mouse hook
Problem/Question/Abstract:
Does anyone know how to tell (in Delphi code) if I have clicked on the Desktop (not an icon). I have written a system wide mouse hook program but the window handle and the icon handle are the same. How can I tell the difference?
Answer:
I did this by creating a DLL (you can only hook into the desktop via a DLL). The DLL then posts messages to the main application. You need to load the DLL, call Initialize supplying the applications handle (note: StdCall). You then need to assign a custom message handler (application.OnMessage) to listen for the messages posted from the DLL.
Here is the application message handler:
const
WM_DESKTOPMOUSEMESSAGE = WM_USER + 1;
procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
case Msg of
WM_DESKTOPMOUSEMESSAGE:
case Msg.WParam of
WM_LBUTTONUP: ShowMessage('You clicked on the desktop');
end;
end;
procedure TForm1.OnCreate(Sender: TObject);
begin
Application.OnMessage := AppMessage;
end;
Here is the hook code:
Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL - even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters.
library test;
uses
SysUtils, Messages, Windows;
{$R *.RES}
const
WM_DESKTOPMOUSEMESSAGE = WM_USER + 1;
var
HookHandle: HHook;
DesktopHandle: HWnd;
AppHandle: HWnd;
procedure log(logstr: string);
var
F1: Textfile;
begin
AssignFile(F1, 'c:\temp.log');
if FileExists('c:\temp.log') then
Append(F1)
else
Rewrite(F1);
writeln(F1, logstr);
CloseFile(F1);
end;
function MouseHook(code: Integer; wparam: WPARAM; lparam: LPARAM): LRESULT stdcall;
var
WinDir: array[0..MAX_PATH] of Char;
f: file of HWnd;
begin
{This only happens once, we use the file just to get the variable across to the systems memory}
if AppHandle = 0 then
begin
GetWindowsDirectory(Windir, MAX_PATH);
AssignFile(f, WinDir + '\ah.dat');
Reset(f);
Read(f, AppHandle);
CloseFile(f);
end;
PostMessage(AppHandle, WM_DESKTOPMOUSEMESSAGE, wParam, lParam);
Result := CallNextHookEx(HookHandle, Code, WParam, LParam);
end;
procedure Initialize(ApplicationHandle: HWnd); stdcall;
var
res, pid: DWORD;
f: file of HWnd;
WinDir: array[0..MAX_PATH] of Char;
begin
{Write the application handle to a file so that it can be read first time round
by the hook (the hook has its own memory space)}
Fillchar(windir, sizeOf(WinDir), 0);
GetWindowsDirectory(Windir, MAX_PATH);
AssignFile(f, WinDir + '\ah.dat');
Rewrite(f);
Write(f, ApplicationHandle);
CloseFile(f);
DesktopHandle := FindWindow(nil, 'Program Manager');
if DesktopHandle = 0 then
HookHandle := 0
else
begin
AppHandle := ApplicationHandle;
res := GetWindowThreadProcessID(DesktopHandle, @pid);
HookHandle := SetWindowsHookEx(WH_MOUSE, @MouseHook, hInstance, res);
end;
end;
procedure DeInitialize; stdcall;
begin
if HookHandle <> 0 then
UnHookWindowsHookEx(HookHandle);
end;
exports
Initialize,
DeInitialize;
begin
end.
2004. október 14., csütörtök
Adding a Master Password to a Paradox Table
Problem/Question/Abstract:
How do I add a password to a Paradox table using nothing but code?
Answer:
It is said that necessity is the mother of invention. And I've found that saying to ring true time and time again. With respect to the subject at hand, I didn't need to know how to do this until I actually had to do it with a program that I needed to write.
But before I started out, I did the usual thing by asking myself some questions:
Is there a method in an existing component (for our purposes TTable) that can do this? Why reinvent the wheel?
If not, is there any resource available that might be able to do this? Again, why reinvent the wheel?
If the answer to both questions is "No," then I know I have to build the capability myself.
You might be thinking, why did I spend time with the discussion above? Well folks, we live in an object-oriented world; moreover, we live in a world where there are a lot of software developers. Someone, somewhere had to think the along the same lines. So as a rule of thumb, before I make an attempt to write a specialized function or component, I always do research to make sure it hasn't been created elsewhere. That said, let's move on, shall we?
First of all, let's talk about Paradox passwords. Paradox has a hierarchical password system. Each table can have a master password that defines any access to it. In addition a table can have several auxilliary passwords that define table and field rights, limiting access in very specialized ways.
In general, though, I've found that encrypting a table with just a master table is adequate because most of the programs I've created that require data encryption only require "all or nothing" security. Besides, having to cover the ins and outs of auxilliary passwords in Delphi would have created an artilce that was just too long. In any case, let me list the code below that will add a master password to a Paradox table, then we'll discuss particulars following it. Here goes...
procedure EncryptPDOXTable(TableName,
Password: string);
var
TblDesc: CRTblDesc;
LocDB: TDatabase;
begin
//Initialize the BDE
Check(DBIInit(nil));
//Initialize random number generator
Randomize;
//Create a local, non-owned database object that
//points to the path associated with the table.
LocDB := TDatabase.Create(nil);
with LocDB do
begin
Params.Add('path=' + ExtractFilePath(TableName));
DatabaseName := 'PDOXEncryptDB' + IntToStr(Random(50));
DriverName := 'STANDARD';
Connected := True;
end;
//Now, initialize the Table Descriptor with the values
//required to set a master password.
FillChar(TblDesc, SizeOf(CRTblDesc), 0);
StrPCopy(TblDesc.szTblName, ExtractFileName(TableName));
with TblDesc do
begin
bProtected := True;
StrPCopy(TblDesc.szPassword, Password);
end;
//Now do the restructure.
try
Check(DbiDoRestructure(LocDB.Handle, 1, @TblDesc,
nil, nil, nil, False));
finally
LocDB.Free;
DBIExit;
end;
end;
I think you've figured out by now that to create a master password in a Paradox table, you have to use direct BDE calls. And while it may seem a bit complex, it's actually pretty easy. You'll notice that I put a couple of words in the code in boldface type. These are the two things that you really have to worry about as far as setting a password. The other stuff is pretty routine stuff. So looked at from that perspective simplifies the process entirely. Why don't we discuss the code in a bit more detail.
The first things that I do in the procedure is to initialize the BDE and create a TDatabase object. You'll find that almost all things that you do in the BDE require a database handle of some sort. DbiDoRestructure is no exception. With respect to initializing the BDE, that's purely an option, but something that I've done as a habit primarily because some of my programs don't make use of any data aware components, and thus won't initialize the BDE by default. If you make calls to the BDE without it having been initialized in some way, you'll get an initialization error. So it's a good idea to do this.
The next thing that happens in the code is that I initialize a table descriptor structure. This is a standard structure defined in the BDE that tells BDE functions that use it, like DbiDoRestructure, about the table that they're going to work with. CRTblDesc is a fairly complex structure that has substructures attached to it. If you want to know more about what kinds of fields are in this structure, I suggest looking in the BDE help file in the BDE directory on your hard disk. But the way cool thing about CRTblDesc is that you only need to fill in the fields that are pertinent to the operation you want to perform. In the case of adding a password to a table, all you need to fill in are the szTblName field and szPassword field. That's it.
Then, once the structure has been filled with proper values, it's a simple matter of calling DbiDoRestructure to restructure the table. We supply the handle to the database that was created at the top, the number of table descriptors we're using (1), the address to the table descriptor, then set the next three parameters to "nil" and the last parameter to False. Easy.
If you want to know more about using DbiDoRestructure (since it's obviously used to do many more table operations than what I just discussed) I encourage you to study the online help, or obtain a copy of the "Borland Database Engine Developer's Guide" which you can purchase directly from Borland. Cheers!
2004. október 13., szerda
Create a (unique) GUID (2)
Problem/Question/Abstract:
I have a very simple piece of test code to generate and display a GUID on the screen every time a user clicks on a button on a form. When I compile the program and run the executable on my Windows 2000 machine, I get a unique GUID every time I click the button, which is what I expect from the documentation. However, when I run the same executable on any Windows 98 SE machine, and even a Windows NT 4.0 Server (with SP5), it simply generates the exact same GUID over, and over, and over again.
Answer:
Using RAW API:
{ ... }
var
Form1: TForm1;
UuidCreateFunc: function(var guid: TGUID): HResult; stdcall;
implementation
{$R *.DFM}
uses
ComObj;
procedure TForm1.Button1Click(Sender: TObject);
var
hr: HRESULT;
m_TGUID: TGUID;
handle: THandle;
begin
handle := LoadLibrary('RPCRT4.DLL');
@UuidCreateFunc := GetProcAddress(Handle, 'UuidCreate');
hr := UuidCreateFunc(m_TGUID);
if failed(hr) then
RaiseLastWin32Error;
ShowMessage(GUIDToString(m_TGUID));
end;
With WIN2K support:
{ ... }
var
Form1: TForm1;
UuidCreateFunc: function(var guid: TGUID): HResult; stdcall;
implementation
{$R *.DFM}
uses
ComObj;
procedure TForm1.Button1Click(Sender: TObject);
var
hr: HRESULT;
m_TGUID: TGUID;
handle: THandle;
WinVer: _OSVersionInfoA;
begin
handle := LoadLibrary('RPCRT4.DLL');
WinVer.dwOSVersionInfoSize := sizeof(WinVer);
getversionex(WinVer);
if WinVer.dwMajorVersion >= 5 then {Windows 2000 }
@UuidCreateFunc := GetProcAddress(Handle, 'UuidCreateSequential')
else
@UuidCreateFunc := GetProcAddress(Handle, 'UuidCreate');
hr := UuidCreateFunc(m_TGUID);
if failed(hr) then
RaiseLastWin32Error;
ShowMessage(GUIDToString(m_TGUID));
end;
2004. október 12., kedd
How to store and retrieve a text file in / from a resource
Problem/Question/Abstract:
I am trying to work out how to store approximately 1000 lines of text (3 columns, 30 chars each) inside an application. I do not want to have any .INI entries and would prefer not to use an external file.
Answer:
You can start with one and embed it into a resource. Let's assume you have the data in a normal textfile. Make a resource script file (filedata.rc) with a line like
FILEDATA RCDATA filedata.txt
Add this RC file to your Delphi 5 project group. This automatically gets you a $R line for it in the project DPR file and the resource is compiled for you when you build the project. Now, how to get at the data at runtime? Each line is a fixed length of 90 characters. So the file could be represented as an array of records of this type:
type
TFileData = packed record
col1, col2, col3: array[1..30] of Char;
crlf: array[1..2] of Char;
end;
PFileData = ^TFileData;
Let's use a class to regulate access to the data. I assume you only need to read it, so there is no need to copy it from the resource.
type
TDatahandler = class
private
FData: TList;
FResHandle: THandle;
function GetRecord(index: Integer): TFileData;
procedure GetRecordCount: Integer;
procedure InitDatalist;
public
constructor Create;
destructor Destroy; override;
property Records[index: Integer]: TFileData read GetRecord;
property Recordcount: Integer read GetRecordcount;
end;
implementation
constructor TDatahandler.Create;
begin
inherited;
FData := TList.Create;
InitDatalist;
end;
destructor TDatahandler.Destroy;
begin
Fdata.Free;
if FResHandle <> 0 then
begin
UnlockResource(FResHandle);
FreeResource(FResHandle);
end;
inherited;
end;
function TDatahandler.GetRecord(index: Integer): TFileData;
begin
Result := PFileData(FData[i])^;
end;
procedure TDatahandler.GetRecordCount: Integer;
begin
Result := FData.Count;
end;
procedure TDatahandler.InitDatalist;
var
dHandle: THandle;
pData: PFileData;
numRecords, i: Integer;
begin
pData := nil;
dHandle := FindResource(hInstance, 'FILEDATA', RT_RCDATA);
if dHandle <> 0 then
begin
numRecord := SizeofResource(hInstance, dHandle) div Sizeof(TFiledata);
FResHandle := LoadResource(hInstance, dHandle);
if FResHandle <> 0 then
begin
pData := LockResource(dHandle);
if pData <> nil then
begin
FData.Capacity := NumRecords;
for i := 1 to Numrecords do
begin
FData.Add(pData);
Inc(pData);
end;
end
else
raise Exception.Create('Lock failed');
end
else
raise Exception.Create('Load failed');
end
else
raise Exception.Create('Resource not found');
end;
You can add a method to sort the FData list, for example, or a filter method that would populate another TList with pointer to records that match a set of criteria.
2004. október 11., hétfő
How to sort a TCheckListBox without loosing the check state
Problem/Question/Abstract:
How to sort a TCheckListBox without loosing the check state
Answer:
Sorting without loosing the check state is a bit of a challenge, but it can be done. The code below has only been superficially tested:
{ ... }
type
TItemState = class
public
Data: TObject;
Checked: Boolean;
constructor Create(aData: TObject; aChecked: Boolean);
end;
constructor TItemState.Create(aData: TObject; aChecked: Boolean);
begin
inherited Create;
Data := aData;
Checked := aChecked;
end;
procedure CustomSortChecklist(aList: TChecklistbox; Compare: TStringListSortCompare = nil);
var
sl: TStringlist;
i: Integer;
stateobj: TItemState;
begin
Assert(Assigned(aList), 'CustomSortChecklist: no list to sort.');
sl := TStringlist.Create;
try
sl.Assign(aList.Items);
for i := 0 to sl.Count - 1 do
sl.Objects[i] := TItemState.Create(sl.Objects[i], aList.Checked[i]);
if Assigned(Compare) then
sl.CustomSort(Compare)
else
sl.Sort;
alist.Items.BeginUpdate;
try
aList.Clear;
for i := 0 to sl.Count - 1 do
begin
stateobj := sl.Objects[i] as TItemState;
aList.Items.AddObject(sl[i], stateobj.Data);
aList.Checked[i] := stateobj.Checked;
end;
finally
aList.Items.EndUpdate;
end;
finally
for i := 0 to sl.Count - 1 do
if Assigned(sl.Objects[i]) and (sl.Objects[i] is TItemState) then
sl.Objects[i].Free;
sl.free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
CustomSortChecklist(checklistbox1);
end;
function ReverseSort(List: TStringList; Index1, Index2: Integer): Integer;
begin
result := AnsiCompareText(list[index2], list[index1]);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
CustomSortChecklist(checklistbox1, Reversesort);
end;
2004. október 10., vasárnap
Hacking the Help Files
Problem/Question/Abstract:
Hacking the Help Files
Answer:
I often get tired of help files with broken links, topics that are hard to get to and difficult navigation. However, there's a great way you can use the arrow keys (along with SHIFT+CTRL) to navigate through the help files.
To be able to navigate through the help files using CTRL+SHIFT+arrow key (left or right), add the following lines to WIN.INI:
[Windows Help]
Help Author=1
This will enabled "Help Author Mode" on Windows. What it means is that you'll have special privileges on help files, such as:
Topic numbers: Title bar text is replaced by unique topic numbers that identify each topic's position in the Help file.
Easier navigation: You can move to the following or preceding topic by pressing CTRL+SHIFT+RIGHT ARROW or CTRL+SHIFT+LEFT ARROW. To move to the beginning or end of your Help file, press CTRL+SHIFT+HOME or CTRL+SHIFT+END.
Information about topics A Topic Information command appears when you click a topic by using your right mouse button. Clicking this command displays a window with several info reagrding that topic.
Information about hotspots An Ask On Hotspots command appears when you click a topic by using your right mouse button.
This information is documented in the Microsoft Help Workshop 4.0 (HCW.EXE) help file. If you have installed Delphi, you probably have this this tool installed in the Help\MSTools dir under the Delphi installation directory. You'll can also gather lots of information about a help file by using the Report... command on the file menu of Help Workshop.
MS Help Workshop has helped me gather lots of info on other help files, and even find out topics the author probably want to keep as a secret, if that is possible. For example, you can save all the text of a help file to a plain text file, or save a listing of all the topics of a help file to a text file.
"Help Author Mode" can also be enabled by selecting "Help Author" from the File menu of MS Help workshop.
2004. október 9., szombat
Some useful Windows NT functions
Problem/Question/Abstract:
Some useful Windows NT functions
Answer:
{-----------------------------------------------------------------------------
Unit Name: unitNTFunctions
Author: StewartM
Documentation Date: 22 February, 2002 (11:04)
Version 1.0
-----------------------------------------------------------------------------
Purpose:
To provide a few handy Windows NT API functions.
Description:
Unit written by Stewart Moss (except where indicated)
Some of the functions are incomplete or not tested.
Copyright 2001 by Stewart Moss. All rights reserved.
-----------------------------------------------------------------------------}
unit unitNTFunctions;
// Unit written by Stewart Moss (except where indicated)
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
const
BSECURITY_NULL_SID_AUTHORITY = 0;
BSECURITY_WORLD_SID_AUTHORITY = 1;
BSECURITY_LOCAL_SID_AUTHORITY = 2;
BSECURITY_CREATOR_SID_AUTHORITY = 3;
BSECURITY_NT_AUTHORITY = 5;
SECURITY_INTERACTIVE_RID = $00000004;
SECURITY_BUILTIN_DOMAIN_RID = $00000020;
DOMAIN_ALIAS_RID_ADMINS = $00000220;
ACL_REVISION = 2;
SECURITY_DESCRIPTOR_REVISION = 1;
type
PACE_Header = ^TACE_Header;
TACE_Header = record
AceType: BYTE;
AceFlags: BYTE;
AceSize: WORD;
end;
PAccess_Allowed_ACE = ^TAccess_Allowed_ACE;
TAccess_Allowed_ACE = record
Header: TACE_Header;
Mask: ACCESS_MASK;
SidStart: DWORD;
end;
const
SECURITY_NT_AUTHORITY: TSIDIdentifierAuthority =
(Value: (0, 0, 0, 0, 0, 5));
function ISHandleAdministrator(UserToken: THandle): Boolean;
function IsAdmin: Boolean;
function ReturnUserHandle(Username: string): THandle;
function IsWinNT: boolean;
function TryToLoginAsUser(Username, Domain, Password: string): THandle;
implementation
function ISHandleAdministrator(UserToken: THandle): Boolean;
// this function written by Stewart Moss
var
tmpBuffer: array[0..1024] of char;
BufferPtr: Pointer;
ptgGroups: PTokenGroups;
dwInfoBufferSize: DWord;
PSIDAdministrators: PSID;
siaNTAuthority: SID_IDENTIFIER_AUTHORITY;
X: DWord;
bSuccess: Boolean;
begin
GetMem(PtgGroups, 1024);
bSuccess := GetTokenInformation(UserToken, TokenGroups, ptgGroups,
1024, dwInfoBufferSize);
result := false;
if not bsuccess then
exit;
if not AllocateAndInitializeSid(siaNtAuthority, 2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
psidAdministrators) then
exit;
for x := 0 to ptgGroups.GroupCount do
begin
if EqualSID(psidAdministrators, ptgGroups.Groups[x].SID) then
begin
result := true;
break;
end;
end;
freemem(PtgGroups);
Freemem(PsidAdministrators);
result := true;
end;
function IsAdmin: Boolean;
// This function written by somebody else
var
hAccessToken: THandle;
ptgGroups: PTokenGroups;
dwInfoBufferSize: DWORD;
psidAdministrators: PSID;
x: Integer;
bSuccess: BOOL;
begin
Result := False;
bSuccess := OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True,
hAccessToken);
if not bSuccess then
begin
if GetLastError = ERROR_NO_TOKEN then
bSuccess := OpenProcessToken(GetCurrentProcess, TOKEN_QUERY,
hAccessToken);
end;
if bSuccess then
begin
GetMem(ptgGroups, 1024);
bSuccess := GetTokenInformation(hAccessToken, TokenGroups,
ptgGroups, 1024, dwInfoBufferSize);
CloseHandle(hAccessToken);
if bSuccess then
begin
AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2,
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0, psidAdministrators);
{$R-}
for x := 0 to ptgGroups.GroupCount - 1 do
if EqualSid(psidAdministrators, ptgGroups.Groups[x].Sid) then
begin
Result := True;
Break;
end;
{$R+}
FreeSid(psidAdministrators);
end;
FreeMem(ptgGroups);
end;
end;
function ReturnUserHandle(Username: string): THandle;
// Function written by Stewart Moss
begin
end;
function IsWinNT: boolean;
// Function Written by Stewart Moss
var
osv: TOSVERSIONINFO;
begin
result := false;
osv.dwOSVersionInfoSize := sizeOf(OSVERSIONINFO);
GetVersionEx(osv);
if (osv.dwPlatformId = VER_PLATFORM_WIN32_NT) then
result := true;
end;
function TryToLoginAsUser(Username, Domain, Password: string): THandle;
// Function written by Stewart Moss
// returns 0 if failed else User Handle
var
tmpstr: string;
hToken: THandle;
begin
result := 0;
if (UserName = '') or (Domain = '') then
exit;
if not LogonUser(PChar(Username), Pchar(Domain), PChar(Password),
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, hToken) then
exit;
result := hToken;
end;
(*function ApplySecurityDescriptorToRegistryKey(Key : Hkey): Boolean;
var lRv : longint;
siaNtAuthority : SID_IDENTIFIER_AUTHORITY;
psidSystem, psidAdministrators: PSID;
tmpACL : ACL;
pNewDACL : PACL;
dwACL : DWord;
ACLRevision : ACL_REVISION_INFORMATION;
begin
siaNtAuthority := SECURITY_NT_AUTHORITY;
result := false;
InitializeSid(psidAdministrators, siaNtAuthority,2);
InitializeSid(psidSystem, siaNtAuthority,1);
//*(GetSidSubAuthority(psidAdministrators,0)) = SECURITY_BUILTIN_DOMAIN_RID;
//*(GetSidSubAuthority(psidAdministrators,1)) = DOMAIN_ALIAS_RID_ADMINS;
//*(GetSidSubAuthority(psidSystem,0)) = SECURITY_LOCAL_SYSTEM_RID;
// getmem(pNewDACL, sizeof(PACL));
// pNewDACL := tmpAcl;
dwAcl := sizeof(PACL);
if not GETAclInformation(pnewAcl,
if (not InitializeAcl(pnewDACL,
dwACL,
ACL_REVISION)) then exit;
if (!AddAccessAllowedAce(pNewDACL,
ACL_REVISION,
KEY_ALL_ACCESS,
psidAdministrators)) return FALSE;
if (!AddAccessAllowedAce(pNewDACL,
ACL_REVISION,
KEY_ALL_ACCESS,
psidSystem)) return FALSE;
if (!InitializeSecurityDescriptor(psdAbsoluteSD,
SECURITY_DESCRIPTOR_REVISION)) return FALSE;
if (!SetSecurityDescriptorDacl(psdAbsoluteSD,
TRUE, // fDaclPresent flag
pNewDACL,
FALSE)) // not a default DACL
return FALSE;
if (!IsValidSecurityDescriptor(psdAbsoluteSD)) return FALSE;
lRv=RegSetKeySecurity(hKey,
(SECURITY_INFORMATION)(DACL_SECURITY_INFORMATION),
psdAbsoluteSD);
if (lRv!=ERROR_SUCCESS) return FALSE;
return TRUE;
}
*)
function do_SetRegACL: boolean;
var
sia: TSIDIdentifierAuthority;
pInteractiveSid, pAdministratorsSid: PSID;
sd: Windows.TSecurityDescriptor;
pDacl: PACL;
dwAclSize: DWORD;
aHKey: HKEY;
lRetCode: longint;
bSuccess: boolean;
begin
sia.Value[0] := 0;
sia.Value[1] := 0;
sia.Value[2] := 0;
sia.Value[3] := 0;
sia.Value[4] := 0;
sia.Value[5] := BSECURITY_NT_AUTHORITY;
pInteractiveSid := nil;
pAdministratorsSid := nil;
pDacl := nil;
bSuccess := false; // assume this function fails
//
// open the key for WRITE_DAC access
//
lRetCode := RegOpenKeyEx(
HKEY_CURRENT_USER,
'SOFTWARE\Test',
0,
WRITE_DAC,
aHKey
);
if (lRetCode <> ERROR_SUCCESS) then
begin
ShowMessage('Error in RegOpenKeyEx');
result := false;
end;
//
// prepare a Sid representing any Interactively logged-on user
//
if (not AllocateAndInitializeSid(
sia,
1,
SECURITY_INTERACTIVE_RID,
0, 0, 0, 0, 0, 0, 0,
pInteractiveSid
)) then
begin
ShowMessage('Error in: AllocateAndInitializeSid');
//goto cleanup;
end;
//
// prepare a Sid representing the well-known admin group
//
if (not AllocateAndInitializeSid(
sia,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
pAdministratorsSid
)) then
begin
ShowMessage('Error in: AllocateAndInitializeSid');
// goto cleanup;
end;
//
// compute size of new acl
//
dwAclSize := sizeof(TACL) +
2 * (sizeof(TAccess_Allowed_ACE) - sizeof(DWORD)) +
GetLengthSid(pInteractiveSid) +
GetLengthSid(pAdministratorsSid);
//
// allocate storage for Acl
//
pDacl := PACL(HeapAlloc(GetProcessHeap(), 0, dwAclSize));
//if(pDacl == nil) goto cleanup;
if (not InitializeAcl(pDacl^, dwAclSize, ACL_REVISION)) then
begin
ShowMessage('Error in: InitializeAcl');
//goto cleanup;
end;
//
// grant the Interactive Sid KEY_READ access to the perf key
//
if (not AddAccessAllowedAce(
pDacl^,
ACL_REVISION,
KEY_READ,
pInteractiveSid
)) then
begin
ShowMessage('Error in: AddAccessAllowedAce');
//goto cleanup;
end;
//
// grant the Administrators Sid KEY_ALL_ACCESS access to the perf key
//
if (not AddAccessAllowedAce(
pDacl^,
ACL_REVISION,
KEY_ALL_ACCESS,
pAdministratorsSid
)) then
begin
ShowMessage('Error in: AddAccessAllowedAce');
//goto cleanup;
end;
if (not InitializeSecurityDescriptor(@sd, SECURITY_DESCRIPTOR_REVISION)) then
begin
ShowMessage('Error in: InitializeSecurityDescriptor');
//goto cleanup;
end;
if (not SetSecurityDescriptorDacl(@sd, TRUE, pDacl, FALSE)) then
begin
ShowMessage('Error in: SetSecurityDescriptorDacl');
//goto cleanup;
end;
//
// apply the security descriptor to the registry key
//
lRetCode := RegSetKeySecurity(
aHKey,
SECURITY_INFORMATION(DACL_SECURITY_INFORMATION),
@sd
);
if (lRetCode <> ERROR_SUCCESS) then
begin
ShowMessage('Error in: RegSetKeySecurity');
//goto cleanup;
end;
bSuccess := TRUE; // indicate success
end;
end.
2004. október 8., péntek
Make a form use two frames in separate units
Problem/Question/Abstract:
How can I make a function which will create a frame (given its class) in a new form?
Answer:
Maybe you should use an enumerated value which is an index of an array of your frame classes. An example with a form using two frames in seperate units:
unit UnitTestForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, UnitFrameOne, UnitFrameTwo;
type
{'class of' and enumeration}
TFrameClass = class of TFrame;
TFrameEnm = (frm_one, frm_two);
{array of classes}
TFrameClasses = array[TFrameEnm] of TFrameClass;
{test form}
TForm1 = class(TForm)
btnCreateFrame1: TButton;
btnCreateFrame2: TButton;
procedure btnCreateFrame1Click(Sender: TObject);
procedure btnCreateFrame2Click(Sender: TObject);
private
function CreateFrame(FrameEnm: TFrameEnm): TForm;
public
end;
var
Frames: TFrameClasses = (TFrameOne, TFrameTwo);
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btnCreateFrame1Click(Sender: TObject);
var
Form: TForm;
begin
Form := Self.CreateFrame(frm_one);
Form.ShowModal;
end;
procedure TForm1.btnCreateFrame2Click(Sender: TObject);
var
Form: TForm;
begin
Form := Self.CreateFrame(frm_two);
Form.ShowModal;
end;
function TForm1.CreateFrame(FrameEnm: TFrameEnm): TForm;
var
aFrame: TFrame;
begin
Result := TForm.Create(nil);
aFrame := Frames[FrameEnm].Create(Result);
aFrame.Parent := Result;
aFrame.Align := alClient;
end;
2004. október 7., csütörtök
How to convert accented characters to unaccented ones
Problem/Question/Abstract:
Is there a way to convert accented characters to unaccented (meaning ASCII A - Z, a - z)?
Answer:
The classical way is to have a conversion table and do a lookup in that table. The problem with that is that the table is of course specific to a certain charset (encoding), like Windows Latin-1. You could build a table for a range of UNICODE (widechar) characters to get around this limitation and convert the strings to widestrings before you do the accent removals. The routine below uses ANSI characters with the Windows western (Latin-1) encoding.
function SimplifyChar(const _ch: char): char;
const
Charmap: array[#128..#255] of Char = (
#128 { ? }, #129 { ? }, #130 { ? }, #131 { ? }, #132 { ? },
#133 { ? }, #134 { ? }, #135 { ? }, #136 { ? }, #137 { ? },
#138 { ? }, #139 { ? }, #140 { ? }, #141 { ? }, #142 { ? },
#143 { ? }, #144 { ? }, #145 { ? }, #146 { ? }, #147 { ? },
#148 { ? }, #149 { ? }, #150 { ? }, #151 { ? }, #152 { ? },
#153 { ? }, #154 { ? }, #155 { ? }, #156 { ? }, #157 { ? },
#158 { ? }, #159 { ? }, #160 { � }, #161 { · }, #162 { � },
#163 { � }, #164 { � }, #165 { � }, #166 { ¦ }, #167 { � },
#168 { � }, #169 { © }, #170 { � }, #171 { « }, #172 { ¬ },
#173 { }, #174 { ® }, #175 { � }, #176 { � }, #177 { ± },
#178 { � }, #179 { � }, #180 { � }, #181 { µ }, #182 { ¶ },
#183 { · }, #184 { � }, #185 { ± }, #186 { � }, #187 { » },
#188 { � }, #189 { � }, #190 { µ }, #191 { � }, 'A' { � },
'A' { � }, 'A' { � }, 'A' { � }, 'A' { � }, 'A' { � },
#198 { � }, #199 { � }, 'E' { � }, 'E' { � }, 'E' { � },
'E' { � }, 'I' { � }, 'I' { � }, 'I' { � }, 'I' { � },
#208 { � }, #209 { � }, 'O' { � }, 'O' { � }, 'O' { � },
'O' { � }, 'O' { � }, #215 { � }, #216 { � }, 'U' { � },
'U' { � }, 'U' { � }, 'U' { � }, #221 { � }, #222 { � },
#223 { � }, 'a' { � }, 'a' { � }, 'a' { � }, 'a' { � },
'a' { � }, 'a' { � }, #230 { � }, #231 { � }, 'e' { � },
'e' { � }, 'e' { � }, 'e' { � }, 'i' { � }, 'i' { � },
'i' { � }, 'i' { � }, #240 { � }, #241 { � }, 'o' { � },
'o' { � }, 'o' { � }, 'o' { � }, 'o' { � }, #247 { � },
#248 { � }, 'u' { � }, 'u' { � }, 'u' { � }, 'u' { � },
#253 { � }, #254 { � }, #255 { � }
);
begin
if _ch >= #128 then
Result := Charmap[_ch]
else
Result := _ch;
end;
The charmap table was created by this little routine and then edited:
procedure CreateCharacterMap(fromchar, tochar: Char);
function DisplayStr(const ch: Char): string;
begin
if ch < #32 then
Result := '^' + Chr(Ord('A') - 1 + Ord(ch))
else
Result := ch;
end;
var
sl: TStringlist;
line, element: string;
ch: char;
begin
Assert(fromchar <= tochar);
sl := Tstringlist.Create;
try
sl.Add('Const');
line := Format(' Charmap: array [#%d..#%d] of Char = (', [Ord(fromchar),
Ord(tochar)]);
sl.Add(line);
line := '';
for ch := fromchar to toChar do
begin
element := Format('#%3.3d { %s }', [Ord(ch), DisplayStr(ch)]);
if (Length(line) + Length(element)) > 66 then
begin
sl.Add(' ' + line);
line := '';
end;
line := line + element;
if ch <> tochar then
line := line + ', ';
end;
sl.Add(' ' + line);
sl.add(' );');
Clipboard.AsText := sl.Text;
finally
sl.Free
end;
end;
Feliratkozás:
Bejegyzések (Atom)