2005. július 31., vasárnap
Local Routines or Nested Routines
Problem/Question/Abstract:
How to declare Local routines in delphi ?
Answer:
It is possible to declare local procedures or Functions within a procedure or function. Though it seems some how unusual at the first glance to declare Local routines within a routine, it is efficient to do this. If we do not need them anywhere other than the routine, why should make them public even within the unit ? Let us place them in their proper place, proper routines in proper place !
We declare local variables, constants and types before the BEGIN statement of a function or procedure. We can also include local routines here. Though it is efficient to include local routines here, but, in practice, very few delphi programmers use these techniques.
Example:
procedure PublicProc(p1: TypeofP1; p2: TypeofP2; ....pn: TypeOfPn);
var
v1: TypeofV1;
V2: TypeofV2;
.....
Vn: TypeofVn;
const
c1: TypeofC1;
{ ............ }
{ ............ }
procedure LocalProcedure1(p1: TypeofP1; p2: TypeofP2; ....pn: TypeOfPn);
var
{ ............ }
{ ............ }
begin
{ ............ }
{ ............ }
end;
procedure LocalProcedure2(p1: TypeofP1; p2: TypeofP2; ....pn: TypeOfPn);
var
{ ............ }
{ ............ }
begin
{ ............ }
{ ............ }
end;
function LocalFunction1(p1: TypeofP1; p2: TypeofP2; ....pn: TypeOfPn): ResultType;
var
{ ............ }
{ ............ }
begin
{ ............ }
{ ............ }
result := {..... }
end;
function LocalFunction2(p1: TypeofP1; p2: TypeofP2; ....pn: TypeOfPn): ResultType;
var
{ ............ }
{ ............ }
begin
{ ............ }
{ ............ }.
result := {..... }
end;
begin {PublicProc}
...........
'''''''''''
LocalProcedure1(...., ...., ....);
...........
LocalProcedure2(...., ...., ....);
...........
v1 := LocalFunction1(...., ...., ....);
...........
v2 := LocalFunction2(...., ...., ....);
...........
'''''''''''
end; {PublicProc}
In this example, the scope of the nested routines
LocalProcedure1,
LocalProcedure2,
LocalFunction1 and
LocalFunction2
is limited only to PublicProc. No other routines in the same unit or in other
units can see them.
2005. július 30., szombat
Show a hint for an iconized application
Problem/Question/Abstract:
I need to show some information to the user when the mouse moves over the icon in the tray sector. I am using the Shell_NotifyIcon funtion with NIM_ADD, NIM_DELETE parameters to show or not show the icon. How to show that hint?
Answer:
It is automatic, you only need to tell Shell_NotifyIcon which hint to use.
{Update the tray icons tooltip}
procedure UpdateTrayTip;
var
nim: TNotifyIconData;
begin
FillChar(nim, sizeof(nim), 0);
nim.cbSize := Sizeof(nim);
nim.Wnd := wnd;
nim.uID := ICONID;
nim.uFlags := NIF_TIP;
StrLCopy(nim.szTip, GetTrayTooltip, Sizeof(nim.szTip) - 1);
Shell_NotifyIcon(NIM_MODIFY, @nim);
end;
GetTrayTooltip is a function that in my case returns a PChar pointing at an entry in a constant array of Pchars. You can use StrPLCopy if you have a String holding the tooltip instead. You can set the tip on NIM_ADD as well.
2005. július 29., péntek
Create an Access database at runtime
Problem/Question/Abstract:
How to create an Access database at runtime
Answer:
Solve 1:
Here is an OP function that will do it for you:
procedure CreateMSAccessDB(filename: string);
var
DBEngine, Workspace: Variant;
const
{Important to use the following constant as is}
dbLangGeneral = '';
LANGID = 0x0409;
CP = 1252;
COUNTRY = '0';
dbVersion30 = 32;
begin
DBEngine := CreateOleObject('DAO.DBEngine');
{DBEngine := CreateOleObject('DAO.DBEngine.35'); For DAO 3.5}
Workspace := DBEngine.Workspaces[0];
try
Workspace.CreateDatabase(filename, dbLangGeneral, dbVersion30);
except
on EOleException do
ShowMessage('Database already exists');
end;
end;
Solve 2:
It's very simple to create a empty Access-Database (*.mdb File) using OLE. It's not necessary to have MS-Access installed on your computer. If an exception occures the error message will returned. After creating the DB you can create Tables with simple SQL-Statements.
uses comobj, sysutils;
function CreateAccessDatabase(FileName: string): string;
var
cat: OLEVariant;
begin
result := '';
try
cat := CreateOleObject('ADOX.Catalog');
cat.create('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=' + Filename + ';');
cat := NULL;
except
on e: Exception do
result := e.message;
end;
end;
2005. július 28., csütörtök
Files used/created by Delphi
Problem/Question/Abstract:
Files used/created by Delphi
Answer:
Here is a list of the file extensions created by Delphi and what they all mean:
DPR - Delphi Project File. This is actually a Pascal source file; it just happens to be the main program for the application.
PAS - In Delphi, PAS files are always the source code to either a unit or a form. The main program of an application is in the DPR file.
DFM - These files are always paired with PAS files. The DFM file is the binary data used to set up initial data for components (IE, the properties you set in design mode rather than in code). You can't edit a DFM file with a text editor, but if you open it in Delphi, you will see a textual version of the contents.
DCU - A compiled unit, similar in concept to an OBJ file.
OPT - Project Options; i.e. compiler and linker settings, which form is the main form, what icon to use for the application, etc. Generally, the stuff you edit under Options/Project.
RES - A Windows resource file; generated automatically by Delphi and required by the compilation process. You don't need to worry about this file, but don't delete it either.
EXE - All of the above linked together into runnable format.
~DP - A backup file of the DPR file before the last save operation.
~PA - A backup of a .PAS file.
~DF - A backup of a .DFM file.
2005. július 27., szerda
UnDo in a memo field
Problem/Question/Abstract:
UnDo in a memo field
Answer:
If you have a pop-up menu in a TMemo, and put shortcuts on it for the Cut, Copy, Paste, then you can handle those events, and call CuttoClipBoard, CopytoClipBoard, etc.
However, if you put an Undo option onto your pop-up menu (normally Ctrl-Z), how do you instruct the TMemo to do the Undo?
If the built-in undo is sufficient, you can get it easier than a Ctrl+Z:
Memo1.Perform(EM_UNDO, 0, 0);
To check whether undo is available so as to enable/disable
an undo menu item:
Undo1.Enabled := Memo1.Perform(EM_CANUNDO, 0, 0) <> 0;
2005. július 26., kedd
How to calculate Easter Day for a specified year
Problem/Question/Abstract:
How to calculate Easter Day for a specified year
Answer:
function Easter(Year: Integer): TDateTime;
var
nMonth, nDay, nMoon, nEpact, nSunday, nGold, nCent, nCorx, nCorz: Integer;
begin
{ The Golden Number of the year in the 19 year Metonic Cycle: }
nGold := (Year mod 19) + 1;
{ Calculate the Century: }
nCent := (Year div 100) + 1;
{ Number of years in which leap year was dropped in order to keep in step with the sun: }
nCorx := (3 * nCent) div 4 - 12;
{ Special correction to syncronize Easter with moon's orbit: }
nCorz := (8 * nCent + 5) div 25 - 5;
{ Find Sunday: }
nSunday := (Longint(5) * Year) div 4 - nCorx - 10; { To prevent overflow at year 6554}
{ Set Epact - specifies occurrence of full moon: }
nEpact := (11 * nGold + 20 + nCorz - nCorx) mod 30;
if nEpact < 0 then
nEpact := nEpact + 30;
if ((nEpact = 25) and (nGold > 11)) or (nEpact = 24) then
nEpact := nEpact + 1;
{ Find Full Moon: }
nMoon := 44 - nEpact;
if nMoon < 21 then
nMoon := nMoon + 30;
{ Advance to Sunday: }
nMoon := nMoon + 7 - ((nSunday + nMoon) mod 7);
if nMoon > 31 then
begin
nMonth := 4;
nDay := nMoon - 31;
end
else
begin
nMonth := 3;
nDay := nMoon;
end;
Easter := EncodeDate(Year, nMonth, nDay);
end;
2005. július 25., hétfő
TString Super Sort Class (Descending,Ignore Case and other)
Problem/Question/Abstract:
TStringList has a Sort method and a Sorted property. This feature is not available in it's useful descendant TStrings. This class allows sorting of TString objects with extra functionality ala UNIX style parameters. (Yes I know UNIX is a four letter word but they do have some neat features). The SORT algorythm utilizes the QUICK SORT method.
Answer:
The features I have implemented are
Options
SORT DESCENDING - srtDescending
TREAT SORT FIELD AS NUMERIC - srtEvalNumeric
IGNORE LEADING BLANKS IN FIELD - srtIgnoreBlank
IGNORE CASE OF FIELD - srtIgnoreCase
Switches
-k Start,End position of substring for search
-f Field number of a delimited string (Zero column based)
-d Character delimiter for -f switch (Default = SPACE)
In it's simplest form it just sorts the TStrings ascending
eg. SuperSort.SortStrings(Memo1.Lines,[]);
Assume a semi-colon delimited list like ..
'Mike;34;Green'
'harry;25;Red'
'Jackie;6;Black'
'Bazil;9,Pink'
'john;52;Blue'
To sort this list DESCENDING on AGE (Field 1) and ignore case
SuperSort(MyStrings, ['-f 1','-d ;'], [srtDescending,srtEvalNumeric,srtIgnoreCase]);
Assume a string list of ...
'1999 12 20 AA432 Comment 1'
'2002 10 12 SWA12 Some other words'
'1998 09 11 BDS65 And so on and so on'
To sort this list on ITEM CODE (Positions 12 to 17) with no options
SuperSort(MyStrings,['-k 12,17']);
Methods :
procedure SortStrings(StringList : TStrings; Switches : array of string;
Options : TSuperSortOptionSet = []);
Switches is a string array of -k,-d and -f settings. If it is set to empty array [] then NO switches are active.
Options is an OPTIONAL set of [srtDescending,srtIgnoreCase,srtIgnoreBlank,srtEvalNumeric]
The default is empty set []
Properties :
SortTime : TDateTime;
Returns the time taken for the sort for stats purposes.
Usage Example :
uses SuperSort;
procedure TForm1.Test;
var
Srt: TSuperSort
begin
Srt := TSuperSort.Create;
Srt.SortStrings(Memo1.Lines, [], [srtIgnoreBlank]);
Label1.Caption := 'Time : ' + FormatDateTine('hh:nn:ss:zzz',Srt.SortTime);
Srt.Free;
end;
Unit TSuperSort:
unit SuperSort;
interface
uses Classes, SysUtils;
// =============================================================================
// Class TSuperSort
// Mike Heydon Nov 2002
//
// Sort class that implements Unix style sorts including ..
//
// SWITCHES
// --------
// -k [StartPos,EndPos] - Keyfield to sort on. Start and End pos in string
// -d [Field Delimiter] - Delimter to use with -f switch. default = SPACE
// -f [FieldNumber] - Zero based field number delimeted by -d
//
// OPTIONS SET
// ============
// srtDescending - Sort descending
// srtIgnoreCase - Ignore case when sorting
// srtIgnoreBlank - Ignore leading blanks
// srtEvalNumeric - Treat sort items as NUMERIC
//
// =============================================================================
type
// Sort Options
TSuperSortOptions = (srtDescending, srtIgnoreCase,
srtIgnoreBlank, srtEvalNumeric);
TSuperSortOptionSet = set of TSuperSortOptions;
// ============
// TSuperSort
// ============
TSuperSort = class(TObject)
protected
function GetKeyString(const Line: string): string;
procedure QuickSortStrA(SL: TStrings);
procedure QuickSortStrD(SL: TStrings);
procedure ResolveSwitches(Switches: array of string);
private
FSortTime: TDateTime;
FIsSwitches,
FIsPositional,
FIsDelimited,
FDescending,
FIgnoreCase,
FIgnoreBlank,
FEvalDateTime,
FEvalNumeric: boolean;
FFieldNum,
FStartPos, FEndPos: integer;
FDelimiter: char;
public
procedure SortStrings(StringList: TStrings;
Switches: array of string;
Options: TSuperSortOptionSet = []);
property SortTime: TDateTime read FSortTime;
end;
// -----------------------------------------------------------------------------
implementation
const
BLANK = -1;
EMPTYSTR = '';
// ================================================
// INTERNAL CALL
// Resolve switches and set internal variables
// ================================================
procedure TSuperSort.ResolveSwitches(Switches: array of string);
var
i: integer;
Sw, Data: string;
begin
FStartPos := BLANK;
FEndPos := BLANK;
FFieldNum := BLANK;
FDelimiter := ' ';
FIsPositional := false;
FIsDelimited := false;
for i := Low(Switches) to High(Switches) do
begin
Sw := trim(Switches[i]);
Data := trim(copy(Sw, 3, 1024));
Sw := UpperCase(copy(Sw, 1, 2));
// Delimiter
if Sw = '-D' then
begin
if length(Data) > 0 then
FDelimiter := Data[1];
end;
// Field Number
if Sw = '-F' then
begin
FIsSwitches := true;
FIsDelimited := true;
FFieldNum := StrToIntDef(Data, BLANK);
Assert(FFieldNum <> BLANK, 'Invalid -f Switch');
end;
// Positional Key
if Sw = '-K' then
begin
FIsSwitches := true;
FIsPositional := true;
FStartPos := StrToIntDef(trim(copy(Data, 1, pos(',', Data) - 1)), BLANK);
FEndPos := StrToIntDef(trim(copy(Data, pos(',', Data) + 1, 1024)), BLANK);
Assert((FStartPos <> BLANK) and (FEndPos <> Blank), 'Invalid -k Switch');
end;
end;
end;
// ====================================================
// INTERNAL CALL
// Resolve the Sort Key part of the string based on
// the Switches parameters
// ====================================================
function TSuperSort.GetKeyString(const Line: string): string;
var
Key: string;
Numvar: double;
DCount, i, DPos: integer;
Tmp: string;
begin
// Default
Key := Line;
// Extract Key from switches -k takes precedence
if FIsPositional then
Key := copy(Key, FStartPos, FEndPos)
else if FIsDelimited then
begin
DPos := 0;
DCount := 0;
for i := 1 to length(Key) do
begin
if Key[i] = FDelimiter then
inc(DCount);
if DCount = FFieldNum then
begin
if FFieldNum = 0 then
DPos := 1
else
DPos := i + 1;
break;
end;
end;
if DCount < FFieldNum then
// No such Field Number
Key := EMPTYSTR
else
begin
Tmp := copy(Key, DPos, 4096);
DPos := pos(FDelimiter, Tmp);
if DPos = 0 then
Key := Tmp
else
Key := copy(Tmp, 1, DPos - 1);
end;
end;
// Resolve Options
if FEvalNumeric then
begin
Key := trim(Key);
// Strip any commas
for i := length(Key) downto 1 do
if Key[i] = ',' then
delete(Key, i, 1);
try
Numvar := StrToFloat(Key);
except
Numvar := 0.0;
end;
Key := FormatFloat('############0.000000', Numvar);
// Leftpad num string
Key := StringOfChar('0', 20 - length(Key)) + Key;
end;
// Ignores N/A for Numeric and DateTime
if not FEvalNumeric and not FEvalDateTime then
begin
if FIgnoreBlank then
Key := trim(Key);
if FIgnoreCase then
Key := UpperCase(Key);
end;
Result := Key;
end;
// ==============================================
// INTERNAL CALL
// Recursive STRING quick sort routine ASCENDING.
// ==============================================
procedure TSuperSort.QuickSortStrA(SL: TStrings);
procedure Sort(l, r: integer);
var
i, j: integer;
x, Tmp: string;
begin
i := l;
j := r;
x := GetKeyString(SL[(l + r) div 2]);
repeat
while GetKeyString(SL[i]) < x do
inc(i);
while x < GetKeyString(SL[j]) do
dec(j);
if i <= j then
begin
Tmp := SL[j];
SL[j] := SL[i];
SL[i] := Tmp;
inc(i);
dec(j);
end;
until i > j;
if l < j then
Sort(l, j);
if i < r then
Sort(i, r);
end;
begin
if SL.Count > 0 then
begin
SL.BeginUpdate;
Sort(0, SL.Count - 1);
SL.EndUpdate;
end;
end;
// ==============================================
// INTERNAL CALL
// Recursive STRING quick sort routine DECENDING
// ==============================================
procedure TSuperSort.QuickSortStrD(SL: TStrings);
procedure Sort(l, r: integer);
var
i, j: integer;
x, Tmp: string;
begin
i := l;
j := r;
x := GetKeyString(SL[(l + r) div 2]);
repeat
while GetKeyString(SL[i]) > x do
inc(i);
while x > GetKeyString(SL[j]) do
dec(j);
if i <= j then
begin
Tmp := SL[j];
SL[j] := SL[i];
SL[i] := Tmp;
inc(i);
dec(j);
end;
until i > j;
if l < j then
Sort(l, j);
if i < r then
Sort(i, r);
end;
begin
if SL.Count > 0 then
begin
SL.BeginUpdate;
Sort(0, SL.Count - 1);
SL.EndUpdate;
end;
end;
// ====================
// Sort a stringlist
// ====================
procedure TSuperSort.SortStrings(StringList: TStrings;
Switches: array of string;
Options: TSuperSortOptionSet = []);
var
StartTime: TDateTime;
begin
StartTime := Now;
FDescending := (srtDescending in Options);
FIgnoreCase := (srtIgnoreCase in Options);
FIgnoreBlank := (srtIgnoreBlank in Options);
FEvalNumeric := (srtEvalNumeric in Options);
ResolveSwitches(Switches);
if FDescending then
QuickSortStrD(StringList)
else
QuickSortStrA(StringList);
FSortTime := Now - StartTime;
end;
end.
2005. július 24., vasárnap
How to store fonts in a resource file
Problem/Question/Abstract:
Is there a way to store a particular font in an *.ini type of file so that it can be recalled when an application starts?
Answer:
There may be copyright issues with Fonts. With that said, you can include the font directly in you program with a resource file.
Using your favorite text editor, create a *.rc file that describes the font:
MY_FONT ANYOL1 "Bauhs93.ttf"
The first two parameters can be whatever you want. They get used in your program later. Then, use the BRCC32.EXE command line compiler that ships with Delphi to create a *.res file. If your file in step 1 was MyFont.rc, the command from the DOS prompt would be:
BRCC32 MyFont
The program will append the .rc to the input, and create a file with the same name except it appends .res: MyFont.res . In your program, add a compiler directive to include your newly created file:
{$R MyFont.res}
This can go right after the default {$R *.DFM} in the implementation section. Add a procedure to create a file from the resource, then make the Font available for use. Example:
procedure TForm1.FormCreate(Sender: TObject);
var
Res: TResourceStream;
begin
Res := TResourceStream.Create(hInstance, 'MY_FONT', Pchar('ANYOL1'));
Res.SavetoFile('Bauhs93.ttf');
Res.Free;
AddFontResource(PChar('Bauhs93.ttf'));
SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
end;
You can now assign the font to whatever you wish:
procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.Font.Name := 'Bauhaus 93';
end;
Caveats:
The above example provides for no error checking whatsoever. The user may already have that font installed.
Notice that the File name is NOT the same as the Font name. It's assumed that you know the font name associated with the file name. You can determine this by double clicking on the file name in the explorer window.
I would recommend placing your font file in the C:\WINDOWS\FONTS folder. It's easier to find them later.
Your newly installed font can be removed programatically, assuming the font is not in use anywhere:
procedure TForm1.FormDestroy(Sender: TObject);
begin
RemoveFontResource(PChar('Bauhs93.ttf'));
SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
end;
2005. július 23., szombat
How to calculate intersection points of lines or line sections with rectangles
Problem/Question/Abstract:
How to calculate intersection points of lines or line sections with rectangles
Answer:
function fuzz(x, fuzzFactor: double): double;
var
s: string;
begin
s := format('%.6f', [x]);
result := StrToFloat(s);
end;
Warning : this function assumes a fuzz factor in comparing values of doubles. This is because
of the tendency of zero sloped edges to need some help in avoiding div-by-zero errors.
function Intersection(p1, p2, p3, p4: pt; var err: boolean): pt;
var
m1, m2, b1, b2: double;
pResult: pt;
begin
err := false;
if p2.x = p1.x then
m1 := MaxReal
else
m1 := (p2.y - p1.y) / (p2.x - p1.x);
if p4.x = p3.x then
m2 := MaxReal
else
m2 := (p4.y - p3.y) / (p4.x - p3.x);
if m1 = m2 then
begin {parallel lines never intersect}
err := true;
exit;
end;
b1 := (p1.y) - (m1 * p1.x);
b2 := (p3.y) - (m2 * p3.x);
if m2 = 0 then
pResult.y := p3.y
else if m1 = 0 then
pResult.y := p1.y
else
pResult.y := ((m1 * b2) - (m2 * b1)) / (m1 - m2);
if (fuzz(m1, 0.0001)) = fuzz(MaxReal, 0.00001) then
pResult.x := p1.x
else if m1 = 0 then
if fuzz(m2, 0.00001) = fuzz(MaxReal, 0.00001) then
pResult.x := p3.x
else
pResult.x := (pResult.y - b1) {/ 0.00001}
else
pResult.x := (pResult.y - b1) / m1;
Result := pResult;
end;
2005. július 22., péntek
How to create context-sensitive help
Problem/Question/Abstract:
How to create context-sensitive help
Answer:
Introduction:
Windows 95 has much better context-sensitive help than Windows 3.1, with support for the small ? button in dialogs and right-button 'What's This?' help. These features allow users to get instant help in dialogs, without opening Help in a separate window. This integration between Help and your application is very user-friendly and makes the program look very professional. Also, your Help file stays smaller because you don't have to include screenshots for all dialogs. Delphi 2.0 allows you to make use of context-sensitive help, but it is not straightforward. Especially using the What's This function requires some investigation.
The goal of this document is to aid Delphi 2.0 developers in adding context-sensitive Help to their application, so that you do not have to reinvent the wheel. You are assumed to have knowledge of Delphi programming and help authoring, and have access to Delphi 2.0, the Help Workshop (provided with the Help Authoring Guide) and a word processor to create .RTF files (or whatever method you use to create help files).
Steps:
Create topics in your Help file that you want to be used as popup-help for dialog controls
Create an include file which contains the mapping of Pascal constants to help identifiers (say mapping.inc)
Add two lines helpcontextidentifier=uniquenumber and ';' i.e. helpokbutton = 1000; for each help topic. The second line with the semi-colon is necessary to allow us to use the include file both in Delphi and Help Workshop
In your Help project file, specify mapping.inc as the Map file
Create a data module in your Delphi project, if you didn't already have one
In the data module add an include statement {$I mapping.inc} preceded by a const statement
Add a TPopupMenu component to the data module, and add one menu-item with the text 'What's This?'
Create an event handler for the OnClick event of this menu item, and add the code shown in bold
The resulting data module code (without any other components you may have in it) should now look like this (I named the module dmMain):
unit Datamodule;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, RegFiles, Menus;
type
TdmMain = class(TDataModule)
PopupMenu1: TPopupMenu;
procedure WhatsThis1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
dmMain: TdmMain;
implementation
{$R *.DFM}
procedure TdmMain.WhatsThis1Click(Sender: TObject);
var
P: TControl;
begin
with PopupMenu1 do
begin
if PopupComponent is TControl then
begin
P := PopupComponent as TControl;
{locate the closest ancestor with a valid HelpContext}
while (P <> nil) and (not (P is TWinControl) or ((P as TWinControl).HelpContext = 0)) do
P := P.Parent;
if (P <> nil) then
Application.HelpCommand(HELP_CONTEXTPOPUP, Longint((P as TWinControl).HelpContext));
end;
end;
end;
In the dialog form, be sure to specify the bordericons as [biHelp] or [biSystemMenu, biHelp] and the border style as bsSingle, to enable the small ? button in the caption of the form
For each control you want What's This help, specify the dmMain.PopupMenu1 as the PopupMenu property
In the OnCreate event handler for the form containing the controls, add a line for each control in the form of: Control1.HelpContext := helpcontextidentifier; where helpcontextidentifier is the constant defined in mapping.inc for this control.
That's it! Compile the Delphi project and the Help project and everything should work. Note that I used a single TPopupMenu for all controls across all dialogs - this is the strength of the data module at work. Now that you have this framework set up, providing context-sensitive help for each new control you add consists of just four steps:
Create a topic for it in the Help source
Create an entry in the mapping file
Add one line in the OnCreate of the form containing the control
Set the PopupMenu of the control to the data module popupmenu.
Remark:
According to the documentation in the Help Authoring Guide, you should create a WndProc in your form to catch the WM_CONTEXTMENU message and respond to it by calling WinHelp() with the control id (coming from Msg.wParam) as the handle, plus an array containing (handle,help topic) pairs. However, apart from being rather cumbersome, this doesn't work for controls which have a parent (like controls on a PageControl) because the handle being passed is the parent's, not the child's. The Help system itself does provide a popup menu (using the HELP_CONTEXTMENU command), so recreating it in the Delphi project is not elegant. However, using the HELP_CONTEXTPOPUP command from Delphi and avoiding the messy WndProc business in my opinion outweighs this minor inelegance by far.
2005. július 21., csütörtök
How to sort a TList
Problem/Question/Abstract:
How to sort a TList
Answer:
procedure BubbleSort(const List: TList; const Compare: TListSortCompare);
var
Limit: Integer;
I: Integer;
Temp: Pointer;
Swapped: Boolean;
begin
for Limit := (List.Count - 1) downto 1 do
begin
Swapped := False;
for I := 0 to (Limit - 1) do
if (Compare(List[I], List[I + 1]) > 0) then
begin
Temp := List[I];
List[I] := List[I + 1];
List[I + 1] := Temp;
Swapped := True;
end;
if (not Swapped) then
Break;
end;
end;
procedure InsertionSort(const List: TList; const Compare: TListSortCompare);
var
Step: Integer;
Temp: Pointer;
I: Integer;
begin
for Step := 1 to (List.Count - 1) do
begin
Temp := List[Step];
for I := (Step - 1) downto 0 do
if (Compare(List[I], Temp) > 0) then
List[I + 1] := List[I]
else
Break;
List[I + 1] := Temp;
end;
end;
procedure ShellSort(const List: TList; const Compare: TListSortCompare);
var
Step: Integer;
H: Integer;
I: Integer;
Temp: Pointer;
begin
H := 1;
while (H <= (List.Count div 9)) do
H := 3 * H + 1;
while (H > 0) do
begin
for Step := H to (List.Count - 1) do
begin
Temp := List[Step];
I := Step - H;
while (I >= 0) do
begin
if (Compare(Temp, List[I]) < 0) then
List[I + H] := List[I]
else
Break;
Dec(I, H);
end;
List[I + H] := Temp;
end;
H := H div 3;
end;
end;
procedure QuickSort1(const List: TList; const Compare: TListSortCompare;
const L: Integer; const R: Integer);
var
I: Integer;
J: Integer;
Temp: Pointer;
begin
I := L - 1;
J := R;
repeat
Inc(I);
while (Compare(List[I], List[R]) < 0) do
Inc(I);
Dec(J);
while (J > 0) do
begin
Dec(J);
if (Compare(List[J], List[R]) <= 0) then
Break;
end;
if (I >= J) then
Break;
Temp := List[I];
List[I] := List[J];
List[J] := Temp;
until
(False);
Temp := List[I];
List[I] := List[R];
List[R] := Temp;
end;
procedure QuickSort(const List: TList; const Compare: TListSortCompare);
begin
QuickSort1(List, Compare, 0, List.Count - 1);
end;
2005. július 20., szerda
Anti Cheat code in savegame files
Problem/Question/Abstract:
Quick way to make Anti Cheat code in save game files using RandSeed
Answer:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
const
NUM_CLUES = 50;
type
Counter = Integer;
TGameRec = record
GameName: string[20];
AnswerFound: array[1..NUM_CLUES] of boolean;
end;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
INIFiles;
var
GameRec: tGameRec;
IniFileName: string;
TheINI: TIniFile;
{ =============================================== }
procedure InitGameRec(var G: TGameRec);
var
C: Counter;
begin
with G do
begin
GameName := 'A Funny Test Game';
for C := 1 to NUM_CLUES do
AnswerFound[C] := false;
end;
end;
{ =============================================== }
function AntiCheatString(R: TGameRec): string;
var
aSeed: LongInt;
S: string;
C: counter;
begin
S := '';
aSeed := 999; // or somwthing else
for C := 1 to length(R.GameName) do
aSeed := aSeed + ord(R.GameName[C]);
for C := 1 to NUM_CLUES do
if R.AnswerFound[C] then
aSeed := aSeed + 333 // or somwthing else
else
aSeed := aSeed + 666; // or somwthing else
RandSeed := aSeed;
for C := 1 to 50 do
S := S + chr(random(26) + ord('A'));
result := S;
end;
{ =============================================== }
procedure SaveINI;
var
R: TGameRec;
C: counter;
S: string;
begin
TheINI := TIniFile.Create(IniFileName);
R := GameRec;
TheINI.WriteString('Files', 'EXE File Name', ParamStr(0));
TheIni.WriteString('Files', 'INI File Name', IniFileName);
TheIni.WriteString('Game', 'Name', R.GameName);
S := AntiCheatString(R);
TheINI.WriteString('Anti Cheat Section', 'Code', S);
for C := 1 to NUM_CLUES do
begin
S := 'Clue Number ' + IntToStr(C);
TheINI.WriteBool('Clues', S, R.AnswerFound[C]);
end;
TheINI.Free;
end;
{ =============================================== }
function ReadINIOK: boolean;
var
R: TGameRec;
C: counter;
aCode,
S: string;
begin
TheINI := TIniFile.Create(IniFileName);
R.GameName := TheINI.ReadString('Game', 'Name', 'Not Found');
for C := 1 to NUM_CLUES do
begin
S := 'Clue Number ' + IntToStr(C);
R.AnswerFound[C] := theINI.ReadBool('Clues', S, false);
end;
aCode := TheINI.ReadString('Anti Cheat Section', 'Code', 'Cheat');
TheINI.Free;
S := AntiCheatString(R);
result := (S = aCode);
end;
{ =============================================== }
procedure TForm1.FormCreate(Sender: TObject);
begin
IniFileName := ExtractFileDir(paramStr(0)) + '\' + 'Test.ini';
if not FileExists(IniFileName) then // save for 1st ever run
begin
InitGameRec(GameRec);
SaveINI;
end;
if ReadINIOK then
ShowMessage('Ini File OK')
else
ShowMessage('Ini File is no good');
end;
end.
2005. július 19., kedd
How to read a TMemoField into a TMemo
Problem/Question/Abstract:
I would like to read the lines from a memo field into my program using FieldByName().As. There does not seem to be any way to move the memo into a TString or TStringList or to access the memo field on a line by line basis. You can use a String or Variant. When you do this you get just one long composite string. Can you help?
Answer:
Almost every TStrings descendant has a LoadFromStream method:
procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);
var
TB: TBlobStream;
begin
with TDataSource(Sender).DataSet do
if (State = dsBrowse) then
begin
TB := TBlobStream.create(FieldByName('Event_Description') as TBlobField, bmRead);
Memo1.Lines.LoadFromStream(TB);
{or ListBox1.items.LoadFromStream(TB);}
{or StringList1.LoadFromStream(TB);}
TB.Free;
end;
end;
2005. július 18., hétfő
Add Taskbar-Button's for SubForms and manage them correctly
Problem/Question/Abstract:
I would like to add some Windows' Taskbar-Buttons for dynamic created forms w/o loosing the ability to 're-focus' the MainForm by clicking it's own button.
Answer:
The following example uses a Button (Button1)to dynamic create forms at runtime. Each form will be accessable via a corresponding Button placed on the Taskbar. You had to include the WMSysCommand method to enable the real "look&feel" of minimizing the MainForm. Otherwise, the MainForm will be minimized to the lower left side of the Screen or will be hidden in the background, so it's not possible to restore it correctly.
If you want to minimize (or hide) all subforms when minimizing the MainForm, you had to iterate through all registered subforms and hide them manualy. I don't know a better way right now, but if you've found any solution...:-)
MainForm Unit
unit MainForm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TMainForm = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
procedure WMSysCommand(var Msg: TMessage); message WM_SYSCOMMAND;
public
{ Public declarations }
procedure CreateParams(var Params:
TCreateParams); override;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
uses
SubForm;
var
SubForm: TSubForm;
procedure TMainForm.FormCreate(Sender: TObject);
begin
SetWindowLong(Application.Handle, GWL_EXSTYLE,
GetWindowLong(Application.Handle, GWL_EXSTYLE) or
WS_EX_TOOLWINDOW and not WS_EX_APPWINDOW);
end;
procedure TMainForm.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW and not WS_EX_TOOLWINDOW;
end;
procedure TMainForm.WMSysCommand(var Msg: TMessage);
begin
DefaultHandler(Msg);
end;
procedure TMainForm.Button1Click(Sender: TObject);
begin
SubForm := TSubForm.Create(Application);
SubForm.Show;
end;
end.
SubForm Unit
unit SubForm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TSubForm = class(TForm)
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
{$R *.DFM}
procedure TSubForm.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;
procedure TSubForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
end.
2005. július 17., vasárnap
How to save two TStringlists to the same stream
Problem/Question/Abstract:
TStrings.LoadFromStream is assuming that itself consumes the rest of the stream! What if I want to save 2 stringlists to the same stream?
Answer:
That is as designed. Just use an intermediate stream to accomplish what you want. Or you can take the following approach using a string buffer:
{ ... }
var
list1: TStringList;
list2: TStringList;
lng: cardinal;
stream: TMemoryStream;
tmpS: string;
begin
list1 := TStringList.Create;
list2 := TStringList.Create;
try
stream := TMemoryStream.Create;
try
{Assume there was code to get something into stream.
The layout of the stream is:
size1|block1|size2|block2}
{Read size of the 1st block}
stream.Read(lng, SizeOf(lng));
if lng > 0 then
begin
{if there are contents, read the block to tmpS}
SetLength(tmpS, lng);
stream.Read(tmpS[1], lng)
{Assign tmpS to the Text property of list1}
list1.Text := tmpS;
end;
{Same procedure for list2}
stream.Read(lng, SizeOf(lng));
if lng > 0 then
begin
SetLength(tmpS, lng);
stream.Read(tmpS[1], lng)
list2.Text := tmpS;
end;
finally
end;
finally
list2.Free;
list1.Free;
end;
end;
2005. július 16., szombat
Interbase Sweep on the Fly in a thread
Problem/Question/Abstract:
In the Interbase Admin components there is a IBValidationService but is hard to use as it is. Sweeping is just one of the functions of the validation service. This component makes doing sweeps of databases alot easier, and also works in a thread. Ideal for use in server applications.
Answer:
(*
Interbase Sweep Thread
Author
Kim Sandell
Email: kim.sandell@nsftele.com
Description
A Thread that performs an Sweep of an interbase database on the fly.
The thread can automatically free itself after the sweep is done.
Note: This can be a lengthy process so make sure you do not interrupt
the program in the middle of the sweep. The sweeping process
can not be interrupted !!! It makes sense to let it run in the
background and free itself if you have a server program !
Parameters
----------
DatabaseName Full : to database
DatabaseUsername The name of the user with rights to sweep the db
DatabasePassword The password of the user
FreeOnTerminate Set this to false if you want to free the thread
yourself. Default is TRUE
Priority The priority of the thread. Default is tpLower
Version
1.0
History
24.09.2002 - Initial version
Known issues
None so far ...
Example of usage
The example below assumes you have included the "IBSweepThread" unit
in the uses clause, and that you have a button on a form.
The Thread must be created and the properties initialized, before the
thread can be Resumed.
procedure TForm1.Button1Click(Sender: TObject);
Var
IBSweep : TIBSweepThread;
begin
Try
IBSweep := TIBSweepThread.Create( True );
IBSweep.DatabaseName := '127.0.0.1:C:\Databases\MyIBDB.GDB';
IBSweep.DatabaseUsername := 'SYSDBA';
IBSweep.DatabasePassword := 'masterkey';
IBSweep.FreeOnTerminate := False; // We want to see the results!
IBSweep.Resume;
{ Wait for it }
While Not IBSweep.Terminated do
Begin
SleepEx(1,True);
Application.ProcessMessages;
End;
{ Just make sure the thread is dead }
IBSweep.WaitForAndSleep;
{ Check for success }
If IBSweep.ResultState = state_Done then
Begin
MessageDlg( 'Sweep OK - Time taken: '+
IntToStr(IBSweep.ProcessTime)+' ms',
mtInformation,[mbOK],0);
ShowMessage( IBSweep.SweepResult.Text );
End Else MessageDlg('Sweep FAILED',mtError,[mbOK],0);
Finally
IBSweep.Free;
End;
end;
*)
unit IBSweepThread;
interface
uses
Windows, Messages, SysUtils, Classes,
IBServices;
const
state_Idle = $0;
state_Initializing = $1;
state_Sweeping = $2;
state_Done = $3;
state_Error = $ - 1;
type
TIBSweepThread = class(TThread)
private
{ Private declarations }
protected
{ Protected declarations }
procedure DoSweep;
public
{ Public declarations }
DatabaseName: string; // Fully qualifyed name to db
DatabaseUsername: string; // Username
DatabasePassword: string; // Password
Processing: Boolean; // True while processing
ResultState: Integer; // See state_xxxx constants
ProcessTime: Cardinal; // Milliseconds of the sweep
property Terminated; // Make the Terminated published
constructor Create(CreateSuspended: Boolean); virtual;
procedure Execute; override;
procedure WaitForAndSleep;
published
{ Published declarations }
end;
implementation
{ TIBSweepThread }
///////////////////////////////////////////////////////////////////////////////
//
// Threads Constructor. Allocated objects, and initializes some
// variables to the default states.
//
// Also sets the Priority and FreeOnTreminate conditions.
//
///////////////////////////////////////////////////////////////////////////////
constructor TIBSweepThread.Create(CreateSuspended: Boolean);
begin
{ Override user parameter }
inherited Create(True);
{ Default parameters }
FreeOnTerminate := False;
Priority := tpLower;
{ Set variables }
Processing := False;
ResultState := state_Idle;
end;
///////////////////////////////////////////////////////////////////////////////
//
// Threads execute loop. Jumps to the DoWork() procedure every 250 ms
//
///////////////////////////////////////////////////////////////////////////////
procedure TIBSweepThread.Execute;
begin
try
{ Perform the Sweep }
DoSweep;
except
on E: Exception do
; // TODO: Execption logging
end;
{ Signal terminated }
Terminate;
end;
///////////////////////////////////////////////////////////////////////////////
//
// Waits for the Thread to finish. Same as WaitFor, but does not take
// 100% CPU time while waiting ...
//
///////////////////////////////////////////////////////////////////////////////
procedure TIBSweepThread.WaitForAndSleep;
var
H: THandle;
D: DWord;
begin
{ Get Handle }
H := Handle;
{ Wait for it to terminate }
repeat
D := WaitForSingleObject(H, 1);
{ System Slizes }
SleepEx(1, True);
until (Terminated) or ((D <> WAIT_TIMEOUT) and (D <> WAIT_OBJECT_0));
end;
///////////////////////////////////////////////////////////////////////////////
//
// Makes a sweep of the database specifyed in the properties.
//
///////////////////////////////////////////////////////////////////////////////
procedure TIBSweepThread.DoSweep;
var
IBSweep: TIBValidationService;
SrvAddr: string;
DBName: string;
begin
try
{ Set Start Time }
ProcessTime := GetTickCount;
{ Extract SrvAddr and DBName from DatabaseName }
SrvAddr := DatabaseName;
{ Correct if Local machine }
if Pos(':', SrvAddr) <> 0 then
begin
Delete(SrvAddr, Pos(':', SrvAddr), Length(SrvAddr));
DBName := DatabaseName;
Delete(DBName, 1, Pos(':', DBName));
end
else
begin
{ Must be localhost since Server Address is missing }
SrvAddr := '127.0.0.1';
DBName := DatabaseName;
end;
{ Set Flags }
Processing := True;
ResultState := state_Initializing;
try
{ Create IBValidationService }
IBSweep := TIBValidationService.Create(nil);
IBSweep.Protocol := TCP;
IBSweep.LoginPrompt := False;
IBSweep.Params.Values['user_name'] := DatabaseUsername;
IBSweep.Params.Values['password'] := DatabasePassword;
IBSweep.ServerName := SrvAddr;
IBSweep.DatabaseName := DBName;
IBSweep.Active := True;
IBSweep.Options := [SweepDB];
try
{ Start the service }
IBSweep.ServiceStart;
{ Set state }
ResultState := state_Sweeping;
{ Get the Report Lines - No lines in Sweeping but needs to be done }
while not IBSweep.Eof do
begin
IBSweep.GetNextLine;
{ Wait a bit }
Sleep(1);
end;
finally
{ Deactive Service }
IBSweep.Active := False;
end;
{ Set State to OK }
ResultState := state_Done;
except
on E: Exception do
begin
{ Set State to OK }
ResultState := state_Error;
end;
end
finally
{ Calculate Process Time }
ProcessTime := GetTickCount - ProcessTime;
{ Free objects }
if Assigned(IBSweep) then
begin
if IBSweep.Active then
IBSweep.Active := False;
IBSweep.Free;
IBSweep := nil;
end;
{ Set flag }
Processing := False;
end;
end;
end.
2005. július 15., péntek
Checking if a URL is valid
Problem/Question/Abstract:
You are given a list of URLs, which may or may not include the file name- eg www.msn.com instead of www.msn.com/default.asp. You want to check them automatically. The function provided does this.
Answer:
This function will check the url with or without a file. The only precondition is that you must be online.
URLs can be given with or without the http:/ prefix - its adds the http:// prefix if absent- this is vital for the internetOpenUrl function which also supports FTP:// and gopher://
I am checking the return code for '200' or '302' - redirects but you may wish to check for other codes. Just modify the result := line to accomodate these codes.
uses wininet;
function CheckUrl(url: string): boolean;
var
hSession, hfile, hRequest: hInternet;
dwindex, dwcodelen: dword;
dwcode: array[1..20] of char;
res: pchar;
begin
if pos('http://', lowercase(url)) = 0 then
url := 'http://' + url;
Result := false;
hSession := InternetOpen('InetURL:/1.0',
INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if assigned(hsession) then
begin
hfile := InternetOpenUrl(
hsession,
pchar(url),
nil,
0,
INTERNET_FLAG_RELOAD,
0);
dwIndex := 0;
dwCodeLen := 10;
HttpQueryInfo(hfile, HTTP_QUERY_STATUS_CODE,
@dwcode, dwcodeLen, dwIndex);
res := pchar(@dwcode);
result := (res = '200') or (res = '302');
if assigned(hfile) then
InternetCloseHandle(hfile);
InternetCloseHandle(hsession);
end;
end;
2005. július 14., csütörtök
Get the Password of the screensaver
Problem/Question/Abstract:
How to get the Password of the screensaver?
Answer:
//this is a small function which give the password of the screensaver!!
function GetScrPass: string;
var
ScrnSvrPss: string;
reg: TRegistry;
buf: array[0..256] of char;
length: word;
a: byte;
asdec: byte;
password: string[128];
const // Decrypts the screen saver password from the registry
xorwert: array[1..128] of byte =
(72, 238, 118, 29, 103, 105, 161,
27, 122, 140, 71, 248, 84, 149, 151, 95, 120, 217, 218, 108, 89, 215, 107,
53, 197, 119, 133, 24, 42, 14, 82, 255, 0, 227, 27, 113, 141, 52, 99, 235,
145, 195, 36, 15, 183, 194, 248, 227, 182, 84, 76, 53, 84, 231, 201, 73, 40,
163, 133, 17, 11, 44, 104, 251, 238, 125, 246, 108, 227, 156, 45, 228, 114,
195, 187, 133, 26, 18, 60, 50, 227, 107, 79, 77, 244, 169, 36, 200, 250, 120
, 173, 35, 161, 228, 109, 154, 4, 206, 43, 197, 182, 197, 239, 147, 92, 168,
133, 43, 65, 55, 114, 250, 87, 69, 65, 161, 32, 79, 128, 179, 213, 35, 2, 100
, 63, 108, 241, 15);
begin
password := '';
reg := TRegistry.Create;
reg.RootKey := HKEY_CURRENT_USER;
Reg.OpenKey('Control PanelDesktop', FALSE);
Reg.ReadBinaryData('ScreenSave_Data', buf, sizeof(buf));
length := (Reg.GetDataSize('ScreenSave_Data') - 1) shr 1;
if Reg.ReadBool('ScreenSaveUsePassword') then
for a := 1 to length do
begin
asdec := StrToInt('$' + buf[(a shl 1) - 2] + buf[(a shl 1) - 1]);
password := concat(password, Chr(asdec xor xorwert[a]));
end
else
password := 'There was an error getting the password.';
reg.free;
ScrnSvrPss := password;
//sleep(1000);
result := ScrnSvrPss;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
edit1.text := GetScrPass;
end;
2005. július 13., szerda
Validate an object
Problem/Question/Abstract:
How do you check if an object fits your condition and that of the compiler ?
Answer:
From time to time you want to be sure, if an object fits your conditions like naming conventions, robustness or you want the compiler find no further errors. The function checkOBJ determines whether a passed object, with a boolean returned from the function, represents an error condition.
function TTrans.checkOBJ(aObject: TObject): boolean;
var
str: string;
i: integer;
begin
result := false;
if aObject = nil then
exit;
try
str := ansiUppercase(aObject.classname);
if str = '' then
exit;
for i := 1 to length(str) do
if not (str[i] in ['0'..'9', 'A'..'Z', '_']) then
exit;
aObject.classType;
if aObject.InstanceSize < 1 then
exit;
aObject.ClassnameIs('TObject');
result := aObject.ClassNameIs(aObject.Classname);
except
exit;
end;
end;
You can call it then with an assert, so you get during the development a bit of quality assurance ;).
accObj := TAccount.createAccount(FCustNo, std_account);
assert(aTrans.checkOBJ(accObj), 'bad condition with OBJ'); //trans
Use Assert as a debugging check to test that conditions assumed to be true are never violated. Assert provides an opportunity to intercept an unexpected condition and halt a program rather than allow execution to continue under unanticipated conditions.
2005. július 12., kedd
The 5 Relationships between Classes
Problem/Question/Abstract:
How do we find related classes with the right UML-Notation, means which relationship belongs to which code ?
Answer:
Despite the fact that several advanced languages have come out of the OO Revolution, such as Java, C++, OP a lot of people are still designing their code with minimal design in mind.
UML was formed in attempt to unify the best (or most popular in this case) modelling methods in Object-Oriented Analysis and Design. Let's focus on the Class Diagram and learn the 5 Relationships with OP (ObjectPascal).
So good up design will actually shorten the development cycle, give you an idea of the resources you need, and how to end the project.
The 5 Relationships are:
Inheritance
Association
Aggregation
Composition
Dependency
Realisation new UML 1.4
The Class Diagram is the static architectural representation of your software and capable with a CASE-Tool to generate Code. It allows you to see the overall object structure of the system.
Let's start with the Inheritance (Generalization). All Relations are represented by Fig.1, (download cd_busobj.tif) but also by Code:
1) Inheritance is represented by a triangle and TBusinessObj is a subclass of TDataModule1, inheriting all of the members (Attributes and Operations) of the superclass.
TBusinessObj = class(TDataModule1)
private
function calcSalary(salary: double): Double;
procedure changeGrade(amount: integer);
public
constructor Create(aOwner: TComponent); override;
destructor destroy; override;
procedure changeSalary(amount: double);
function getFullName: string;
function getOldSalary: Double;
function open_QueryAll: Boolean;
function open_QuerySalary(qryID: integer): Boolean;
end;
2) Association is represented by a line, means a relationship at runtime. In Fig.1 seen by from TDataToXML. Association is not a tight coupling between objects, you call it and free it at runtime with local instances:
procedure TForm1.btnToXMLClick(Sender: TObject);
begin
with TDataToXML.create do
begin
try
dataSetToXML(datEmployee.query1, 'salaryXport.xml');
finally
free;
end
end;
end;
3) Aggregation is a whole-part relationship. A TDataModule1 has Queries from TQuery, so the white diamond is positioned near the container to represent the Queries are the parts of the DataModule. It means also a relationship at designtime, Query1 is a steady member of TDataModule1:
TDataModule1 = class(TDataModule)
Database1: TDatabase;
DataSource1: TDataSource;
Query1: TQuery;
public
procedure loadTree(myTree: TTreeView; fromFile: string);
procedure storeTree(myTree: TTreeView; toFile: string);
end;
4) Composition is a stronger form of Aggregation. Composition is represented by a black diamond. For example in the VCL you can often find constructs like this: memo1.lines.add, so memo1 is TMemo and lines is TStrings. Means in our example if a class TForm1 has an instance and needs another instance too, there we have a composition:
procedure TForm1.fillEmployees;
begin
with datEmployee.dataSource1 do
begin
while not dataSet.EOF do
begin
cmxEmployee.items.add(intToStr(dataSet.fieldValues['EMP_NO']));
dataSet.next;
end;
end;
end;
5) Dependency is a dotted arrow and not shown in our diagram. It is used to show that one UML Element depends upon another. Dependencies can be used to describe the relationship not only between classes, also packages, or components. In a Class Diagram you find it for ex. that one class depends on a type of another class and the class is part of a Library, like the VCL. In our case TDataModule1 depends upon TTreeView (TreeView uses ComCtrls). But it's TForm1 which really depends on TTreeView, cause the instance TTreeView1 is a member of the Form:
TForm1 = class(TForm)
TreeView1: TTreeView;
procedure TDataModule1.storeTree(myTree: TTreeView; toFile: string);
begin
with TFileStream.create(toFile, fmcreate) do
begin
try
writeComponent(myTree);
finally
free;
end;
end
end;
6) Interface support is like inheritance but there is a strict interface-specification and a class which supports the interface, marked in UML like a lollipop in the diagram or a dotted arrow from implement to interface:
IIncomeInt = interface(IUnknown)
['{DBB42A04-E60F-41EC-870A-314D68B6913C}']
function GetIncome(const aNetto: Currency): Currency; stdcall;
function GetRate: Real; stdcall;
{.....}
TIncomeRealSuper = class(TInterfacedObject, IIncomeInt)
private
FRate: Real;
function Power(X: Real; Y: Integer): Real;
protected
function GetRate: Real;
public
constructor Create;
Interfaces works the same way in CLX or Kylix, as long as you don't use IDispatch from COM! So you don't need a MS-specific library to use interfaces.
Component Download: http://max.kleiner.com/download/businessobj.zip
2005. július 11., hétfő
Restrict the mouse movement to form
Problem/Question/Abstract:
Using the Windows API function ClipCursor, it is possible to restrict the movement of the mouse to a specific rectangular region on the screen.
Answer:
//restrict the mouse mouvement to form and release
//this restriction after a click on the form
procedure TForm1.FormCreate(Sender: TObject);
var
r: TRect;
begin
//it would be good idea to move the
//mouse inside the form before restriction
r := BoundsRect;
ClipCursor(@R);
end;
procedure TForm1.FormClick(Sender: TObject);
begin
//always be sure to release the cursor
ClipCursor(nil);
end;
2005. július 10., vasárnap
Use forms declared in DLL by an executable
Problem/Question/Abstract:
How to use forms declared in DLL by an executable?
Answer:
In the example that follows the exe only sees a totally "virtual abstract" interface to the object as is being exported from the dll but it still can create the object and use it. Of course the exe can not see or execute any methods declared in the exe but that is the whole purpose of implementing them in a custom dll to begin with.
// Example code:
program Dlloader;
uses
Sharemem,
Forms,
exeunit1 in 'exeunit1.pas' {Form1},
DllIntfu in 'DllIntfu.pas';
{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
//--------------------------
unit DllIntfu;
interface
type
TDllobject = class
protected
function Get_UserName: string; virtual; abstract;
procedure Set_UserName(Value: string); virtual; abstract;
public
property UserName: string read Get_UserName write Set_UserName;
end;
TDllobjectClass = class of TDllobject;
implementation
end.
//---------------------------
unit exeunit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, DllIntfu, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
type
TDllfunc = function: TDllobjectClass;
stdcall;
procedure TForm1.Button1Click(Sender: TObject);
var
i: DWORD;
fHandle: THandle;
fDllfunc: TDllfunc;
fDllobject: TDllobject;
fUserName: string;
begin
fHandle := LoadLibrary('UserName.dll');
if (fHandle <> 0) then
begin
@fDllfunc := GetProcAddress(fHandle, 'Dllfunc');
if Assigned(@fDllfunc) then
begin
i := 255;
SetLength(fUserName, i);
GetUserName(PChar(fUserName), i);
fUserName := StrPas(PChar(fUserName));
fDllobject := fDllfunc.Create;
fDllobject.UserName := fUserName;
ShowMessage(fDllobject.UserName);
fDllobject.Free;
end;
FreeLibrary(fHandle);
end;
end;
end.
//-------------------------------
library UserName;
uses
Sharemem,
Sysutils,
DllIntfu;
type
TCustomDllobject = class(TDllobject)
private
fUserName: string;
function Getfilecount: Integer;
protected
function Get_UserName: string; override;
procedure Set_UserName(Value: string); override;
end;
TCustomDllobjectclass = class of TCustomDllobject;
function TCustomDllobject.Getfilecount: Integer;
var
doserr: Integer;
fsrch: TSearchRec;
begin
Result := 0;
doserr := FindFirst('*.*', faanyfile, fsrch);
if (doserr = 0) then
begin
while (doserr = 0) do
begin
if (fsrch.attr and faDirectory) = 0 then
Inc(Result);
doserr := findnext(fsrch);
end;
FindClose(fsrch);
end;
end;
function TCustomDllobject.Get_UserName: string;
begin
Result := 'You signed on as ''' + fUserName + '''' +
' and there ' + IntToStr(Getfilecount) +
' files in this directory.';
end;
procedure TCustomDllobject.Set_UserName(Value: string);
begin
fUserName := Value;
end;
function Dllfunc: TCustomDllobjectClass; stdcall;
begin
Result := TCustomDllobject; // class type only
end;
exports
Dllfunc name 'Dllfunc';
begin
end.
2005. július 9., szombat
Print a TListView
Problem/Question/Abstract:
I have a TListView component on my form. When I call listview1.paintto(printer.handle,30,30), where listview1 is of type TListView, the content of the grid is printed like expected, but the column headings are not printed. Is this a (windows?) bug or is it just me using the PaintTo the wrong way?
Answer:
It is no bug, just a problem caused by the way PaintTo works. Basically it sends a WM_PAINT message to a control with a canvas handle in wparam. This tells the control to paint its client area to the canvas. The problem in your case is that the listview header is not part of the listviews client area, it is an embedded header control. So you need to send that one a paint message as well. Unfortunately it has no VCL wrapper control, so PaintTo cannot be used. There is a little-known Windows message that can be used as alternative to WM_PAINT. The problem is that not all controls seem to implement it. But TListview does:
procedure TForm1.Button1Click(Sender: TObject);
var
bmp: TBitmap;
begin
bmp := Tbitmap.Create;
try
bmp.width := listview1.width;
bmp.height := listview1.height;
with bmp.canvas do
begin
Lock;
try
listview1.perform(WM_PRINT, handle, PRF_CHILDREN or PRF_CLIENT or
PRF_NONCLIENT or PRF_ERASEBKGND);
finally
Unlock
end;
image1.picture.bitmap := bmp;
end;
finally
bmp.free
end;
end;
2005. július 8., péntek
Knowing Run-Time verses Design-Time mode in an Active Form
Problem/Question/Abstract:
How to find out if your ActiveX control is running in Design mode or Run mode
Answer:
Every now and then an ActiveForm developer will want to add a feature to an ActiveX control that only shows up in either Design-Time or Run-Time (splash screens and nag screens are an example). (Background: Design-Time is when a control is applied to a form in the Visual Basic environment; Run-Time is when the control is running inside of an executable). If you are using TActiveXControl as you’re base object, you get this for free. If you are using TActiveForm you have to work for the information.
The property we need is UserMode. If this property is True we are in Run-Time mode; if False we are in Design-Time mode. UserMode is one of the AmbientProperties that a control’s container is supposed to provide. Delphi even supplies an interface call IAmbientDispatch in AxCtrls for retrieving the ambient properties.
To get it: Your ActiveForm has a property called ActiveFormControl.ClientSite. This property is set sometime before your control's OnShow event (ClientSite will not be set in the constructor or the initialize procedures). Cast ClientSite to an IAmbientDispatch (found in AxCtrls), then get the UserMode property. (see code below)
procedure TDesignCtrl.HandleOnShow(Sender: TObject);
var
b: Boolean;
pIAmbient: IAmbientDispatch;
begin
pIAmbient := Self.ActiveFormControl.ClientSite as IAmbientDispatch;
b := pIAmbient.UserMode;
if b then
ShowMessage('is in runtime mode')
else
ShowMessage('is in design mode');
end;
There are other properties in the IAmbientDispatch interface that could also be useful to a control writer so feel free to experiment.
2005. július 7., csütörtök
Managing a lot of Forms
Problem/Question/Abstract:
How do you controll hundreds of forms by number in a main unit ?
Answer:
Suppose you have a call from a dear developer that had about 100 forms that he have to create and run. So you tell him, create and run it by number in, here's a solution to this problem...
The following part is to be placed on the main form's unit, maybe a frmController unit and the purpose is to have a couple of arrays that are referenced by number and then accessible to manipulate:
const
maxForms = 100;
var
frmController: TForm1
frmArray: array[1..maxForms] of TForm;
frmRefArray: array[1..maxForms] of TFormClass;
implementation
uses Unit7; // and all of the units
procedure TForm1.btnfrmController(sender: TObject);
begin
... // iterating or indexing as you like
frmArray[7] := frmRefArray[7].create(self);
frmArray[7].showModal; // whatever you need
frmArray[7].free;
end;
The next step is, each form must register itself in the array of the controller or main form unit. This can be done at load time or runtime, let's get straight to the implementation part:
unit Unit7;
implementation
uses frmControllerU;
procedure TForm7.FormCreate(sender: TObject);
frmArray[7] := self;
end;
initialization
frmRefArray[7] := TForm7 //hard coded
The last part means that you must tell the array which class has to be associated with which array element. So you get by ObjectPascal the classReference, another way that this can be done is by using an extra unit where all of the form's information is centralized and easier to maintain, but the idea remains the same.
ps: If the form is already instantiated, then you find it through the TScreen object (don't scream use TScreen ;):
for i := 0 to screen.FormCount - 1 do
if screen.forms[i].className = 'TForm7' then {... }
2005. július 6., szerda
Export functions and methods from DLL
Problem/Question/Abstract:
Some time ago I have created a program using Delphi3. Now I want to adapt it to Delphi7. The problem is that creating the program I have used component (TComponent descendent) without source files and I can not install it into Delphi7.
Actually I use only one function and one method (event) which returns the progress. I thought maybe I could add that component into DLL using D3 and use this DLL with D7. The function of the component exports successfully from DLL but how to export the Method (Event) of it?
Answer:
Well, if there is no D7 version of this component a DLL build with D3 is indeed the best solution to your problem. It would have helped if you had posted the declarations of the function you need to call and the event you want to handle, though. Without that at hand i can only give you some general guidance.
What you need to do is to build a set of exported functions for the DLL that gives you access to the components functionality. Since DLL and host EXE are build with different Delphi versions they cannot safely share a memory manager via the ShareMem unit, so you cannot pass data types like AnsiString to the DLL functions or receive such parameters in the event handler. You have to write the DLL interface like a set of Windows API methods, using only types that do not require a shared memory manager. Since the original component may not fit this requirements you need a layer of insulation between the DLLs exported functions and the component, best implemented as a class since you will need an object method to handle the components event anyway.
There is also the question of how to manage the lifetime of the DLL component.You can create it easily the first time the DLLs exported function is called to get the component do some work. But where to destroy it again? The usage pattern your post implies is not synchronous, the component seems to be doing something in a secondary thread after its mystery method has been called, delivering progress events while at work. The best option seems to be to provide another exported function the host EXE can call to get the DLL to destroy the component when it is no longer needed.
OK, let's try to code a DLL interface as a wrapper for this hypothetical component:
type
TProgressEvent = procedure(PercentDone: Integer) of object;
TMysteryComponent = class(TComponent)
{....}
public
function ProcessData(const Data: string): Boolean;
published
property OnProgress: TProgressEvent
read FProgressEvent write FProgressEvent;
end;
The import unit for the DLL used in your D7 program would then look like this:
unit MysteryComponentWrapper;
interface
type
TWrapperProgressEvent = procedure(PercentDone: Integer) of object;
function WrapperProcessData(Data: Pchar;
ProgressCallback: TWrapperProgressEvent): Boolean;
function DestroyWrapper: Boolean;
implementation
function WrapperProcessData(Data: Pchar;
ProgressCallback: TWrapperProgressEvent): Boolean;
external 'MystComp.DLL';
function DestroyWrapper: Boolean;
external 'MystComp.DLL';
end.
The DLL project file would look like this:
library MystComp;
uses
WrapperU;
exports
WrapperProcessData, DestroyWrapper;
begin
end.
The meat is in the WrapperU unit:
unit WrapperU;
interface
type
TWrapperProgressEvent = procedure(PercentDone: Integer) of object;
function WrapperProcessData(Data: Pchar;
ProgressCallback: TWrapperProgressEvent): Boolean;
function DestroyWrapper: Boolean;
implementation
uses Sysutils, MysteryComponentU;
type
TWrapper = class
private
FProgressEvent: TWrapperProgressEvent;
FMysteryComponent: TMysteryComponent;
procedure ProgressHandler(PercentDone: Integer);
public
constructor Create;
destructor Destroy; override;
function ProcessData(Data: Pchar;
ProgressCallback: TWrapperProgressEvent): Boolean;
end;
var
Wrapper: TWrapper; //starts out as Nil
function WrapperProcessData(Data: Pchar;
ProgressCallback: TWrapperProgressEvent): Boolean;
begin
try
if not Assigned(Wrapper) then
Wrapper := TWrapper.Create;
Result := Wrapper.ProcessData(Data, ProgressCallback);
except
Result := false;
end;
end;
function DestroyWrapper: Boolean;
begin
Wrapper.Free;
Wrapper := nil;
end;
procedure TWrapper.ProgressHandler(PercentDone: Integer);
begin
if Assigned(FProgressEvent) then
FProgressEvent(PercentDone);
end.
constructor TWrapper.Create;
begin
inherited;
FMysteryComponent := TMysteryComponent.Create(nil);
FMysteryComponent.OnProgress := ProgressHandler;
end;
destructor TWrapper.Destroy;
begin
FMysteryComponent.Free;
inherited;
end;
function TWrapper.ProcessData(Data: Pchar;
ProgressCallback: TWrapperProgressEvent): Boolean;
begin
FProgressEvent := ProgressCallback;
Result := FMysteryComponent.ProcessData(Data);
end;
end.
2005. július 5., kedd
Inheritance - Creating Sub/Super Classes - A Guideline...
Problem/Question/Abstract:
When can we create sub/super classes in an Object Oriented Design?
Answer:
As every Object Pascal developer knows, inheritance is one of the fundamental concepts in Object Oriented Design. I’m not going to give you any explanation on what Inheritance is since everybody knows the definition already. Instead, I'm going to give you some of the tips in designing classes in the early stages of Object Oriented Design.
In any project development, the analysis and design phases will be given importance in the initial stage. In Object Oriented Design/Visual Modeling, once the team starts collecting information regarding the project, the team will identify the objects involved in each of the activities.
At one stage, the team will have some sample classes for those objects identified. As the design stage matures, there would be more and more classes coming. Sometimes, you may need to inherit a new class from an existing one or you may need to group two classes into one. At this time, you may use the following concepts/techniques to create a sub/super class from existing classes:
What is a Sub Class and Super Class
It's a class inherited/derived from another class. The new class(sub-class) will have all the properties/methods and events of the parent class(from which it inherited) and can have additional properties specific to this sub-class. The parent class is called Super Class.
Let me explain this concept with an example.
Let us suppose we have a class called TCitizen.
The structure of TCitizen is something like this:
TCitizen = class
SocialSecurityNo: string;
Name: string;
Age: integer;
Street: string;
City: string;
State: string;
Zip: integer;
{..................
..................etc., }
end;
The above attributes are some of the common attributes you can have for a Citizen. This citizen could be anybody from a small child to an old man in a country.
Let us suppose that we have, in our analysis, some Veterans information also. Veterans are people who were being in Army and/or some distinguished government services and retired now. Those Veterans are also part of normal citizens but they would have some special privileges. In this case, we can use the existing TCitizen class by adding the special privileges attributes for a Veteran but that would not be a better design. In this case, we can call this Veteran as a SPECIALIZED Citizen. So we can create a new SUB CLASS derived from the TCitizen, called TVeteranCitizen.
The TVeteranCitizen class may look like something like this:
TVeteranCitizen = class(TCitizen)
NoOfYearsOfService: integer;
Rewards: string;
Ranks: string;
DateRetired: TDateTime;
{.........................................
......................................etc., }
end;
GENERALIZATION: Creating Super Classes
Let me explain this also thru some sample classes.
Let us suppose we are designing a library system and we identified two classes TStudent, containing student information, and TProfessor, containing professor information, among other classes. We take these two classes for our discussion.
The structure of those two classes would be as follows:
TStudent = class
StudentID: string;
Name: string;
Age: string;
Grade: string;
{........................
.....................etc., }
end;
TProfessor = class
ProfID: string;
Name: string;
Age: string;
{....................
................etc., }
end;
The system will allow both the students and professors to login using their student and professor ids and do the library related activities. The system will verify the student and professor ids at the time of login.
Here we can GENERALIZE an information pertaining to both the classes as long as they both agree in their structure and type. I’m talking about the two attributes StudentID in TStudent and ProfID in TProfessor. In this case, they both are of same type: String. The second thing is that they should be both of the same structure/size string. If StudentID is of seven charactors length and ProfID is of four charactors length, then we cannot generalize this info.
As long as they both agree on those two things, we can generalize.
They both serve as a way to login to the system after verification. So we can GENERALIZE this information and create a SUPER CLASS with a name TUser and inherit both TStudent and TProfessor from TUser.
Although this seems to be a simple issue, I just would like to share this with our Delphi Community.
2005. július 4., hétfő
Reducing Source Code Complexity in your application
Problem/Question/Abstract:
Have you ever written an application where things have to know when things happen, such as when an object gets freed then you need to update some UI screen or remove some depency. Or in the case of a paint program where when a mode change requires a cursor change, buttons to enable or disable or push down... if something gets deleted then you have to do this and that etc... I have a solution that will keep your code clean of linking code.
Answer:
There are times when you write an application that turns into a linking nightmare when your system needs to react to certain conditions. Examples are Mode changing in a paint program requires cursor changes, an object being updated needs to update some UI element or disable and enable controls, when an object gets freed you need to remove dependencies. In other words there are side effects that you need to happen as a result of something changing in your application. Coding these side effects can produce some nasty code that is like a big spider web.
The solution to the problem is to use a "Message Center". I have created a easy to use MessageCenter class that uses the built in messaging capablity already built into TObject. Source code is at the end of this artical.
1. Concept of the message center
The concept is simple, you have a central "hub" that receives maybe all actions that happen in your program. Certain parts of your program need to change when these events happen. Instead of hard coding these "reactions" into your code, you send the message of the event to the message center in a record structure. Anything that needs to react or change based on the event is registered with and notified by the MessageCenter.
2. Example Implementation
This app is an image editor where you can have multiple images opened at once.
Each Image is opened in a Form class of TForm_ImageEdit.
A graphical list of buttons are listed at the top of the main form, there is one button per opened image and a picture of the image is drawn on the surface of the button. Users can click the button and active the form for that image.
The rule of the system is
A button should be added when a new form is added.
The button should remove when the form is removed.
The button should push down when the editor form becomes active.
First define the MessageID and the record for the message.
const
MID_ImageEdit = 14936;
type
TMID_ImageEdit = packed record
MessageID: Cardinal; // This is required field for Dispatching
Action: (aDestroyed, aActivated);
ImageEdit: TForm_ImageEdit;
end;
Then within the TForm_ImageEdit Broadcast the messages...
procedure TForm_ImageEdit.FormDestroy(Sender: TObject);
var
M: TMID_ImageEdit;
begin
with M do
begin
M.MessageID := MID_ImageEdit;
M.Action := aClosed;
M.ImageEdit := Self;
end;
GetMessageCenter.BroadcastMessage(Self, M);
end;
procedure TForm_ImageEdit.FormActivate(Sender: TObject);
var
M: TMID_ImageEdit;
begin
with M do
begin
M.MessageID := MID_ImageEdit;
M.Action := aActivated;
M.ImageEdit := Self;
end;
GetMessageCenter.BroadcastMessage(Self, M);
end;
Now to edit the main form
At some point in your main form when you create the Image Editor, add this code after creation:
F := TForm_ImageEdit.Create(Self);
// Listen to messages
GetMessageCenter.AttachListner(Self, F);
// Next few lines will add the button for the new form at the top of the main window.
{.
.
. }
This way the Main form will receive messages from the ImageEditor window.
So now Add this MessageHandler to your main form:
Create this method to receive messages of type MID_IMageEdit:
procedure ImageEditorWindowChanged(var Msg: TMID_ImageEdit); message MID_ImageEdit;
And implement it in this way
procedure TForm_NMLDA.ImageEditorWindowChanged(var Msg: TMID_ImageEdit);
begin
case Msg.Action of
aDestroyed:
begin
ImageEditorClosed(Msg.ImageEdit);
GetMessageCenter.DetachListner(Self, Msg.ImageEdit);
end;
aActivated: EditorFocused(Msg.ImageEdit);
end;
end;
ImageEditorClosed method will remove the button from the main form EditorFocused will push down the button associated with the ImageEditor.
Thats all, you have low coupling and you may attach as many listners as you like.
This concept has a lot of potential and it will make your complex apps very simple and maintainable.
Here is the code:
unit MessageCenter;
{
William Egge public@eggcentric.com
Created Feb - 28, 2002
You can modify this code however you wish and use it in commercial apps. But
it would be cool if you told me if you decided to use this code in an app.
The goal is to provide an easy way to handle notifications between objects
in your system without messy coding. The goal was to keep coding to a minimum
to accomplish this. That is why I chose to use Delphi's built in
Message dispatching.
This unit/class is intended to be a central spot for messages to get dispatched,
every object in the system can use the global GetMessageCenter function.
You may also create your own isolated MessageCenter by creating your own
instance of TMessageCenter.. for example if you had a large subsystem and
you feel it would be more effecient to have its own message center.
The goal is to capture messages from certain "Source" objects.
Doc:
procedure BroadcastMessage(MessageSource: TObject; var Message);
The message "Message" will be sent to all objects who called AttachListner
for the MessageSource.
If no objects have ever called AttachListner then nothing will happen and
the code will not blow up :-). Notice that there is no registration for
a MessageSource, this is because the MessageSource registration happens
automatically when a listner registers itself for a sender.
(keeping external code simpler)
procedure AttachListner(Listner, MessageSource: TObject);
This simply tells the MessageCenter that you want to receive messages from
MessageSource.
procedure DetachListner(Listner, MessageSource: TObject);
This removes the Listner so it does not receive messages from MessageSource.
Technique for usage with interfaces:
If your program is interface based then its not possible to pass a
MessageSource but it IS possible to pass an object listner if it is being
done from within the object wanting to "listen" (using "self").
To solve the problem of not being able to pass a MessageSource, you can
add 2 methods to your Sender interface definition,
AttachListner(Listner: TObject) and DetachListner(Listner: TObject).
Internally within those methods your interfaced object can call the
MessageCenter and pass its object pointer "Self".
Info:
Performance and speed were #1 so...
MessageSources are sorted and are searched using a binary search so that
a higher number of MessageSources should not really effect runtime performance.
The only performance penalty for this is on adding a new MessageSource because
it has to do an insert rather than an add, this causes all memory to be shifted
to make room for the new element. The benifit is fast message dispatching.
There is no check for duplicate MesssageListners per Sender, this would have
slowed things down and this coding is usefull only when you have bugs. And
hoping you prevent bugs, you do not have to pay for this penalty when your
code has no bugs.
}
interface
uses
Classes, SysUtils;
type
TMessageCenter = class
private
FSenders: TList;
FBroadcastBuffers: TList;
function FindSenderList(Sender: TObject; var Index: Integer): TList;
public
constructor Create;
destructor Destroy; override;
procedure BroadcastMessage(MessageSource: TObject; var Message);
procedure AttachListner(Listner, MessageSource: TObject);
procedure DetachListner(Listner, MessageSource: TObject);
end;
// Shared for the entire application
function GetMessageCenter: TMessageCenter;
implementation
var
GMessageCenter: TMessageCenter;
ShuttingDown: Boolean = False;
function GetMessageCenter: TMessageCenter;
begin
if GMessageCenter = nil then
begin
if ShuttingDown then
raise
Exception.Create('Shutting down, do not call GetMessageCenter during shutdown.');
GMessageCenter := TMessageCenter.Create;
end;
Result := GMessageCenter;
end;
{ TMessageCenter }
procedure TMessageCenter.AttachListner(Listner, MessageSource: TObject);
var
L: TList;
Index: Integer;
begin
L := FindSenderList(MessageSource, Index);
if L = nil then
begin
L := TList.Create;
L.Add(MessageSource);
L.Add(Listner);
FSenders.Insert(Index, L);
end
else
L.Add(Listner);
end;
procedure TMessageCenter.BroadcastMessage(MessageSource: TObject; var Message);
var
L, Buffer: TList;
I: Integer;
Index: Integer;
Obj: TObject;
begin
L := FindSenderList(MessageSource, Index);
if L <> nil then
begin
// Use a buffer because objects may detach or add during the broadcast
// Broadcast can be recursive. Only broadcast to objects that existed
// before the broadcast and not new added ones. But do not broadcast to
// objects that are deleted during a broadcast.
Buffer := TList.Create;
try
FBroadcastBuffers.Add(Buffer);
try
for I := 0 to L.Count - 1 do
Buffer.Add(L[I]);
// skip 1st element because it is the MessageSender
for I := 1 to Buffer.Count - 1 do
begin
Obj := Buffer[I];
// Check for nil because items in the buffer are set to nil when they are removed
if Obj <> nil then
Obj.Dispatch(Message);
end;
finally
FBroadcastBuffers.Delete(FBroadcastBuffers.Count - 1);
end;
finally
Buffer.Free;
end;
end;
end;
constructor TMessageCenter.Create;
begin
inherited;
FSenders := TList.Create;
FBroadcastBuffers := TList.Create;
end;
destructor TMessageCenter.Destroy;
var
I: Integer;
begin
for I := 0 to FSenders.Count - 1 do
TList(FSenders[I]).Free;
FSenders.Free;
FBroadcastBuffers.Free;
inherited;
end;
procedure TMessageCenter.DetachListner(Listner, MessageSource: TObject);
var
L: TList;
I, J: Integer;
Index: Integer;
begin
L := FindSenderList(MessageSource, Index);
if L <> nil then
begin
for I := L.Count - 1 downto 1 do
if L[I] = Listner then
L.Delete(I);
if L.Count = 1 then
begin
FSenders.Remove(L);
L.Free;
end;
// Remove from Broadcast buffers
for I := 0 to FBroadcastBuffers.Count - 1 do
begin
L := FBroadcastBuffers[I];
if L[0] = MessageSource then
for J := 1 to L.Count - 1 do
if L[J] = Listner then
L[J] := nil;
end;
end;
end;
function TMessageCenter.FindSenderList(Sender: TObject;
var Index: Integer): TList;
function ComparePointers(P1, P2: Pointer): Integer;
begin
if LongWord(P1) < LongWord(P2) then
Result := -1
else if LongWord(P1) > LongWord(P2) then
Result := 1
else
Result := 0;
end;
var
L, H, I, C: Integer;
begin
Result := nil;
L := 0;
H := FSenders.Count - 1;
while L <= H do
begin
I := (L + H) shr 1;
C := ComparePointers(TList(FSenders[I])[0], Sender);
if C < 0 then
L := I + 1
else
begin
H := I - 1;
if C = 0 then
begin
Result := FSenders[I];
L := I;
end;
end;
end;
Index := L;
end;
initialization
finalization
ShuttingDown := True;
FreeAndNil(GMessageCenter);
end.
Component Download: http://www.eggcentric.com/download/MCDemo.zip
2005. július 3., vasárnap
Delphi 6 - Imported Automation Events Bug
Problem/Question/Abstract:
Why don't any of the events in my imported ActiveX control work?
Answer:
Sad but true. Delphi 6's type library importer is badly broken with regards to the event sinks.
What's happening is that in InvokeEvent mehod that determines where to send the events by DispID each event handler called has its parameters reversed.
So for example in Delphi 5 where it's correct the imported event looks like this;
if Assigned(FOnRecognition) then
FOnRecognition(Self, Params[0] {Integer}, Params[1] {OleVariant}, Params[2]
{SpeechRecognitionType}, Params[3] {const ISpeechRecoResult});
while in Delphi 6 it looks like this;
if Assigned(FOnRecognition) then
FOnRecognition(Self, Params[3] {const ISpeechRecoResult}, Params[2]
{SpeechRecognitionType}, Params[1] {OleVariant}, Params[0] {Integer});
which just will not do.
The solution is to either
fix them all by hand
use an import created by Delphi 5
use official Borland pach for Delphi 6
2005. július 2., szombat
Making adjustments to Delphi Colors
Problem/Question/Abstract:
How to make Delphi standard colors lighter or darker
Answer:
Here are some functions I use to make adjustments to the standard colors in Delphi.
The functions Darker and Lighter require 2 parameters and are used like this:
Panel1.Color := Darker(clBlue, 20);
This produces a panel color that is 20% darker than blue.
How it works:
Each of the three primary colors (Red,Green,Blue) can have values from 0 to 255 and can combine to form 16,777,216 possible colors. You can visualize the three primaries as the three axis' of a cube where the directions x, y and z correspond to the colors red, green and blue. Then each 3 dimensional point in the cube would represent one of the 16M colors. At the point in the cube where all the values are 0 (0,0,0) the color is black, and at (255,255,255) the color is white, (255,0,0) is pure red, etc.
If you visualize a line drawn between any color (r,g,b) and white (255,255,255) then all the points that make up that line corespond to all valures of the color (r,g,b) as it becomes lighter and lighter until it reaches pure white.
That same for a line line drawn between any color (r,g,b) and black (0,0,0). The line represents all shades of that color as it darkens to pure black.
The function "Darker" returns a new color value that is the specified percentage closer to black. 100% is pure black.
The function "Lighter" returns a new color value that is the specified percentage closer to white. 100% is pure white.
function Darker(Color: TColor; Percent: Byte): TColor;
var
r, g, b: Byte;
begin
Color := ColorToRGB(Color);
r := GetRValue(Color);
g := GetGValue(Color);
b := GetBValue(Color);
r := r - muldiv(r, Percent, 100); //Percent% closer to black
g := g - muldiv(g, Percent, 100);
b := b - muldiv(b, Percent, 100);
result := RGB(r, g, b);
end;
function Lighter(Color: TColor; Percent: Byte): TColor;
var
r, g, b: Byte;
begin
Color := ColorToRGB(Color);
r := GetRValue(Color);
g := GetGValue(Color);
b := GetBValue(Color);
r := r + muldiv(255 - r, Percent, 100); //Percent% closer to white
g := g + muldiv(255 - g, Percent, 100);
b := b + muldiv(255 - b, Percent, 100);
result := RGB(r, g, b);
end;
I have also added these convenience functions that can be used like this:
Panel1.Color := Light(clBlue);
Panel1.Color := SlightlyDark(clRed);
Panel1.Color := VeryLight(clMagenta);
{etc. }
function SlightlyDark(Color: TColor): TColor;
begin
Result := Darker(Color, 25);
end;
function Dark(Color: TColor): TColor;
begin
Result := Darker(Color, 50);
end;
function VeryDark(Color: TColor): TColor;
begin
Result := Darker(Color, 75);
end;
function SlightlyLight(Color: TColor): TColor;
begin
Result := Lighter(Color, 25);
end;
function Light(Color: TColor): TColor;
begin
Result := Lighter(Color, 50);
end;
function VeryLight(Color: TColor): TColor;
begin
Result := Lighter(Color, 75);
end;
Feliratkozás:
Bejegyzések (Atom)