2008. június 30., hétfő

How to loop through the keys in the registry


Problem/Question/Abstract:

I am making a program for editing the registry. It will basically be the same as regedit, but with some added features. What I want to know is how should I go about reading the registry into my TreeView and ListView components and how do I specify which icons to use in the tree view? I know you can load files into a list view using the FindFirst, FindNext and FindClose, but how do you loop through the keys in the registry?

Answer:

Enumerating registry keys:


procedure TForm1.Button1Click(Sender: TObject);
var
  indent: Integer;

  procedure EnumAllKeys(hkey: THandle);
  var
    l: TStringList;
    n: Integer;
  begin
    Inc(indent, 2);
    with TRegistry.Create do
    try
      RootKey := hkey;
      OpenKey(EmptyStr, false);
      l := TStringLIst.Create;
      try
        GetKeynames(l);
        CloseKey;
        for n := 0 to l.Count - 1 do
        begin
          memo1.lines.add(StringOfChar(' ', indent) + l[n]);
          if OpenKey(l[n], false) then
          begin
            EnumAllKeys(CurrentKey);
            CloseKey;
          end;
        end;
      finally
        l.Free
      end;
    finally
      Free;
    end;
    Dec(indent, 2);
  end;
begin
  memo1.Clear;
  memo1.lines.add('Keys under HKEY_CURRENT_USER');
  indent := 0;
  EnumAllKEys(HKEY_CURRENT_USER);
end;

2008. június 29., vasárnap

Application help file set properly


Problem/Question/Abstract:

Delphi provides a dialog box to set the application help file. Click Project -> Options and select the Application tab. If you click [Browse] to locate the help file of your application, Delphi sets the help file name including the full path. When you deploy the application, the help file is probably not found because the installation path on your user's computer is not the same.

Answer:

You must specify the help file name without a path or with a relative path. If the help is located in the same directory as the application exe, simply omit the path. If it is located in a sub-directory, specify a relative path. For instance, enter

./help/myhelpfile.hlp

in the dialog box, if there is a sub-directory called "help" below your application's installation folder.

Problem solved? Not completely. This solution works as long as the current directory of your application is the default directory of your application (one where the exe file is installed). This is typically the case, but the current directory may change while your application is running. A call to SetCurrentDir(newdirname) for instance, changes that.

But Windows provides a solution for this. You can enter the path to your help file in the Windows registry. If you do, specify only the help file name in Delphi, no path at all. Then modify the registry key

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\Help

and enter a new key for your help file which specifies the path to it.

A third way (and my preferred one) to specify the application help file is to code it manually. It's a single code line you add to the Create event of your main form and you don't have to worry about current directories or registry entries:

procedure TMainForm.FormCreate(Sender: TObject);
begin
  Application.HelpFile :=
    ExtractFileDir(Application.Exename) +
    '\help\HilfeDatei.hlp';
end;

2008. június 28., szombat

How to display an HTML document


Problem/Question/Abstract:

I need to display the contents of a HTML document from a Delphi4 program. Can I somehow execute a 'doubleclick' of the HTML file (automatically activating the default browser)?

Answer:

Yes, that's what ShellExecute or ShellExecuteEx allow you to do:

function showHtml(sDocName: string): boolean;
begin
  result := (32 < shellExecute(0, 'open', pChar(sDocName), nil, nil, sw_minimize));
end;

2008. június 27., péntek

Kernel32.dll Interactive Applications


Problem/Question/Abstract:

This article will teach you how to be interactive with Windows&reg; operating system DLL's, this also helps you building smaller windows applications .

Answer:

Well, here are the commands !!
to try any of them just run it from windows, and in Delphi as you know just
winexec(Pchar('ABCD'),sw_Show);
where 'ABCD' is one of the following lines of commands .
OR
example:
shellExecute( handle, 'open', 'rundll32', 'shell32.dll,Control_RunDLL', '', SW_SHOWNORMAL );
to open control pane, for example on windows_NT !!


"rundll32 shell32,Control_RunDLL" - Run The Control Panel

"rundll32 shell32,OpenAs_RunDLL" - Open The 'Open With...' Window

"rundll32 shell32,ShellAboutA Info-Box" - Open 'About Window Window'

"rundll32 shell32,Control_RunDLL desk.cpl" - Open Display Properties

"rundll32 user,cascadechildwindows" - Cascade All Windows

"rundll32 user,tilechildwindows" - Minimize All Child-Windows

"rundll32 user,repaintscreen" - Refresh Desktop

"rundll32 shell,shellexecute Explorer" - Re-Start Windows Explorer

"rundll32 keyboard,disable" - Lock The Keyboard

"rundll32 mouse,disable" - Disable Mouse

"rundll32 user,swapmousebutton" - Swap Mouse Buttons

"rundll32 user,setcursorpos" - Set Cursor Position To (0,0)

"rundll32 user,wnetconnectdialog" - Show 'Map Network Drive' Window

"rundll32 user,wnetdisconnectdialog" - Show 'Disconnect Network Disk' Window

"rundll32 user,disableoemlayer" - Display The BSOD Window note '''(BSOD) = Blue Screen Of Death '''

"rundll32 diskcopy,DiskCopyRunDll" - Show Copy Disk Window

"rundll32 rnaui.dll,RnaWizard" - Run 'Internet Connection Wizard', If run with "/1" - silent mode

"rundll32 shell32,SHFormatDrive" - Run 'Format Disk (A)' Window

"rundll32 shell32,SHExitWindowsEx -1" - Cold Restart Of Windows Explorer

"rundll32 shell32,SHExitWindowsEx 1" - Shut Down Computer

"rundll32 shell32,SHExitWindowsEx 0" - Logoff Current User

"rundll32 shell32,SHExitWindowsEx 2" Windows9x Quick Reboot

"rundll32 krnl386.exe,exitkernel" - Force Windows 9x To Exit (no confirmation)

"rundll rnaui.dll,RnaDial "MyConnect" - Run 'Net Connection' Dialog

"rundll32 msprint2.dll,RUNDLL_PrintTestPage" - Choose & Print Test Page Of Current Printer

"rundll32 user,setcaretblinktime" - Set New Cursor Rate Speed

"rundll32 user, setdoubleclicktime" - Set New DblClick Speed (Rate)

"rundll32 sysdm.cpl,InstallDevice_Rundll" - Search For non PnP Devices .


hope you enjoy it !!
Ruslan.
====================

2008. június 26., csütörtök

Check for an existing worksheet in Excel


Problem/Question/Abstract:

How can I check if a work sheet (e.g. 'first') is existing in an Excel file?

Answer:

In late binding:

{ ... }
WB := Excel.Workbooks[1];
for Idx := 1 to WB.Worksheets.Count do
  if WB.Worksheets[Idx].Name = 'first' then
    Showmessage('Found the worksheet');
{ ... }

2008. június 25., szerda

Write a stack class for Interfaces


Problem/Question/Abstract:

How to work with a stack of interfaces? Should I use TObjectStack or simply TStack?

Answer:

Using a storage class that uses elements typed as pointer or TObject is very risky with interfaces, since hard typecasts are needed, and they mess up the reference counting. So it is better to write your own stack class for interfaces, perhaps based on an internal TInterfaceList as storage mechanism. Something like this (untested!):

{ ... }
type
  TInterfaceStack = class
  private
    FList: TInterfacelist;
    FCurrent: IInterface;
    function GetTop: IInterface;
  public
    constructor Create; virtual;
    destructor Destroy; override;
    procedure Push(aIntf: IInterface);
    procedure Pop;
    function IsEmpty: boolean;
    property Top: IInterface read GetTop;
  end;

  { TInterfaceStack }

constructor TInterfaceStack.Create;
begin
  inherited;
  FList := TInterfacelist.Create;
  FList.Capacity := 32;
end;

destructor TInterfaceStack.Destroy;
begin
  FList.Free;
  inherited;
end;

function TInterfaceStack.GetTop: IInterface;
begin
  Result := FCurrent;
end;

function TInterfaceStack.IsEmpty: boolean;
begin
  Result := not Assigned(FCurrent);
end;

procedure TInterfaceStack.Pop;
begin
  if Flist.Count > 0 then
  begin
    FCurrent := FList[FList.count - 1];
    FList.Delete(Flist.Count - 1);
  end
  else
    FCurrent := nil;
end;

procedure TInterfaceStack.Push(aIntf: IInterface);
begin
  if not IsEmpty then
    FList.Add(FCurrent);
  FCurrent := aIntf;
end;

2008. június 24., kedd

Determine your local IP using Winsock


Problem/Question/Abstract:

Determine your local IP using Winsock

Answer:

The code below uses the WinSock unit to lookup your local IP number. Simply call the function LocalIP - it will return your IP as a string.

In a LAN, it will return your local IP number, e.g. 192.168.100.25, not your external IP number.


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, WinSock;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Button1: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

function LocalIP: string;
type
  TArrayPInAddr = array[0..10] of PInAddr;
  PArrayPInAddr = ^TArrayPInAddr;
var
  phe: PHostEnt;
  pptr: PArrayPInAddr;
  Buffer: array[0..63] of char;
  i: integer;
  GInitData: TWSADATA;
begin
  WSAStartup($101, GInitData);
  result := '';
  GetHostName(Buffer, sizeof(Buffer));
  phe := GetHostByName(Buffer);
  if phe = nil then
  begin
    exit
  end;
  pptr := PArrayPInAddr(phe^.h_addr_list);
  i := 0;
  while pptr^[i] <> nil do
  begin
    result := StrPas(inet_ntoa(pptr^[i]^));
    Inc(i);
  end;
  WSACleanup;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ShowMessage('Your computer''s IP address is: ' + LocalIP);
end;

end.

2008. június 23., hétfő

How to save and restore the position of TCoolBar bands


Problem/Question/Abstract:

I can save the TToolbar positions but I never succeed in restoring their positions. I put them in either in TControlBar or TCoolBar. How do I position them correctly?

Answer:

Saving:

{Coolbar}
for i := 0 to Coolbar.Bands.Count - 1 do
  with Coolbar.Bands[i] do
    seOptions['Band' + IntToStr(ID)] := Format('%d,%d,%d,%d', [Integer(Break),
      Width, Index, Integer(Control.Visible)]);


Loading:

{Coolbar}
for i := 0 to Coolbar.Bands.Count - 1 do
  with Coolbar.Bands[i] do
  begin
    BandInfo := seOptions['Band' + IntToStr(ID)];
    if BandInfo <> '' then
    try
      Break := Boolean(StrToInt(CutFirst(BandInfo)));
      Width := StrToInt(CutFirst(BandInfo));
      Index := StrToInt(CutFirst(BandInfo));
      Control.Visible := Boolean(StrToInt(CutFirst(BandInfo))); {this line untested}
    except;
    end;
  end;

seOptions is a settings object and can store values 'by name'. BandInfo is a string. CutFirst returns the first value from a comma separated list string and removes it from the string.

2008. június 22., vasárnap

Get and Set volume (soundcard)


Problem/Question/Abstract:

How do I get the soundcard's volume? How to set it ?

Answer:

procedure GetVolume(var volL, volR: Word);
var
  hWO: HWAVEOUT;
  waveF: TWAVEFORMATEX;
  vol: DWORD;
begin
  volL := 0;
  volR := 0;
  // init TWAVEFORMATEX
  FillChar(waveF, SizeOf(waveF), 0);
  // open WaveMapper = std output of playsound
  waveOutOpen(@hWO, WAVE_MAPPER, @waveF, 0, 0, 0);
  // get volume
  waveOutGetVolume(hWO, @vol);
  volL := vol and $FFFF;
  volR := vol shr 16;
  waveOutClose(hWO);
end;

procedure SetVolume(const volL, volR: Word);
var
  hWO: HWAVEOUT;
  waveF: TWAVEFORMATEX;
  vol: DWORD;
begin
  // init TWAVEFORMATEX
  FillChar(waveF, SizeOf(waveF), 0);
  // open WaveMapper = std output of playsound
  waveOutOpen(@hWO, WAVE_MAPPER, @waveF, 0, 0, 0);
  vol := volL + volR shl 16;
  // set volume
  waveOutSetVolume(hWO, vol);
  waveOutClose(hWO);
end;

2008. június 21., szombat

How to store a procedure or function in a variable


Problem/Question/Abstract:

Is there some way I can store a procedure or function in a variable so I can call the procedure with the variable? I'm thinking of something similar to where you can declare a variable of a certain object type, then assign different objects of that type to the variable. If it can be done with procedures, how would it be assigned and what would the syntax be to call the procedure?

Answer:

Yes, you can declare a procedural type for functions with the same parameter list and function type. Briefly it looks something like this:


{ ... }
type
  TMathFunc = function(A, B: double): double; {defines signature of function}
  { ... }
var
  mathfunc: TMathFunc;
  answer: double;
  { ... }

{Now if you define two functions}

function Adder(A, B: double): double;
begin
  result := A + B;
end;

function Multiplier(A, B: double): double;
begin
  result := A * B;
end;

begin
  {You can do this}
  mathfunc := Adder;
  answer := mathfunc(5, 9);
  mathfunc := Multiplier;
  answer := mathfunc(5, 9);
end;

2008. június 20., péntek

How to validate 24 hour time using a DBEdit field


Problem/Question/Abstract:

I want to have the user to enter a valid 24 hour time into a string field using hours and minutes only. How do I set up a validation to make sure the user does not enter something like 25:00 or 23:60 ?

Answer:

In order to prevent invalid entry character by character, you can use an OnKeyPress event handler and the following as an example:

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if not ValidEditTime(Edit1, Key) then
    Key := #0;
end;

function ValidEditTime(ed: TCustomEdit; sfx: char): boolean;
var
  pfx: string;

  function CheckVal(const s: string; lim1, lim2: integer): boolean;
  var
    v: integer;
  begin
    v := StrToIntDef(s + sfx, lim2);
    if Length(s) = 0 then
      Result := (v < lim1)
    else
      Result := (v < lim2);
  end;

var
  p: integer;
begin
  Result := not (sfx in ['0'..'9', ':']);
  if (not Result) or (sfx <> #8) then
  begin
    pfx := ed.Text;
    if ed.SelLength > 0 then
      Delete(pfx, ed.SelStart + 1, ed.SelLength);
    p := Pos(':', pfx + sfx);
    if p = 0 then
      Result := CheckVal(pfx, 3, 24)
    else
    begin
      Result := (p = 3);
      if Result then
      begin
        Result := (p > Length(pfx));
        if not Result then
          Result := CheckVal(Copy(pfx, p + 1, Length(pfx) - p), 6, 60)
      end;
    end;
  end;
end;

Although the above is quite sophisticated, you will probably need an OnValidate routine as well in order to handle pasting into the control.

2008. június 19., csütörtök

Determine the maximum free disk space


Problem/Question/Abstract:

I need a function to get the local disk with the maximum free space.

Answer:

uses
  Windows;

{ ... }
var
  lpFreeBytesAvailableToCaller, lpTotalNumberOfBytes, lpTotalNumberOfFreeBytes:
    PLargeInteger;
begin
  New(lpFreeBytesAvailableToCaller);
  New(lpTotalNumberOfBytes);
  New(lpTotalNumberOfFreeBytes);
  try
    GetDiskFreeSpaceEx('C:', lpFreeBytesAvailableToCaller, lpTotalNumberOfBytes,
      lpTotalNumberOfFreeBytes);
  finally
    Dispose(lpFreeBytesAvailableToCaller);
    Dispose(lpTotalNumberOfBytes);
    Dispose(lpTotalNumberOfFreeBytes);
  end;
end;

2008. június 18., szerda

Place a business logic in server side


Problem/Question/Abstract:

How to store a business objects in database tables?

Answer:

The devlopment of large projects is hard work. Especially when our clients are remote/mobil users.Also in our life exists the task which are not stay on one place and which algorithm changes in time. In these situations we must to develop the multi-tier application and store the business logic in server-app level. But for some tasks this way is very expensive. So I prefers the some "2 1/2"-tier level when part of business logic we can store in database. I'll try to describe this my opinion.

On server database I have a few own "system" tables with descriptions of project:

CLASS table is a list of all available user queries with short description, select/update/insert/delete statements
DESCRIPTION table stores the descriptions of fields for each record in CLASS
A few additional tables with security (user, groups, privileges, access modes etc) PS: I do not describe them, as for the basic description they are not so important

In application I have a main datamodule, which contains the one TDatabase component (linked to my BDE alias or ADO connection). Also I have a some TDataModule component which are the parent for each other. On this component I dropped the TQuery (linked to main TDatabase), and TUpdateSQL and TDataSource, which are linked to TQuery.

Also in this parent datamodule I included the some additional properties and procedures/functions:

to get a descriptions from server by class name
to fill a update/delete/insert properties of TUpdateSQL
to change a filter (via where clause), a data order (via order by) etc (any clause in sql - I use a some macros in own sqls)
post/cancel changes in server database, refresh data
to get the new ID (for autoincremental fields)
to read a user privileges
to open class
to get a "lookup" class. I not use a delphi lookup fields. Instead it I store in class descriptions for some fields the additional info (which class I must open for edit/view of data, which fields are linked in "lookup" class etc and more)
and more additional features

Each datamodule are successor of this parent DM with "personal" extension in business logic. For example, in the class for details of orders I defined the specific calculations (some subtotals and sums) or procedures (check of outputing qnt). The full logic I included in these DMs only (in visual forms
I use a calls only!).

Also I have a basic visual TForm with grid, navigator, filter panel etc.
This form have a "linked" DataModule. Also this form known how:

to fill the info from DM (a columns list for dbgrid (from description in DM), form caption and more)
to call a form for edit/append of record (those form not uses the any DB-components!!)
to generate report (I use a SMReport)
to export data (I use a SMExport)
to find a record by some criteria
to select a records (the some forms I use for selecting of some recors) PS: for example, in orders mode my users can select a records from product class and append the wished
to open a "lookup" class. PS: for example, in grid with customer list my users can open a class of customer types (instead standard delphi lookup field) or "drill" in orders list for this customer
to setup the visualization of data (fonts, colors, order etc, filter, sort and grouping)
and more additional features

All the rest forms inherits from this parent and are different one from other in needed possibilities only. For example, if my users want to calculate a sum of some order for specific product type only, of course, I add a calc function in linked DM and drop a button on form for calling of this function in DM

PS: in visual forms (when I need to calc or to do a something) I call the "assigned" method in "assigned" DM.

Also I have a two list - opened DMs and created forms. So if I need open a "lookup" class, I search it in list of DM and create only if I not found. And with forms I work similar.

Using this schema I can devide the project from my "2 1/2"-tier level on multi-tier in few simple operations (because the all business logic I store in database on db-server and mine DM-classes. The visual forms not contains the logic - only visualization).

PS: this technology I uses in tens projects (current and finished), in small app and in big projects. I described the customers-orders-products schema for availability of understanding only. Of course, sometimes (more often in the beginning of development of small app) the perspective of the extension of functionality up to large client-server
system (and especially multi-tier) is not visible and it's possible to go on easy way - drop on form the ttables (or even to allocate them in separate datamodule), linked it with grids, use a master-detail link and lookup fields etc
But when you decide to expand possibilities of the app and transfer it from local DB (Paradox/DBase/Access etc) on normal DB server and maybe use a 3-tier you will understand, that is necessary to change the approach to DB programming for rise of productivity, easy support and extension of project.

Of course, it's my opinion only, but I have come to such technology by many cuts and tries during 10 years on different DBs and tools of development. Though I still do not have also thirty years I have a  large number of successful developments in other departs and if I can to someone reduce this long way, it will be well.

I do not apply for indisputable true and I know many weak places in the described technology, but they not impasses - it is simply not up to the end are realized (and it's good:))) I'll read criticism of other with pleasure too.

2008. június 17., kedd

How to indent the focus rectangle in an owner-drawn TListBox


Problem/Question/Abstract:

I have a TListBox with style lbOwnerDrawVariable. I want to draw some text indented 10 pixels from the left (this is no problem). However, I also want it so the highlighting color and focus rectangle are indented as well (so the 10 pixel margin is completely blank, no matter what items are selected). I can't seem to do this ... the focus rectangle always seems to extend all the way to the left. How do I get around this?

Answer:

The problem is that the control as coded draws the focus rectangle after your owner drawing code has completed. To override that you have to make a new control descending from TListbox (or TCustomlistbox) and give it a handler for the CN_DRAWITEM message. Here you need to duplicate what the TCustomlistbox.CNDrawItem method does:

procedure TMyListBox.CNDrawItem(var Message: TWMDrawItem);
var
  State: TOwnerDrawState;
begin
  with Message.DrawItemStruct^ do
  begin
    State := TOwnerDrawState(LongRec(itemState).Lo);
    Canvas.Handle := hDC;
    Canvas.Font := Font;
    Canvas.Brush := Brush;
    if (Integer(itemID) >= 0) and (odSelected in State) then
    begin
      Canvas.Brush.Color := clHighlight;
      Canvas.Font.Color := clHighlightText
    end;
    if Integer(itemID) >= 0 then
      DrawItem(itemID, rcItem, State)
    else
      Canvas.FillRect(rcItem);
    if odFocused in State then
    begin
      Inc(rcItem.left, 10); {this is the change}
      DrawFocusRect(hDC, rcItem);
    end;
    Canvas.Handle := 0;
  end;
end;

2008. június 16., hétfő

How to put data from a TStringGrid into an Excel spreadsheet


Problem/Question/Abstract:

How to put data from a TStringGrid into an Excel spreadsheet

Answer:

procedure TTradingForm.Button1Click(Sender: TObject);
var
  i, j: Integer;
  r, c: Integer;
  v: OleVariant;
  sRange: string;
  WorkBook: _Workbook;
  WorkSheet: _Worksheet;

  function ColToStr(ilCol: integer): string;
  var
    FirstLetter: integer;
    lastLetter: integer;
  begin
    result := '';
    firstLetter := (ilCol - 1) div 26 - 1;
    lastLetter := (ilCol - 1) mod 26;
    if firstLetter >= 0 then
      result := chr(ord('A') + firstletter);
    result := result + chr(ord('A') + lastLetter);
  end;

begin
  v := VarArrayCreate([0, Grid.RowCount - 1, 0, Grid.ColCount - 1], varVariant);
  for i := 0 to Grid.RowCount - 1 do
  begin
    for j := 0 to Grid.ColCount - 1 do
      if i = 0 then
        v[i, j] := Grid.Columns[j].Caption
      else
        v[i, j] := Grid.Cells[j, i];
  end;
  Screen.Cursor := crHourglass;
  try
    Excel.Connect;
    Excel.Visible[GetUserDefaultLCID] := False;
    WorkBook := Excel.Workbooks.Add(EmptyParam, GetUserDefaultLCID);
    WorkBook.Activate(GetUserDefaultLCID);
    Worksheet := Excel.ActiveWorkbook.Worksheets.Add(EmptyParam, EmptyParam, 2,
      xlWBATWorksheet, GetUserDefaultLCID) as _Worksheet;
    Excel.XLSelectWorksheet(1);
    r := VarArrayHighBound(v, 2) + 1;
    c := VarArrayHighBound(v, 1) + 1;
    sRange := 'A1..' + ColToStr(r) + IntToStr(c);
    Excel.XLSetRangeValue(sRange, v);
  finally
    Excel.Visible[GetUserDefaultLCID] := True;
    Excel.Disconnect;
    v := null;
    Screen.Cursor := crDefault;
  end;
end;

2008. június 15., vasárnap

Build a TTreeView from a file


Problem/Question/Abstract:

I would like to populate a TTreeView from a simple file with the following structure

Key: Integer (unique)
Name: String (description)
Parent: Integer (key of parent in treeview)

I assume that the key and parent fields are all I need to build the treeview (parent = 0 would be a root node)

Answer:

I would break this down into two steps:

1) Read the file into memory
2) Populate the treeview using a recursive function


1) One method of doing this would be by building a TCollection/ TCollectionItem pair of classes. The TCollectionItems just need three fields:

TInputItem = class(TCollectionItem)
private
  fKey: integer;
  fName: string;
  fParent: integer;
public
  property Key: integer read fKey write fKey;
  property Name: string read fName write fName;
  property Parent: integer read fParent write fParent;
end;

Note: using properties is not strictly necessary, but is good style as it allows easier subsequent amendment.

Now we could use a standard TCollection to hold our TInputItems but it is neater to have a descendent of this too:

TInputCollection = class(TCollection)
public
  function AddItem(const AName: string; AKey, AParent: integer): TInputItem;
  property InputItem[index: integer]: TInputItem read GetInputItem; default;
end;

Creating a default property like InputItem above makes coding very tidy. It allows us to do the following:

var
  InputCollection: InputCollection;
  ix: integer;

InputCollection := TInputCollection.Create(TInputItem);
InputCollection.AddItem('First', 1, 0);
InputCollection.AddItem('Second', 2, 0);
InputCollection.AddItem('FirstChild', 3, 1);

for ix := 0 to InputCollection.Count - 1 do
  if InputCollection[ix].Parent = 0 then
    {DoSomething};

The last line, because of the index property being declared default, is the same as:

if InputCollection.InputItem[ix].Parent = 0 then
  {DoSomething;}

Without the property at all, you would code:

if TInputItem(InputCollection.Items[ix]).Parent = 0 thenDoSomething;
{DoSomething;}

In order to support the above, the implementation of the two methods:

function TInputCollection.AddItem(const AName: string; AKey, AParent: integer):
  TInputItem;
begin
  Result := Add as TInputItem;
  Result.Key := AKey;
  Result.Name := AName;
  Result.Parent := AParent;
end;

function TInputCollection.GetInputItem(index: integer): TInputItem;
begin
  Result := Items[ix] as TInputItem;
end;

We can now design an overall structure of a PopulateTree procedure:

procedure PopulateTree(tv: TTreeView);
var
  ic: TInputCollection;
begin
  ic := TInputCollection.Create(TInputItem);
  try
    LoadTreeItems(ic);
    PopulateTreeItems(tv, nil, ic, 0);
  finally
    ic.Free;
  end;
end;

LoadTreeItems can be tested via code similar to:

procedure LoadTreeItems(ic: TInputCollection);
begin
  ic.AddItem('First', 1, 0);
  ic.AddItem('Second', 2, 0);
  ic.AddItem('FirstChild', 3, 1);
end;

before replacing with your own loop through your input file. PopulateTreeItems is passed the treeview, the parent node and the parent id and it is a recursive routine.


2) Having done all the above, this part is now very easy. PopulateTreeItems iterates through the collection looking for items that match the passed parent id. For each item that matches, it adds a treenode and then calls PopulateTreeItems passing itself as the parent:

procedure PopulateTreeItems(tv: TTreeView; pnode: TTreeNode; ic: TInputCollection;
  parent: integer);
var
  node: TTreeNode;
  ix: integer;
begin
  for ix := 0 to ic.Count - 1 do
  begin
    if ic[ix].Parent = parent then
    begin
      node := tv.Items.Add(pnode, ic[ix].Name);
      PopulateTreeItems(tv, node, ic, ic[ix].Key); {recursive call}
    end;
  end;
end;

I apologise in advance if there are problems with the above code. It is completely untested. In practice, I don't do things quite like that, but populate treenodes on demand via the OnExpand event handler.

2008. június 14., szombat

How to measure the distance between two points


Problem/Question/Abstract:

I need to measure a distance between two points to compute an intensity value. Currently I use

Dx := i - Cur.X;
Dy := j - Cur.Y;
Distance := Round(Sqrt(Dx * Dx + Dy * Dy));

Is there a faster way to compute this? Distance is an integer type so I was hoping to get a reasonably accurate solution as this method seems to produce. Problem is that it slows things down a bit.

Answer:

You can calculate the angle and then make a lookup table for the value to be used to multiply the Y-axis. Try this:


program DCDemo;

{$APPTYPE CONSOLE}

uses
  SysUtils, Math, My_Crt32;

const
  DegToRadFact = Pi / 180;
  RadToDegFact = 180 / Pi;

var
  DistSinArray: array[1..359] of Extended;

function Calcdist(const dX, dY: LongInt): LongInt;
var
  Angle, n: word;
begin
  if (dX <> 0) then
  begin
    if (dY <> 0) then
    begin
      Angle := Round(ArcTan(dY / dX) * RadToDegFact);
      Result := Round(dY / DistSinArray[Angle]);
    end
    else
      Result := dX;
  end
  else
    Result := dY;
end;

var
  dX, dY: LongInt;
begin
  {First time operation}
  for dX := 1 to 359 do
    DistSinArray[dX] := Sin(dX * DegToRadFact);
  Writeln('Geef dX: ');
  readln(dX);
  Writeln('Geef dY: ');
  readln(dY);
  Writeln(FloatToStr(Calcdist(dX, dY)));
  Readln;
end.

2008. június 13., péntek

TClientDataSet: Temporary vs. permanent indices


Problem/Question/Abstract:

TClientDataSet: Temporary vs. permanent indices

Answer:

On TClientDataSets, that are not connected to a provider but loaded as local text files, ('thin client in brief-case mode'), you cannot use TQuerys. You may use the Filter property to select data and use an index to sort.

This sample code shows how to create a temporary index (not saved by SaveToFile())
and how to create a permanent index (saved by SaveToFile()):

with ClientDataSet1 do
begin
  Close;
  // Define the fields
  FieldDefs.Clear;
  FieldDefs.Add('Project', ftInteger, 0, True);
  FieldDefs.Add('Number', ftInteger, 0, False);
  // [..]
  // Define the PERMANENT index - it is saved with SaveToFile()
  IndexDefs.Clear;
  IndexDefs.Add('IProject', 'Project', [ixPrimary, ixUnique]);
  // Create the dataset
  CreateDataSet;
  Open;
  // the following temporary index is not saved
  // with data when using SaveToFile()
  AddIndex('TempIndex', 'Number;Project', [ixPrimary, ixUnique]);
end { with ClientDataSet1 }

2008. június 12., csütörtök

Create an array of TEdit components on a TFrame


Problem/Question/Abstract:

Is it possible to have an array of components (for example TEdit) in a frame? If so, must I create them at runtime?

Answer:

You can create the components at design time, as usual. But you have to set up the array with the component references at run-time. This is quite painless if you use the default naming for the components the IDE produces, or something equivalent, with a running number at the names end:

{ ... }
private
Edits: array[1..10] of TEdit;
public

constructor Create(aOwner: TComponent); override;
{ ...  }

constructor TFrameX.Create(aOwner: TComponent);
var
  i: Integer;
  edt: TComponent;
begin
  inherited;
  for i := Low(Edits) to High(Edits) do
  begin
    edt := FindComponent('edit' + IntToStr(i));
    Assert(Assigned(edt), 'Edit' + IntToStr(i) + ' not found!');
    Edits[i] := edt as TEdit;
  end;
end;

2008. június 11., szerda

Installer problem: '-115 error'


Problem/Question/Abstract:

I have reinstalled my system (NT 4.0 WS), but now when trying to install my Delphi 5 Professional I get this message:

**************
Setup has detected a -115 error while attempting to copy files.

This can be caused by a file being un use while trying to install.

Component: Program Files\Main program files\debugger
File: C:\Program Files\Common Files\Borland Shared\Debugger\bordbk50.dll\bordbk50.dll
Error: -115
**************

Answer:

Open a command-prompt. Change to the 'C:\Program Files\Common Files\Borland Shared\Debugger' directory and type the following command:

regsvr32 bordbk50.dll

2008. június 10., kedd

How to create custom graphic hints


Problem/Question/Abstract:

How can I create my own hint windows, even with bitmaps and so on?

Answer:

You just have to add the following code to your project (it's just an example). You don't need to change something else in your project.  Maybe the code will run in earlier versions of Borland Delphi too, but didn't test it.


type
  TGraphicHintWindow = class(THintWindow)
    constructor Create(AOwner: TComponent); override;
  private
    FActivating: Boolean;
  public
    procedure ActivateHint(Rect: TRect; const AHint: string); override;
  protected
    procedure Paint; override;
  published
    property Caption;
  end;

  {...}

constructor TGraphicHintWindow.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  //Here you can set custom Font properties:

  with Canvas.Font do
  begin
    Name := 'Arial';
    Style := style + [fsBold];
    Color := clBlack;
  end;
end;

procedure TGraphicHintWindow.Paint;
var
  R: TRect;
  bmp: TBitmap;
  l: TStrings;
begin
  R := ClientRect;
  Inc(R.Left, 2);
  Inc(R.Top, 2);

  {*******************************************************
   The following Code is an example how to create a custom
   Hint Object. :
   *******************************************************}

  bmp := TBitmap.create;
  bmp.LoadfromFile('D:\hint.bmp');

  with Canvas do
  begin
    Brush.style := bsSolid;
    Brush.color := clsilver;
    Pen.color := clgray;
    Rectangle(0, 0, 18, R.Bottom + 1);
    Draw(2,(R.Bottom div 2) - (bmp.height div 2), bmp);
  end;

  bmp.free;

  Color := clWhite; //Beliebige HintFarbe
  //custom Hint Color

  Canvas.Brush.style := bsClear;
  //Canvas.TextOut(20,(R.Bottom div 2)-(canvas.Textheight(caption) div 2),caption);
  Inc(R.Left, 20);
  l := TStringlist.create;
  l.SetText(PChar(Caption));
  R.top := (R.Bottom div 2) - ((canvas.Textheight(caption) * l.count) div 2);

  DrawText(Canvas.Handle, PChar(Caption), -1, R, 0);

  l.free;
  {********************************************************}
end;

procedure TGraphicHintWindow.ActivateHint(Rect: TRect; const AHint: string);
begin
  FActivating := True;
  try
    Caption := AHint;
    Inc(Rect.Bottom, 14); //Set the "Height" Property of the Hint

    Rect.Right := Rect.right + 20; //Set the "Width" Property of the Hint
    UpdateBoundsRect(Rect);
    if Rect.Top + Height > Screen.DesktopHeight then
      Rect.Top := Screen.DesktopHeight - Height;
    if Rect.Left + Width > Screen.DesktopWidth then
      Rect.Left := Screen.DesktopWidth - Width;
    if Rect.Left < Screen.DesktopLeft then
      Rect.Left := Screen.DesktopLeft;
    if Rect.Bottom < Screen.DesktopTop then
      Rect.Bottom := Screen.DesktopTop;
    SetWindowPos(Handle, HWND_TOPMOST, Rect.Left, Rect.Top, Width, Height,
      SWP_SHOWWINDOW or SWP_NOACTIVATE);
    Invalidate;
  finally
    FActivating := False;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  HintWindowClass := TGraphicHintWindow;
  Application.ShowHint := False;
  Application.ShowHint := True;
end;

2008. június 9., hétfő

How to iterate over the MDI child array


Problem/Question/Abstract:

How to iterate over the MDI child array

Answer:

The documentation of TForm.MDIChildren[] states that the index of the first-created MDI child is 0. This is incorrect - the index of the most-recently-created MDI child is always 0, and the index of the first-created MDI child is always MDIChildCount - 1. With this in mind, you can use the following code to iterate over the MDI child array from from the first-created to the last:


procedure TForm1.IterateOverMDIChildren;
var
  i: integer;
begin
  for i := MDIChildCount - 1 downto 0 do
  begin
    {do something with MDI child here}
  end;
end;

2008. június 8., vasárnap

How to hide the seconds in a TDateTimePicker


Problem/Question/Abstract:

I'm using a TDateTimePicker component to edit time values, but I don't need the seconds. Input of hours and minutes would be enough. Setting the time format in the control panel to "hh:mm" does this. But I would like to change any settings in the application to get the result.

Answer:

{ ... }
const
  date_format = 'yyyy MMM dd'; {year, short month, day format}
  L_time_format = 'HH:mm:ss'; {24-hour clock, hours, minutes and seconds}
  S_time_format = 'HH:mm'; {24-hour clock, hours, minutes}
var
  fmt: string;
begin
  fmt := L_time_format; {Was S_time_format}
  SendMessage(DateTimePickerTime.Handle, DTM_SETFORMATA, 0, Integer(fmt));
  fmt := date_format;
  SendMessage(DateTimePickerDate.Handle, DTM_SETFORMATA, 0, Integer(fmt));
end;

2008. június 7., szombat

How to stop and delete all print jobs through code


Problem/Question/Abstract:

I can stop and delete current printing jobs from the "Printer Manager". Can I do it in my code, too? I want to stop all print jobs and delete them in my program.

Answer:

Try the PurgeJobsOnCurrentPrinter procedure given below. Not tested!

uses
  winspool, printers;

{GetCurrentPrinterHandle:
Retrieves the handle of the current printer and returns an API printer handle for the
current printer. Uses WinSpool.OpenPrinter to get a printer handle. The caller takes
ownership of the handle and must call ClosePrinter on it once the handle is no longer
needed. Failing to do that creates a serious resource leak! Raises EWin32Error if the
OpenPrinter call fails.}

function GetCurrentPrinterHandle: THandle;
const
  Defaults: TPrinterDefaults = (pDatatype: nil; pDevMode: nil; DesiredAccess:
    PRINTER_ACCESS_USE or PRINTER_ACCESS_ADMINISTER);
var
  Device, Driver, Port: array[0..255] of char;
  hDeviceMode: THandle;
begin
  Printer.GetPrinter(Device, Driver, Port, hDeviceMode);
  if not OpenPrinter(@Device, Result, @Defaults) then
    RaiseLastWin32Error;
end;

{Kill all pending jobs on the current printer}

procedure PurgeJobsOnCurrentPrinter;
var
  hPrinter: THandle;
begin
  hPrinter := GetCurrentPrinterHandle;
  try
    if not WinSpool.SetPrinter(hPrinter, 0, nil, PRINTER_CONTROL_PURGE) then
      RaiseLastWin32Error;
  finally
    ClosePrinter(hPrinter);
  end;
end;

2008. június 6., péntek

Italian tax payer code checksum algorithm


Problem/Question/Abstract:

In Italy tax payer code ("codice fiscale") is a 16 digits string identifying every one. The last char is a checksum to simply check if code is valid. There's a simply and rapid function that returns is it's valid.

Answer:

function IsTaxPayer(code: string): boolean;
const
  tables: array[0..1] of string =
    ('A0B1C2D3E4F5G6H7I8J9KKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ',
    'B1A0KKPPLLC2QQD3RRE4VVOOSSF5TTG6UUH7MMI8NNJ9WWZZYYXX');
var
  i, x: integer;
begin
  result := false;
  code := uppercase(trim(code));
  if (length(code) = 16) then
  begin
    i := 0;
    for x := 1 to 15 do
      i := i + (((pos(code[x], tables[x mod 2]) - 1) and ($7FFE)) div 2);
    result := chr(65 + (i mod 26)) = code[length(code)];
  end;
end;

2008. június 5., csütörtök

How to determine if an object has a particular property


Problem/Question/Abstract:

How to determine if an object has a particular property

Answer:

The first hasprop will return True if a property of name prop exists, eg. hasprop(MyLabel,'Caption') will return true while hasprop(MyEdit,'Caption') will return false. The second one will set property prop to string value s if it exists and is a string type property.

function hasprop(comp: TComponent; const prop: string): Boolean;
var
  proplist: PPropList;
  numprops, i: Integer;
begin
  result := false;
  getmem(proplist, getTypeData(comp.classinfo)^.propcount * Sizeof(Pointer));
  try
    NumProps := getproplist(comp.classInfo, tkProperties, proplist);
    for i := 0 to pred(NumProps) do
    begin
      if comparetext(proplist[i]^.Name, prop) = 0 then
      begin
        result := true;
        break;
      end;
    end;
  finally
    freemem(proplist, getTypeData(comp.classinfo)^.propcount * Sizeof(Pointer));
  end;
end;

procedure setcomppropstring(comp: TComponent; const prop, s: string);
var
  proplist: PPropList;
  numprops, i: Integer;
begin
  getmem(proplist, getTypeData(comp.classinfo)^.propcount * Sizeof(Pointer));
  try
    NumProps := getproplist(comp.classInfo, tkProperties, proplist);
    for i := 0 to pred(NumProps) do
    begin
      if (comparetext(proplist[i]^.Name, prop) = 0) and
        (comparetext(proplist[i]^.proptype^.name, 'string') = 0 then
        begin
          setStrProp(comp, proplist[i], s);
          break;
        end;
    end;
  finally
    freemem(proplist, getTypeData(comp.classinfo)^.propcount * Sizeof(Pointer));
  end;
end;

2008. június 4., szerda

Dynamic loading and binding of DLLs


Problem/Question/Abstract:

Dynamic loading and binding of DLLs

Answer:

Sometimes you may need to load a DLL at runtime, for example

if you have a couple of different DLLs to choose between
to have a concept for optional functionality.

This small source code shows how to load the DLL with LoadLibrary and use the returned handle to access (bind) the functions that are contained:

{ function declaration }

type
  � TfncCnx = function(s: string): THandle;

var
  � cnx: TfncCnx;
begin
  { load the DLL and get the function's address }
  � h := LoadLibrary('myDll');
  if h = 0 then
  begin
    ShowMessage('DLL not available');
  end
  else
  begin
    �@cnx := GetProcAddress(h, 'myProc');
    if @cnx = nil then
    begin
      { function not found.. misspelled? }
      ShowMessage('blub');
    end
    else
    begin
      { call the function as usually }
      x := cnx('alpha');
    end;
    { unload the DLL }
    � FreeLibrary(h);
  end;
end;

2008. június 3., kedd

How to get the text width of a TControl in pixels


Problem/Question/Abstract:

How can I find the length of a string drawn in a particular font? For instance, Edit1 has the text of 'Hello World' in Arial bold, size = 16.

Answer:

Solve 1:

You measure it using the TextWidth method of a canvas into which the font has been copied. You can usually use the forms Canvas for this kind of work since it is not used anywhere else (unless you have a handler for the OnPaint event). The typcial code would be:

canvas.font := edit1.font; {edit1.font has size etc. to measure}
aTextwidth := canvas.TextWidth(someText);

One problem with this approach is that it will fail if you do the measuring at a time the form does not have a window handle yet. I prefer to use a dynamically created canvas for this kind of task:

function CalcMaxWidthOfStrings(aList: TStrings; aFont: TFont): Integer;
var
  max, n, i: Integer;
  canvas: TCanvas;
begin
  Assert(Assigned(aList));
  Assert(Assigned(aFont));
  canvas := TCanvas.Create;
  try
    canvas.Handle := CreateDC('DISPLAY', nil, nil, nil);
    try
      Canvas.Font := aFont;
      max := 0;
      for i := 0 to aList.Count - 1 do
      begin
        n := Canvas.TextWidth(aList[i]);
        if n > max then
          max := n;
      end;
      Result := max;
    finally
      DeleteDC(canvas.Handle);
      canvas.Handle := 0;
    end;
  finally
    canvas.free;
  end;
end;


Solve 2:

function GetTextWidthInPixels(AText: string; AControl: TControl): integer;
var
  propInfo: PPropInfo;
  thisFont: TFont;
begin
  Result := 0;
  propInfo := GetPropInfo(AControl.ClassInfo, 'Font');
  if propInfo <> nil then
  begin
    thisFont := TFont(GetObjectProp(AControl, 'Font'));
    if Assigned(thisFont) then
      with TControlCanvas.Create do
      try
        Control := AControl;
        Font.Assign(thisFont);
        Result := TextWidth(AText);
      finally
        Free;
      end;
  end;
end;

Call with:

twidth := GetTextWidthInPixels(Edit1.Text, Edit1);

2008. június 2., hétfő

Draw to a metafile


Problem/Question/Abstract:

How to draw to a metafile

Answer:

unit Metaform;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, Buttons, ExtCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    BitBtn1: TBitBtn;
    Image1: TImage;
    procedure BitBtn1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

type
  TMetafileCanvas = class(TCanvas)
  private
    FClipboardHandle: THandle;
    FMetafileHandle: HMetafile;
    FRect: TRect;
  protected
    procedure CreateHandle; override;
    function GetMetafileHandle: HMetafile;
  public
    constructor Create;
    destructor Destroy; override;
    property Rect: TRect read FRect write FRect;
    property MetafileHandle: HMetafile read GetMetafileHandle;
  end;

constructor TMetafileCanvas.Create;
begin
  inherited Create;
  FClipboardHandle := GlobalAlloc(GMEM_SHARE or GMEM_ZEROINIT, SizeOf(TMetafilePict));
end;

destructor TMetafileCanvas.Destroy;
begin
  DeleteMetafile(CloseMetafile(Handle));
  if Bool(FClipboardHandle) then
    GlobalFree(FClipboardHandle);
  if Bool(FMetafileHandle) then
    DeleteMetafile(FMetafileHandle);
  inherited Destroy;
end;

procedure TMetafileCanvas.CreateHandle;
var
  MetafileDC: HDC;
begin
  { Create a metafile DC in memory }
  MetafileDC := CreateMetaFile(nil);
  if Bool(MetafileDC) then
  begin
    { Map the top,left corner of the displayed rectangle to the top,left of the
      device context. Leave a border of 10 logical units around the picture. }
    with FRect do
      SetWindowOrg(MetafileDC, Left - 10, Top - 10);
    { Set the extent of the picture with a border of 10 logical units.}
    with FRect do
      SetWindowExt(MetafileDC, Right - Left + 20, Bottom - Top + 20);
    { Play any valid metafile contents to it. }
    if Bool(FMetafileHandle) then
    begin
      PlayMetafile(MetafileDC, FMetafileHandle);
    end;
  end;
  Handle := MetafileDC;
end;

function TMetafileCanvas.GetMetafileHandle: HMetafile;
var
  MetafilePict: PMetafilePict;
  IC: HDC;
  ExtRect: TRect;
begin
  if Bool(FMetafileHandle) then
    DeleteMetafile(FMetafileHandle);
  FMetafileHandle := CloseMetafile(Handle);
  Handle := 0;
  { Prepair metafile for clipboard display. }
  MetafilePict := GlobalLock(FClipboardHandle);
  MetafilePict^.mm := mm_AnIsoTropic;
  IC := CreateIC('DISPLAY', nil, nil, nil);
  SetMapMode(IC, mm_HiMetric);
  ExtRect := FRect;
  DPtoLP(IC, ExtRect, 2);
  DeleteDC(IC);
  MetafilePict^.xExt := ExtRect.Right - ExtRect.Left;
  MetafilePict^.yExt := ExtRect.Top - ExtRect.Bottom;
  MetafilePict^.HMF := FMetafileHandle;
  GlobalUnlock(FClipboardHandle);
  { I'm giving you this handle, but please do NOT eat it. }
  Result := FClipboardHandle;
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
var
  MetafileCanvas: TMetafileCanvas;
begin
  MetafileCanvas := TMetafileCanvas.Create;
  MetafileCanvas.Rect := Rect(0, 0, 500, 500);
  MetafileCanvas.Ellipse(10, 10, 400, 400);
  Image1.Picture.Metafile.LoadFromClipboardFormat(cf_MetafilePict,
    MetafileCanvas.MetafileHandle, 0);
  MetafileCanvas.Free;
end;

end.

2008. június 1., vasárnap

Paste files from Windows Explorer into your application


Problem/Question/Abstract:

I would like to be able to go to Windows Explorer, select a series of files, and then allow the user to "Paste" these files into my application. I simply need a list of the file names that were copied to the clipboard. Anyone know how to access this list?

Answer:

You can use OLE Drag & Drop, but since Explorer creates a standard CF_HDROP clipboard block you can also hack it this way:

uses
  clipbrd, shellapi;

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var
  f: THandle;
  buffer: array[0..MAX_PATH] of Char;
  i, numFiles: Integer;
begin
  Clipboard.Open;
  try
    f := Clipboard.GetAsHandle(CF_HDROP);
    if f <> 0 then
    begin
      numFiles := DragQueryFile(f, $FFFFFFFF, nil, 0);
      memo1.Clear;
      for i := 0 to numfiles - 1 do
      begin
        buffer[0] := #0;
        DragQueryFile(f, i, buffer, sizeof(buffer));
        memo1.lines.add(buffer);
      end;
    end;
  finally
    Clipboard.close;
  end;
end;