2006. március 31., péntek

Search for a keyword in a help file


Problem/Question/Abstract:

How to search for a keyword in a help file

Answer:

const
  EmptyString: PChar = '';
begin
  Application.HelpCommand(HELP_PARTIALKEY, LongInt(EmptyString));
end;

2006. március 30., csütörtök

Generic File Importer Base Class


Problem/Question/Abstract:

Here is a useful base class to create derived classes to import data from any flat file format you can think of...

Answer:

{-----------------------------------------------------------------------------
Unit Name:     classParentDataManipulator
Author:        StewartM (Stewart Moss)

Documentation Date: 23, 08, 2002  (14:39,)

Version 1.0
-----------------------------------------------------------------------------

Compiler Directives:

Purpose:

Dependancies:

Description:

Parent Class for data manipulation
Creates the basic skelton for adding data manipulation sub-classes

Each of the inherited classes must override the ProcessData method and provide
  their own properties specific to the class (ie Invoice Number etc...)

Very useful class.

inheritance Diagram

  + -- TParentDataProcessor    // base class
       +
       |
       + --- TDerivedImporter  // sub class

Notes:

History:

Copyright 2002 by Stewart Moss.
All rights reserved.
-----------------------------------------------------------------------------}

unit classParentDataManipulator;

interface

uses Sysutils, Classes;

type
  TParentDataProcessor = class(TObject)
  private
    StringIn: string;
    LineCounter: Integer;

  public
    FieldNames,
      FieldValues,
      MultiFieldNames,
      MultiFieldValues: TStringList;

    FormName,
      FileName: string;
    Delimiters: string;
    // A list of delimiters (ie ',/[];:') used in inherited ProcessData()

    constructor create;
    destructor Destroy; override;
    procedure ProcessFile;

    function DataAtPos(S: string; StartP, EndP: Integer): string;
    // Returns the data from "StartP" to "EndP" in String "S"

    function ExpandTabs(s: string): string;
    // ExpandTabs to 8 Spaces

    procedure ProcessData(StrIn: string; LineNumber: Integer); virtual;
    // Virtual method for override in sub-classes

    procedure FieldAdd(FieldName, Data: string; GenException: Boolean);
    // Adds FieldName and FieldValue to Strings and can generate exception if
    // string is empty when required

    procedure MultiFieldAdd(FieldName, Data: string; GenException: Boolean);
    // Adds FieldName and FieldValue to Multi Field Strings and can generate exception
    // if string is empty when required

  end;

  TProcessException = Exception;

implementation

var
  F: text;
  //  Exception: TProcessException;

  { TDataProcessor }

constructor TParentDataProcessor.create;
begin
  inherited create;
  FieldNames := TStringList.Create;
  FieldValues := TStringList.Create;
  MultiFieldNames := TStringList.Create;
  MultiFieldValues := TStringList.Create;
  FieldNames.Clear;
  FieldValues.Clear;
  MultiFieldNames.Clear;
  MultiFieldValues.Clear;
end;

procedure TParentDataProcessor.ProcessFile;
begin
  if Filename = '' then
    raise Exception.Create('No Filename specified');

  try
    AssignFile(F, Filename);
    Reset(f);
  except
    try
      CloseFile(F);
    except
    end;
    raise Exception.Create('Could not open file ' + FileName);
  end;

  LineCounter := 0;

  while not eof(f) do
  begin
    Inc(LineCounter);

    try
      Readln(f, StringIn);
    except
      try
        CloseFile(f);
      except // swallow CloseFile errors
      end;

      raise Exception.Create('Could not read from file. Line number ' +
        IntToStr(LineCounter));
    end;

    StringIn := ExpandTabs(StringIn);
    // Exapnd Tabs to 8 Spaces

    ProcessData(StringIn, LineCounter);
    // Execute virutal method in sub-classes passing current line and LineNumber
  end;

  try
    closefile(f);
  except
    raise Exception.Create('Could not close file ' + FileName);
  end;
end;

procedure TParentDataProcessor.ProcessData(StrIn: string; LineNumber: Integer);
// Virtual method for override in sub-classes
begin
  //
end;

destructor TParentDataProcessor.Destroy;
begin
  FieldNames.Free;
  FieldValues.Free;
  MultiFieldNames.Free;
  MultiFieldValues.Free;
end;

function TParentDataProcessor.DataAtPos(S: string; StartP,
  EndP: Integer): string;
begin
  // Returns the data from "StartP" to "EndP" in String "S"
  Result := trim(Copy(S, StartP, EndP - StartP));
end;

function TParentDataProcessor.ExpandTabs(s: string): string;
begin
  // ExpandTabs to 8 Spaces
  Result := StringReplace(S, #09, '        ', [rfReplaceAll]);
end;

procedure TParentDataProcessor.FieldAdd(FieldName, Data: string;
  GenException: Boolean);
begin
  // Adds FieldName and FieldValue to Strings and can generate exception if
  // string is empty
  if (GenException) and (Data = '') then
    raise Exception.create('-- No ' + FieldName + ' Specified --');
  Fieldnames.add(FieldName);
  FieldValues.add(Data);
end;

procedure TParentDataProcessor.MultiFieldAdd(FieldName, Data: string;
  GenException: Boolean);

var
  loop: integer;
  flag: Boolean;
begin
  // Adds FieldName and FieldValue to Multi Field Strings and can generate exception
  // if string is empty

  if (GenException) and (Data = '') then
    raise Exception.create('-- No Multiple Field - ' + FieldName + ' Specified --');

  flag := false;
  loop := 0;
  while (loop < MultiFieldNames.count) and not flag do
  begin
    if MultiFieldNames.Strings[loop] = FieldName then
      flag := true;
    inc(Loop);
  end;

  dec(loop);

  if Flag then
    MultiFieldValues.Strings[loop] := MultiFieldValues.Strings[loop] + ';' + Data
  else
  begin
    MultiFieldNames.add(FieldName);
    MultiFieldValues.add(Data);
  end;
end;

end.

2006. március 29., szerda

How to give the scrollbars of a TRichEdit a flat look


Problem/Question/Abstract:

Does anyone know of a way to change the scroll bars in the RichEdit to obtain the flat look?

Answer:

Start with this one and do not forget to put CommCtrl in your uses clause:

procedure TForm1.FormCreate(Sender: TObject);
var
  XVerScrollInfo, XHorScrollInfo: TScrollInfo;
begin
  InitializeFlatSB(RichEdit1.Handle);
  GetScrollInfo(RichEdit1.Handle, SB_VERT, XVerScrollInfo);
  GetScrollInfo(RichEdit1.Handle, SB_HORZ, XHorScrollInfo);
  FlatSB_SetScrollProp(RichEdit1.Handle, WSB_PROP_VSTYLE, FSB_ENCARTA_MODE, True);
  FlatSB_SetScrollProp(RichEdit1.Handle, WSB_PROP_HSTYLE, FSB_ENCARTA_MODE, True);
end;

2006. március 28., kedd

Easily use HTML Help files in your programs


Problem/Question/Abstract:

Do you long to move from WinHelp to HTML Help in your programs? The unit below converts all WinHelp calls to HTML Help enabling you to upgrade with the minimum of effort.

Answer:

Save this unit to a directory on your Environment Options|Library|Library Path and add to your project uses clause, all WinHelp requests will now be translated to HTML Help. Specify your *.chm file in the Project Options|Application|Help file setting. Context sensitive help will work as normal, use TApplication.HelpCommand to send help commands. eg. Application.HelpCommand(HELP_KEY, DWORD(keyData))

unit dmHTMLHelp;
{Unit to translate WinHelp requests into HTML Help and call the API.}
{Written by Dave Murray, October 2001. dmurray@worldmark.com}
{NOTES:
This unit assigns its own handler to the Application.OnHelp event.
DO NOT assign your own handler to Application.OnHelp.
Also, this unit ignores any form's HelpFile property. (Delphi 4+)}

interface

uses
  Windows, Messages, SysUtils, Forms;

const
  {commands to pass to HtmlHelp(), see HTML Help API Reference}
  HH_DISPLAY_TOPIC = $0000; {open help topic}
  HH_HELP_FINDER = $0000; {backwards compatibility,use HH_DISPLAY_TOPIC instead}
  HH_DISPLAY_TOC = $0001; {select Contents tab in nav pane}
  HH_DISPLAY_INDEX = $0002; {select Index tab + search for keyword}
  HH_DISPLAY_SEARCH = $0003; {select Search tab in nav pane}
  HH_SET_WIN_TYPE = $0004;
  HH_GET_WIN_TYPE = $0005;
  HH_GET_WIN_HANDLE = $0006;
  HH_ENUM_INFO_TYPE = $0007;
  HH_SET_INFO_TYPE = $0008;
  HH_SYNC = $0009;
  HH_RESERVED1 = $000A; {not currently implemented}
  HH_RESERVED2 = $000B; {not currently implemented}
  HH_RESERVED3 = $000C; {not currently implemented}
  HH_KEYWORD_LOOKUP = $000D;
  HH_DISPLAY_TEXT_POPUP = $000E; {display string resource/text in a popup}
  HH_HELP_CONTEXT = $000F; {display topic for context number}
  HH_TP_HELP_CONTEXTMENU = $0010; {text popup help, same as HELP_CONTEXTMENU}
  HH_TP_HELP_WM_HELP = $0011; {text popup help, same as HELP_WM_HELP}
  HH_CLOSE_ALL = $0012; {close all windows opened by caller}
  HH_ALINK_LOOKUP = $0013; {ALink version of HH_KEYWORD_LOOKUP}
  HH_GET_LAST_ERROR = $0014; {not currently implemented}
  HH_ENUM_CATEGORY = $0015;
  HH_ENUM_CATEGORY_IT = $0016;
  HH_RESET_IT_FILTER = $0017;
  HH_SET_INCLUSIVE_FILTER = $0018;
  HH_SET_EXCLUSIVE_FILTER = $0019;
  HH_INITIALIZE = $001C;
  HH_UNINITIALIZE = $001D;
  HH_PRETRANSLATEMESSAGE = $00FD;
  HH_SET_GLOBAL_PROPERTY = $00FC;

function HtmlHelp(hwndCaller: THandle; pszFile: PChar; uCommand: cardinal; dwData:
  longint): THandle; stdcall;

implementation

function HtmlHelp(hwndCaller: THandle; pszFile: PChar; uCommand: cardinal; dwData:
  longint): THandle; stdcall; external 'hhctrl.ocx' name 'HtmlHelpA'; {external API call}

type
  TdmHTMLHelp = class(TObject) {encapsulates function}
    function ApplicationHelp(Command: Word; Data: Longint; var CallHelp: Boolean):
      Boolean;
  end; {TdmHTMLHelp..}

function TdmHTMLHelp.ApplicationHelp(Command: Word; Data: Longint; var CallHelp:
  Boolean): Boolean;
{translates WinHelp commands to HTMLHelp commands + calls API}
var
  HCommand: word;
begin
  {make sure VCL doesn't activate WinHelp + function succeeds}
  CallHelp := false;
  result := true;
  {translate WinHelp > HTMLHelp}
  case Command of
    HELP_CONTENTS:
      begin
        HCommand := HH_DISPLAY_TOC;
        Data := 0;
      end; {HELP_CONTENTS..}
    HELP_CONTEXT: HCommand := HH_HELP_CONTEXT;
    HELP_CONTEXTPOPUP: HCommand := HH_HELP_CONTEXT;
    HELP_FINDER: HCommand := HH_DISPLAY_TOPIC;
    HELP_KEY: HCommand := HH_DISPLAY_INDEX;
    HELP_QUIT:
      begin
        HCommand := HH_CLOSE_ALL;
        Data := 0;
      end; {HELP_QUIT..}
  else
    begin {default}
      HCommand := HH_DISPLAY_TOPIC;
      Data := 0;
    end; {default..}
  end; {case Command..}
  {call HTML Help API}
  HtmlHelp(Application.MainForm.Handle, PChar(Application.HelpFile), HCommand, Data);
end; {function TdmHTMLHelp.ApplicationHelp}

var
  HTMLHelper: TdmHTMLHelp;

initialization
  {create object + assign event handler}
  HTMLHelper := TdmHTMLHelp.Create;
  Application.OnHelp := HTMLHelper.ApplicationHelp;
finalization
  {free event handler + object}
  Application.OnHelp := nil;
  HTMLHelper.Free;
end.

2006. március 27., hétfő

Two ways to change the default project options


Problem/Question/Abstract:

Two ways to change the default project options

Answer:

You can change the default project options (which is being used by every new project you create) from Delphi GUI:

Create a new project (File | New Application)
Go to "Project | Options" and change the options as you wish.
Check "Default" checkbox in the tabs which you changed options in.

If you rather change the options "manually," you can do so using a simple text editor:

Edit defproj.dof file located in your Delphi's BIN directory (C:\Program Files\Borland\Delphi 2.0\Bin for example) using Notepad or any other text editor. If you don't see the defproj.dof, create one using your favorite text editor in the Delphi's BIN directory using the following format:

[Compiler]
A=1
B=0
C=0
D=1
E=0
F=0
G=1
H=1
I=1
J=1
K=0
L=1
M=0
N=1
O=1
P=1
Q=0
R=0
S=0
T=0
U=0
V=1
W=0
X=1
Y=0
Z=1
ShowHints=0
ShowWarnings=0
UnitAliases=WinTypes=Windows;<cont.>
WinProcs=Windows;DbiTypes=BDE;<cont.>
DbiProcs=BDE;DbiErrs=BDE;

[Linker]
MapFile=0
OutputObjs=0
ConsoleApp=1
DebugInfo=0
MinStackSize=16384
MaxStackSize=1048576
ImageBase=4194304
ExeDescription=

[Directories]
OutputDir=
SearchPath=
Conditionals=

[Parameters]
RunParams=

2006. március 26., vasárnap

How to display a help file on top of a form with style fsStayOnTop


Problem/Question/Abstract:

I have a form with its formstyle set to fsStayOnTop. If I display the help for this form, the help windows is opened behind my form. Even if I click on the help window it stays behind. How can I display the help windows in front of any form?

Answer:

You can do this by sending a macro to WinHelp.


procedure TForm1.Button1Click(Sender: TObject);
begin
  with Application do
  begin
    Helpfile := 'C:\Programme\Borland\Delphi5\Help\TOOLS\HCW.HLP';
    HelpCommand(HELP_CONTENTS, 0);
    HelpCommand(HELP_COMMAND, Integer(PChar('Generate(' + IntToStr(WM_COMMAND)
      + ', 1471, 0)')));
  end;
end;


This macro call fakes a menu action for the "keep on top" menu item. Use 1472 to deactivate the on top state. Help macros are documented in the helpfile I used above. The HelpOnTop macro gives the menu IDs to use for Generate in this case. HelponTop itself may be useful if you simply want to change the current on-top default for the window (it is a toggle).

2006. március 25., szombat

How to make a TButton flee from the mouse cursor


Problem/Question/Abstract:

As a joke, I am trying to have a button on a form move around the form to avoid the mouse pointer. I was trying to use the OnMouseMove event of the form to accomplish this, but have not had any success. Does anybody know a quick way of doing this?

Answer:

procedure TMainForm.Button1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
  NewPoint: TPoint;
begin
  Randomize;
  NewPoint.X := X;
  NewPoint.Y := Y;
  repeat
    NewPoint.X := NewPoint.X + Random((Sender as TButton).Width div 2);
    NewPoint.Y := NewPoint.Y + Random((Sender as TButton).Height div 2);
  until
    PtInRect(ClientRect, NewPoint) and not PtInRect((Sender as TButton).ClientRect, NewPoint);
  (Sender as TButton).Left := NewPoint.X;
  (Sender as TButton).Top := NewPoint.Y;
end;

2006. március 24., péntek

How to store several bitmaps into a single file


Problem/Question/Abstract:

Is there a simple way to write a TBitmap object to a file and read it back? I want to store bitmaps and other data all in one file (much like word processors are capable of doing).

Answer:

If you wish to store multiple things into a file, you'll need to implement some sort of file structure so you can know what and where things are in the file. For example, if you wished to store several bitmaps to a file, you could structure your file like this:

file header
bitmap count

bitmap header
bitmap size
bitmap stream
bitmap trailer
...

file trailer

Where "file header" contains information such as the version of the file and a unique file structure identifier, "bitmap count" is the number of bitmaps saved to the file, "bitmap header" is a unique identifier which indicates the start of a bitmap entry in the file, "bitmap size" is the size of the bitmap stream, "bitmap stream" is the bitmap's stream (from SaveToStream), "bitmap trailer" is a trailer identifier which indicates the end of the bitmap entry, and "file trailer" is a unique identifier which indicates the end of the file, and optionally contains the size of the file and a CRC of the file (for error detection). Of course, you'd iterate the "bitmap header"..."bitmap trailer" structure once per bitmap saved to the file.

You can use a TFileStream to read / write this structure. You'll need to write a number of methods which read and interpret each section. You'll also want to create a TBitmap instance each time you encounter a "bitmap header" structure. Here's a quick example of how to implement the "bitmap header"..."bitmap trailer" section:

const
  BITMAP_HEADER = 100;
  BITMAP_TRAILER = 200;

procedure SaveBitmap(Bitmap: TBitmap; Stream: TStream);
var
  Buffer: TMemoryStream;
  Identifier: LongInt;
  Size: LongInt;
begin
  Buffer := TMemoryStream.Create;
  try
    Bitmap.SaveToStream(Buffer);
    Identifier := BITMAP_HEADER;
    Stream.Write(Identifier, SizeOf(Identifier));
    Size := Buffer.Size;
    Stream.Write(Size, SizeOf(Size));
    Buffer.Position := 0;
    Stream.CopyFrom(Buffer, Size);
    Identifier := BITMAP_TRAILER;
    Stream.Write(Identifier, SizeOf(Identifier));
  finally
    Buffer.Free;
  end;
end;

procedure ReadBitmap(Bitmap: TBitmap; Stream: TStream);
var
  Buffer: TMemoryStream;
  Identifier: LongInt;
  Size: LongInt;
begin
  Buffer := TMemoryStream.Create;
  try
    Stream.Read(Identifier, SizeOf(Identifier));
    if Identifier <> BITMAP_HEADER then
      raise Exception.Create('Bitmap header expected');
    Stream.Read(Size, SizeOf(Size));
    Buffer.CopyFrom(Stream, Size);
    Bitmap.LoadFromStream(Buffer);
    Stream.Read(Identifier, SizeOf(Identifier));
    if Identifier <> BITMAP_TRAILER then
      raise Exception.Create('Bitmap trailer expected');
  finally
    Buffer.Free;
  end;
end;

Of course, you'll need to write other methods to read the other file sections, and you'll need to call ReadBitmap the correct number of times (specified in "bitmap count") with a TBitmap instance.

2006. március 23., csütörtök

Simple HTML parsing and painting


Problem/Question/Abstract:

How to do simple HTML parsing and painting

Answer:

This morning a friend asked me how to do a simple HTML parsing. He wanted to implement a hint box with formatting possibilities.

So I developed a very simple procedure which draws the contents of a string to a rectangle on a canvas. It only understands the HTML tags b, i and u, and it moves to the next line when it finds a new line code. (This is not HTML conform, but in this case really useful)

The parsing itself is extremely simple, but you need to work  with pointers and not with strings since they make it more difficult to get each individual character.

The following procedure is just a little example. If you need HTML editing/painting please try out WPTools.

// Draw simple HTML text to any canvas
// 3/16/2000 by Julian Ziersch, http://www.ziersch.com/
// Products: WPTools, WPReporter, WPForm
// wPDF: PDF Export for WPTools

procedure DrawHTML(r: TRect; aCanvas: TCanvas; const text: string);
var
  p: PChar;
  c: Char;
  x, y, w, wc, hc: Integer;
  code: string;
begin
  p := PChar(text);
  x := r.Left;
  y := r.Top;
  hc := aCanvas.TextHeight('Ag');
  if p <> nil then
    while p^ <> #0 do
    begin
      c := p^;
      if c = '<' then
      begin
        code := '';
        inc(p);
        while (p^ <> '>') and (p^ <> #0) do
        begin
          code := code + uppercase(p^);
          inc(p);
        end;
        if code = 'B' then
          aCanvas.Font.Style :=
            aCanvas.Font.Style + [fsBold]
        else if code = 'I' then
          aCanvas.Font.Style :=
            aCanvas.Font.Style + [fsItalic]
        else if code = 'U' then
          aCanvas.Font.Style :=
            aCanvas.Font.Style + [fsUnderline]
        else if code = '/B' then
          aCanvas.Font.Style :=
            aCanvas.Font.Style - [fsBold]
        else if code = '/I' then
          aCanvas.Font.Style :=
            aCanvas.Font.Style - [fsItalic]
        else if code = '/U' then
          aCanvas.Font.Style :=
            aCanvas.Font.Style - [fsUnderline];
      end
      else if c = #10 then
      begin
        x := r.Left;
        inc(y, hc);
      end
      else if c >= #32 then
      begin
        wc := aCanvas.TextWidth(c);
        if x + wc > r.Right then
        begin
          x := r.Left;
          inc(y, hc);
        end;
        if y + hc < r.Bottom then
          aCanvas.TextOut(x, y, c);
        inc(x, wc);
      end;
      if p^ > #0 then
        inc(p);
    end;
end;

2006. március 22., szerda

How to use the Win95 Help (What's this?) button


Problem/Question/Abstract:

How to use the Win95 Help (What's this?) button

Answer:

The help button is supposed to be used only with dialogs, Borderstyle := bsDialog. This is a Microsoft thingy, for a main window you are supposed to provide either a menu item or a speedbutton that does a SendMessage(windowhandle, WM_SYSCOMMAND, SC_CONTEXTHELP, 0) to get the window into the context help mode.

The help button is only available if biMinimize and biMaximize is not set. Furthermore, sometimes you have to override the CreateParams method and set the style accordingly.


interface

type
  TfrmMain = class(TForm)
  public
    procedure CreateParams(var Params: TCreateParams); override;
  end;

implementation

procedure TfrmMain.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.Style := Params.Style or DS_CONTEXTHELP;
  Params.ExStyle := Params.ExStyle or WS_EX_CONTEXTHELP;
end;

2006. március 21., kedd

How to create a non-rectangular TPanel


Problem/Question/Abstract:

How to create a non-rectangular TPanel

Answer:

unit ShapedPanel;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, ExtCtrls;

type
  TShapedPanel = class(TCustomControl)
  private
    { Private declarations }
    FBorderColor: TColor;
    IsLoaded: Boolean;
    FBorderWidth: Integer;
    FRgn, FRgn2: HRGN;
    RgnBrush: TBrush;
    FFIlLColor: TColor;
    procedure SetFillColor(const Value: TColor);
    function GetFillColor: TColor;
    procedure MakeRegion;
    procedure SetBorderColor(Value: TColor);
    procedure WMSize(var Message: TMessage); message WM_SIZE;
  protected
    { Protected declarations }
    procedure Paint; override;
    procedure CreateWnd; override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    { Published declarations }
    property BorderColor: TColor read FBorderColor write SetBorderColor default clBlack;
    property BorderWidth: Integer read FBorderWidth write FBorderWidth default 2;
    property FillColor: TColor read GetFillColor write SetFillColor;
    property Height default 200;
    property Width default 200;
    property OnClick;
    property OnContextPopup;
    property OnDblClick;
    property OnEndDock;
    property OnEndDrag;

    property OnEnter;
    property OnExit;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnResize;
    property OnStartDrag;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('EXS', [TShapedPanel]);
end;

constructor TShapedPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := [csCaptureMouse, csClickEvents, csOpaque, csDoubleClicks];
  Width := 200;
  Height := 200;
  RgnBrush := TBrush.Create;
  RgnBrush.Color := clBlack;
  FFillColor := clWhite;
  IsLoaded := False;
  FBorderWidth := 2;
  FBorderColor := clBlack;
  FRgn := 0;
  FRgn2 := 0;
end;

destructor TShapedPanel.Destroy;
begin
  DeleteObject(FRgn);
  DeleteObject(FRgn2);
  inherited;
end;

procedure TShapedPanel.CreateWnd;
begin
  inherited;
  MakeRegion;
  IsLoaded := True;
  {IsLoaded is to make sure MakeRegion is not called before there is a
  Handle for this control, but it may not be nessary}
end;

procedure TShapedPanel.MakeRegion;
var
  x4, y2: Integer;
  FPoints: array[0..5] of TPoint;
begin
  {I moved the Region creation to this procedure so it can be called for WM_SIZE}
  SetWindowRgn(Handle, 0, False);
  {this clears the window region}
  if FRgn <> 0 then
  begin
    {Make sure to Always DeleteObject for a Region}
    DeleteObject(FRgn);
    DeleteObject(FRgn2);
    FRgn := 0;
    FRgn2 := 0;
  end;
  x4 := Width div 4;
  y2 := Height div 2;
  FPoints[0] := Point(x4, 0);
  FPoints[1] := Point(Width - x4, 0);
  FPoints[2] := Point(Width, y2);
  FPoints[3] := Point(Width - x4, Height);
  FPoints[4] := Point(x4, Height);
  FPoints[5] := Point(0, y2);
  FRgn := CreatePolygonRgn(FPoints, 6, WINDING);
  SetWindowRGN(Handle, FRgn, True);
  FRgn2 := CreatePolygonRgn(FPoints, 6, WINDING);
  {FRgn2 is used for FrameRgn in Paint}
end;

procedure TShapedPanel.WMSize(var Message: TMessage);
var
  TmpClr: TColor;
begin
  inherited;
  if IsLoaded then
  begin
    TmpClr := Canvas.Brush.Color;
    Canvas.Brush.Color := FFillColor;
    MakeRegion;
    FillRgn(Canvas.Handle, FRgn2, Canvas.Brush.Handle);
    Paint;
    Canvas.Brush.Color := TmpClr;
  end;
end;

procedure TShapedPanel.Paint;
var
  TmpClr: TColor;
begin
  inherited;
  if IsLoaded then
  begin
    TmpClr := Canvas.Brush.Color;
    Canvas.Brush.Color := FFillColor;
    MakeRegion;
    FillRgn(Canvas.Handle, FRgn2, Canvas.Brush.Handle);
    FrameRgn(Canvas.Handle, FRgn2, RgnBrush.Handle, FBorderWidth, FBorderWidth);
    Canvas.Brush.Color := TmpClr;
  end;
end;

procedure TShapedPanel.SetBorderColor(Value: TColor);
begin
  if FBorderColor <> Value then
  begin
    FBorderColor := Value;
    RgnBrush.Color := FBorderColor;
    Paint;
  end;
end;

procedure TShapedPanel.SetFillColor(const Value: TColor);
begin
  if FFillColor <> Value then
  begin
    FFillColor := Value;
    Paint;
  end
end;

function TShapedPanel.GetFillColor: TColor;
begin
  Result := FFillColor;
end;

end.

2006. március 20., hétfő

How to randomly select records from a TTable


Problem/Question/Abstract:

How to randomly select records from a TTable

Answer:

procedure TForm1.FormCreate(Sender: TObject);
begin
  randomize; {call only once}
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  table1.first;
  table1.MoveBy(Random(table1.recordcount));
end;

2006. március 19., vasárnap

Retrieving POST data in a TWebBrowser


Problem/Question/Abstract:

How do I get the POST data in a TWebBrowser event?

Answer:

In the BeforeNavigate2 event of TWebBrowser, you receive the PostData and Header data as OleVariant.  If you simply assign the OleVariant type to a string, you may get part of the data or garbage.  

You can convert the OleVariant to String using this function:

function VariantToString(AVar: OleVariant): string;
var
  i: integer;
  V: olevariant;
begin
  Result := '';
  if VarType(AVar) = (varVariant or varByRef) then
    V := Variant(TVarData(AVar).VPointer^)
  else
    V := AVar;

  if VarType(V) = (varByte or varArray) then
  try
    for i := VarArrayLowBound(V, 1) to VarArrayHighBound(V, 1) do
      Result := Result + Chr(Byte(V[i]));
  except;
  end
  else
    Result := V;
end;

2006. március 18., szombat

How to call Windows system dialogs from code


Problem/Question/Abstract:

How to call Windows system dialogs from code

Answer:

{ ... }

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure Shell(sMethod: Integer);
  end;

var
  Form1: TForm1;
  oShell: OleVariant;

implementation

{$R *.DFM}

procedure TForm1.Shell(sMethod: Integer);
begin
  case sMethod of
    0: {Minimizes all windows on the desktop}
      begin
        oShell.MinimizeAll;
        Button1.Tag := Button1.Tag + 1;
      end;
    1: {Displays the Run dialog}
      begin
        oShell.FileRun;
        Button1.Tag := Button1.Tag + 1;
      end;
    2: {Displays the Shut Down Windows dialog}
      begin
        oShell.ShutdownWindows;
        Button1.Tag := Button1.Tag + 1;
      end;
    3: {Displays the Find dialog}
      begin
        oShell.FindFiles;
        Button1.Tag := Button1.Tag + 1;
      end;
    4: {Displays the Date/ Time dialog}
      begin
        oShell.SetTime;
        Button1.Tag := Button1.Tag + 1;
      end;
    5: {Displays the Internet Properties dialog}
      begin
        oShell.ControlPanelItem('INETCPL.cpl');
        Button1.Tag := Button1.Tag + 1;
      end;
    6: {Enables user to select folder from Program Files}
      begin
        oShell.BrowseForFolder(0, 'My Programs', 0, 'C:\Program Files');
        Button1.Tag := Button1.Tag + 1;
      end;
    7: {Displays the Taskbar Properties dialog}
      begin
        oShell.TrayProperties;
        Button1.Tag := Button1.Tag + 1;
      end;
    8: {Un-Minimizes all windows on the desktop}
      begin
        oShell.UndoMinimizeAll;
        Button1.Tag := 0;
      end;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  oShell := CreateOleObject('Shell.Application');
  Shell(Button1.Tag);
  oShell := VarNull;
end;

2006. március 17., péntek

How to move down selected items in a TListBox


Problem/Question/Abstract:

I want to move items up and down a TListBox controlled by the up and down arrows. Has anyone got the code to do this?

Answer:

Here's something to move a selected item down:



procedure TF_MainForm.b_fileDownClick(Sender: TObject);
var
  i: integer;
begin
  i := LB_Files.itemindex;
  if (i = -1) or (i = LB_Files.items.count - 1) then
    Exit;
  LB_Files.Items.Move(LB_Files.itemindex, LB_Files.itemindex + 1);
  LB_Files.itemindex := i + 1;
end;

2006. március 16., csütörtök

How to copy folders from one drive to another


Problem/Question/Abstract:

How to copy folders from one drive to another

Answer:

uses
  ShellAPI;

procedure CopyTree(dir, dest: string);
var
  sfos: TSHFileOpStruct;
begin
  FillChar(sfos, SizeOf(sfos), 0);
  dir := dir + '\*.*'#0;
  dest := dest + '\*.*'#0;
  with sfos do
  begin
    wnd := 0;
    wfunc := FO_COPY;
    pFrom := PChar(dir);
    pTo := PChar(dest);
    fFlags := FOF_ALLOWUNDO or FOF_RENAMEONCOLLISION or FOF_SIMPLEPROGRESS;
    fAnyOperationsAborted := false;
    hNameMappings := nil;
    lpszProgressTitle := nil
  end;
  SHFileOperation(sfos);
end;

2006. március 15., szerda

Get field values of a dataset as comma text


Problem/Question/Abstract:

How to get field values of a dataset as comma text ?
Getting the unique field values (strings of course) as comma text can be a big advantage in populating any TStrings descendant. The following functions implement it with respect to a table and also on TBDEDataset.

Answer:

Getting the unique field values (strings of course) as comma text can be a big advantage if you want to fill in a List box or CheckedListBox or for that matter a PickList of DBGrid.

Here are two functions that will let you get the field values as CommaText.The first one gets it from a table given the databasename ,tablename and field name. The second function retrieves it from a TBDEDataSet given the dataset  and field name. The components used in the functions are created at runtime so you don't require a component to be added to the form per se, but the respective units should be added in the uses clause.

The idea is to use a query to get just the required field values. A for loop is used to concatenate the values with a comma in between. The use of DISTINCT in the SQL ensures that there are no repeated entries.
The second function, which works with a dataset, uses a BatchMove component to move the data to a table and then does the function of creating a commatext string.

The Commatext can be assigned to any TStrings descendant making stuff like

ChecklistBox.Items.CommaText := GetCommaTextFromdb(table.DatabaseName, 'fieldName',
  'Tablename');

possible.

function GetCommaTextFromdb(const Dbname, dbField, Tablename: string): string;
var
  i: integer;
  QryTemp: TQuery;
  sFieldname: string;
begin
  Result := '';
  QryTemp := TQuery.Create(nil);
  with QryTemp do
  begin
    DatabaseName := Dbname;
    SQL.Clear;
    SQL.Add('SELECT DISTINCT ' + dbField + ' FROM ' + Tablename);
    Active := True;
    First;
    for i := 0 to QryTemp.RecordCount - 1 do
    begin
      sFieldname := FieldByName(dbField).AsString;
      if (sFieldname <> '') then
      begin
        Result := Result + '"' + (sFieldname) + '"';
        if i <> (QryTemp.RecordCount - 1) then
          Result := Result + ',';
        Next;
      end;
      Active := False;
    end;
    QryTemp.Free;
  end;

function GetCommaTextFromDataSet(Dataset: TBDEDataSet; dbField: string): string;
var
  i: integer;
  QryTemp: TQuery;
  sFieldname: string;
  BatchMove: TBatchMove;
  TempOutTable: TTable;
begin
  Result := '';
  QryTemp := TQuery.Create(nil);
  BatchMove := TBatchMove.Create(nil);
  TempOutTable := TTable.Create(nil);
  TempOutTable.TableName := 'TempOutTable';

  if Dataset is TQuery then
    QryTemp.DatabaseName := TQuery(Dataset).DatabaseName
  else
    QryTemp.DatabaseName := TTable(Dataset).DatabaseName;

  TempOutTable.DatabaseName := QryTemp.DatabaseName;

  with BatchMove do
  begin
    Mappings.Clear;
    Source := Dataset;
    Destination := TempOutTable;
    Mode := batCopy;
    Execute;
  end;

  with QryTemp do
  begin
    SQL.Clear;
    SQL.Add('SELECT DISTINCT ' + dbField + ' FROM TempOutTable');
    Active := True;
    First;

    for i := 0 to QryTemp.RecordCount - 1 do
    begin
      sFieldname := FieldByName(dbField).AsString;
      if (sFieldname <> '') then
      begin
        Result := Result + '"' + (sFieldname) + '"';
        if i <> (QryTemp.RecordCount - 1) then
          Result := Result + ',';
      end;
      Next;
    end;
    Active := False;
  end;
  TempOutTable.DeleteTable;
  QryTemp.Free;
  BatchMove.Free;
  TempOutTable.Free;
end;

2006. március 14., kedd

Create and print a screen shot of a TForm


Problem/Question/Abstract:

How to create and print a screen shot of a TForm

Answer:

The following details a better way to print the contents of a form, by getting the device independent bits in 256 colors from the form, and using those bits to print the form to the printer.

In addition, a check is made to see if the screen or printer is a palette device, and if so, palette handling for the device is enabled. If the screen device is a palette device, an additional step is taken to fill the bitmap's palette from the system palette, overcoming some buggy video drivers who don't fill the palette in.

Note: Since this code does a screen shot of the form, the form must be the topmost window and the whole from must be viewable when the form shot is made.

unit Prntit;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Image1: TImage;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses
  Printers;

procedure TForm1.Button1Click(Sender: TObject);
var
  dc: HDC;
  isDcPalDevice: BOOL;
  MemDc: HDC;
  MemBitmap: hBitmap;
  OldMemBitmap: hBitmap;
  hDibHeader: THandle;
  pDibHeader: pointer;
  hBits: THandle;
  pBits: pointer;
  ScaleX: Double;
  ScaleY: Double;
  ppal: PLOGPALETTE;
  pal: hPalette;
  Oldpal: hPalette;
  i: integer;
begin
  {Get the screen dc}
  dc := GetDc(0);
  {Create a compatible dc}
  MemDc := CreateCompatibleDc(dc);
  {create a bitmap}
  MemBitmap := CreateCompatibleBitmap(Dc, form1.width, form1.height);
  {select the bitmap into the dc}
  OldMemBitmap := SelectObject(MemDc, MemBitmap);
  {Lets prepare to try a fixup for broken video drivers}
  isDcPalDevice := false;
  if GetDeviceCaps(dc, RASTERCAPS) and RC_PALETTE = RC_PALETTE then
  begin
    GetMem(pPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
    FillChar(pPal^, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)), #0);
    pPal^.palVersion := $300;
    pPal^.palNumEntries := GetSystemPaletteEntries(dc, 0, 256, pPal^.palPalEntry);
    if pPal^.PalNumEntries <> 0 then
    begin
      pal := CreatePalette(pPal^);
      oldPal := SelectPalette(MemDc, Pal, false);
      isDcPalDevice := true
    end
    else
      FreeMem(pPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
  end;
  {copy from the screen to the memdc/ bitmap}
  BitBlt(MemDc, 0, 0, form1.width, form1.height, Dc, form1.left, form1.top, SrcCopy);
  if isDcPalDevice = true then
  begin
    SelectPalette(MemDc, OldPal, false);
    DeleteObject(Pal);
  end;
  {unselect the bitmap}
  SelectObject(MemDc, OldMemBitmap);
  {delete the memory dc}
  DeleteDc(MemDc);
  {Allocate memory for a DIB structure}
  hDibHeader := GlobalAlloc(GHND, sizeof(TBITMAPINFO) + (sizeof(TRGBQUAD) * 256));
  {get a pointer to the alloced memory}
  pDibHeader := GlobalLock(hDibHeader);
  {fill in the dib structure with info on the way we want the DIB}
  FillChar(pDibHeader^, sizeof(TBITMAPINFO) + (sizeof(TRGBQUAD) * 256), #0);
  PBITMAPINFOHEADER(pDibHeader)^.biSize := sizeof(TBITMAPINFOHEADER);
  PBITMAPINFOHEADER(pDibHeader)^.biPlanes := 1;
  PBITMAPINFOHEADER(pDibHeader)^.biBitCount := 8;
  PBITMAPINFOHEADER(pDibHeader)^.biWidth := form1.width;
  PBITMAPINFOHEADER(pDibHeader)^.biHeight := form1.height;
  PBITMAPINFOHEADER(pDibHeader)^.biCompression := BI_RGB;
  {find out how much memory for the bits}
  GetDIBits(dc, MemBitmap, 0, form1.height, nil, TBitmapInfo(pDibHeader^),
    DIB_RGB_COLORS);
  {Alloc memory for the bits}
  hBits := GlobalAlloc(GHND, PBitmapInfoHeader(pDibHeader)^.BiSizeImage);
  {Get a pointer to the bits}
  pBits := GlobalLock(hBits);
  {Call fn again, but this time give us the bits!}
  GetDIBits(dc, MemBitmap, 0, form1.height, pBits, PBitmapInfo(pDibHeader)^,
    DIB_RGB_COLORS);
  {Lets try a fixup for broken video drivers}
  if isDcPalDevice = true then
  begin
    for i := 0 to (pPal^.PalNumEntries - 1) do
    begin
      PBitmapInfo(pDibHeader)^.bmiColors[i].rgbRed := pPal^.palPalEntry[i].peRed;
      PBitmapInfo(pDibHeader)^.bmiColors[i].rgbGreen := pPal^.palPalEntry[i].peGreen;
      PBitmapInfo(pDibHeader)^.bmiColors[i].rgbBlue := pPal^.palPalEntry[i].peBlue;
    end;
    FreeMem(pPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
  end;
  {Release the screen dc}
  ReleaseDc(0, dc);
  {Delete the bitmap}
  DeleteObject(MemBitmap);
  {Start print job}
  Printer.BeginDoc;
  {Scale print size}
  if Printer.PageWidth < Printer.PageHeight then
  begin
    ScaleX := Printer.PageWidth;
    ScaleY := Form1.Height * (Printer.PageWidth / Form1.Width);
  end
  else
  begin
    ScaleX := Form1.Width * (Printer.PageHeight / Form1.Height);
    ScaleY := Printer.PageHeight;
  end;
  {Just in case the printer driver is a palette device}
  isDcPalDevice := false;
  if GetDeviceCaps(Printer.Canvas.Handle, RASTERCAPS) and
    RC_PALETTE = RC_PALETTE then
  begin
    {Create palette from dib}
    GetMem(pPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
    FillChar(pPal^, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)), #0);
    pPal^.palVersion := $300;
    pPal^.palNumEntries := 256;
    for i := 0 to (pPal^.PalNumEntries - 1) do
    begin
      pPal^.palPalEntry[i].peRed := PBitmapInfo(pDibHeader)^.bmiColors[i].rgbRed;
      pPal^.palPalEntry[i].peGreen := PBitmapInfo(pDibHeader)^.bmiColors[i].rgbGreen;
      pPal^.palPalEntry[i].peBlue := PBitmapInfo(pDibHeader)^.bmiColors[i].rgbBlue;
    end;
    pal := CreatePalette(pPal^);
    FreeMem(pPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
    oldPal := SelectPalette(Printer.Canvas.Handle, Pal, false);
    isDcPalDevice := true
  end;
  {send the bits to the printer}
  StretchDiBits(Printer.Canvas.Handle, 0, 0, Round(scaleX), Round(scaleY), 0, 0,
    Form1.Width, Form1.Height, pBits, PBitmapInfo(pDibHeader)^,
    DIB_RGB_COLORS, SRCCOPY);
  {Just in case you printer driver is a palette device}
  if isDcPalDevice = true then
  begin
    SelectPalette(Printer.Canvas.Handle, oldPal, false);
    DeleteObject(Pal);
  end;
  {Clean up allocated memory}
  GlobalUnlock(hBits);
  GlobalFree(hBits);
  GlobalUnlock(hDibHeader);
  GlobalFree(hDibHeader);
  {End the print job}
  Printer.EndDoc;
end;

2006. március 13., hétfő

How to parse a line from a comma-separated file into a record


Problem/Question/Abstract:

How to parse a line from a comma-separated file into a record

Answer:

{ ... }
type
  TRec = record
    lastname, firstname: string[30];
    age: Integer;
    position: string[40];
    salary: Single;
  end;

procedure ParseLine(const Line: string; var rec: TRec);
var
  i, start, field: Integer;

  procedure CopyField(currPos: Integer);
  var
    len, err: Integer;
    temp: string;
  begin
    len := currpos - start;
    if len > 0 then
    begin
      temp := Copy(Line, start, len);
      err := 0;
      with rec do
        case field of
          0: lastname := temp;
          1: firstname := temp;
          2: Val(temp, age, err);
          3: position := temp;
          4: Val(temp, salary, err)
        else
          { too much data in this line, issue error message }
        end;
      if err <> 0 then
      begin
        {issue error message}
      end;
    end
    else
      {no data in this field, issue error message or leave the default }
  end;

begin
  {set defaults for the fields, init variables}
  FillChar(rec, sizeof(rec), 0);
  field := 0; {fields keeps track of which field to fill next}
  start := 1; {fencepost for start of field data}
  for i := 1 to Succ(Length(Line)) do
  begin
    if i > Length(Line) then
    begin
      {copy the last section of the line to the last field}
      CopyField(i);
    end
    else
      {test for separator character} if Line[i] = ',' then
      begin
        {found one, copy data from current fencepost to this separator}
        CopyField(i);
        {advance fencepost to position after separator}
        start := i + 1;
        {next field}
        Inc(field);
      end;
  end;
end;

2006. március 12., vasárnap

Copying files in delphi using streams


Problem/Question/Abstract:

I'd like to be able to copy files in Delphi, but am having trouble figuring out how to do it. I've been using operating system level calls, but don't want to limited by them. Is there a way to do it in Delphi?

Answer:

This is one of those topics that I've gotten asked about frequently enough that I decided it's time to write a short article on how to do it. It's funny that something as basic as this is not as visible as might be expected. It falls into a category that I call, "You gotta know what you're looking for..." Essentially, it means that the technique may not be hard to implement, it's just hard to find. In any case, once you know how to do it, it's not that difficult at all.

There are actually a number of ways to copy files. One way is to use untyped files along with BlockRead and BlockWrite. This also entails the use of an intermediary buffer. It works, but it can be a bit unwieldy, especially for novices. An easier way to accomplish file copying in Delphi is to use streams. As the term implies, a stream is sequential stream of data. When copying a file, you stream the file into a buffer, then stream buffer out to another file. Pretty simple in concept. Now in Delphi there are several types of streams which descend from the abstract base class TStream. I encourage you to look them up in the online help since they are beyond the scope of this discussion. But for our purposes, the descendant class that we're interested in is called TFileStream. This class allows applications to read from and write to files on disk. For simplicity's sake, I won't be going into the various intricacies of the class; again, encouraging you to study the online help. Or better yet, Ray Lischner's Book Secrets of Delphi 2 has a great discussion about streams as well (don't worry, the material applies to Delphi 3).

Quick and Dirty File Copying

The easiest method of copying a file with streams is called stream to stream copying. Essentially, this method involves creating a stream for the source file, and creating one for the destination file. Once that's done, it's a simple matter of copying the contents of the source stream to the destination stream. Listing 1 below shows a procedure that encapsulates stream to stream copying:

{Quick and dirty stream copy}

procedure FileCopy(const FSrc, FDst: string);
var
  sStream,
    dStream: TFileStream;
begin
  sStream := TFileStream.Create(FSrc, fmOpenRead);
  try
    dStream := TFileStream.Create(FDst, fmCreate);
    try
      {Forget about block reads and writes, just copy
       the whole darn thing.}
      dStream.CopyFrom(sStream, 0);
    finally
      dStream.Free;
    end;
  finally
    sStream.Free;
  end;
end;

Undoubtedly, you can get a lot more sophisticated with this. But for now, we'll leave it at this...

2006. március 11., szombat

Changing properties for all components of a certain type


Problem/Question/Abstract:

Changing properties for all components of a certain type

Answer:

To change the font color of all the labels of a form to a certain color, call the following procedure. In the call itself, you have to replace NewColor with an existing color, e.g. SetLabelsFontColor(clRed) sets all the labels' font color to red.

procedure TForm1.SetLabelsFontColor(NewColor: TColor);
var
  i: Integer;
begin
  for i := 0 to ComponentCount - 1 do
    if Components[i] is TLabel then
      TLabel(Components[i]).Font.Color := NewColor;
end;

Of course, you can use this technique to change other properties of other components. To change the color of all edits, the code would be:

procedure TForm1.SetEditsColor(NewColor: TColor);
var
  i: Integer;
begin
  for i := 0 to ComponentCount - 1 do
    if Components[i] is TEdit then
      TEdit(Components[i]).Color := NewColor;
end;

2006. március 10., péntek

How to create gradient color schemes


Problem/Question/Abstract:

How to create gradient color schemes

Answer:

Just cut and paste the routines below into a unit somewhere and make the function declarations at the top of your unit.

You can use GetGradientColor2 to get a color that is somewhere between two other colors. For example, to get the color that is 50% between Red and Blue, do this:


var
  MyColor: TColor;
begin
  R1 := 255;
  G1 := 0;
  B1 := 0;
  R2 := 0;
  G2 := 0;
  B2 := 0;
  Percent := 0.5;
  MyNewColor := GetGradientColor2(R1, G1, B1, R2, G2, B2, Percent);


You could put percent in a loop from 0 to 1, and get all the colors as a nice gradient.

Function GetGradientColor3 works in a similar manner, except that you can do a gradient between 3 colors, such as between red to yellow to blue. This can help prevent the colors from loosing intensity when you go between say blue and red, where the purple would otherwise be darker.



function ColorFromRGB(Red, Green, Blue: Integer): Integer;
{Returns the color made up of the red, green, and blue components. Red, Green, and Blue can
be from 0 to 255.}
begin
  {Convert Red, Green, and Blue values to color.}
  Result := Red + Green * 256 + Blue * 65536;
end;

function GetPigmentBetween(P1, P2, Percent: Double): Integer;
{Returns a number that is Percent of the way between P1 and P2}
begin
  {Find the number between P1 and P2}
  Result := Round(((P2 - P1) * Percent) + P1);
  {Make sure we are within bounds for color.}
  if Result > 255 then
    Result := 255;
  if Result < 0 then
    Result := 0;
end;

function GetGradientColor2(R1, G1, B1, R2, G2, B2, Percent: Double): Integer;
{Gets a color that is inbetween the colors defined by (R1,G1,B1) and (R2,G2,B2)
Percent ranges from 0 to 1.0 (i.e. 0.5 = 50%)
If percent =0   then the color of (R1,G1,B1) is returned
If Percent =1   then the color of (R2,G2,B2) is returned
if Percent is somewhere inbetween, then an inbetween color is returned.}
var
  NewRed, NewGreen, NewBlue: Integer;
begin
  {Validate input data in case it is off by a few thousanths.}
  if Percent > 1 then
    Percent := 1;
  if Percent < 0 then
    Percent := 0;
  {Calculate Red, green, and blue components for the new color.}
  NewRed := GetPigmentBetween(R1, R2, Percent);
  NewGreen := GetPigmentBetween(G1, G2, Percent);
  NewBlue := GetPigmentBetween(B1, B2, Percent);
  {Convert RGB to color}
  Result := ColorFromRGB(NewRed, NewGreen, NewBlue);
end;

function GetGradientColor3(R1, G1, B1, R2, G2, B2, R3, G3, B3, Percent: Double): Integer;
{Gets a color that is inbetween the color spread defined (R1,G1,B1), (R2,G2,B2) and (R3,G3,B3).
This is similar to GetGradientColor2, except that it allows you to specify 3 colors instead of 2.}
begin
  {Use GetGradient2 to do most the work}
  if Percent < 0.5 then
    Result := GetGradientColor2(R1, G1, B1, R2, G2, B2, Percent * 2)
  else
    Result := GetGradientColor2(R2, G2, B2, R3, G3, B3, (Percent - 0.5) * 2);
end;

2006. március 9., csütörtök

How to specify a wildcard character for date parameters


Problem/Question/Abstract:

I'd like to do something like this:

select * from Person where Surname like '%'

but with DOB instead. Is there a way to specify a wildcard character for date parameters? I keep getting type mismatch errors.

Answer:

The LIKE predicate can only be used with CHAR (or VARCHAR) type values. To use LIKE with a value of any other data type, you would need to use the SQL function CAST to convert the value to CHAR type. For example, converting a DATE type column to CHAR(10):

SELECT *
FROM Person
WHERE(CAST(DOB as CHAR(10))LIKE "%94")

However, if this is performed on a TIMESTAMP type column, the time portion of the column's value can interfere with this. Convert the column first to DATE and then that to CHAR(10).

SELECT *
FROM Person
WHERE(CAST(CAST(DOB as DATE) as CHAR(10))LIKE "%94")

But SQL provides a function specifically for extracting a single element of a DATE or TIMESTAMP value for making such partial-value comparisons: EXTRACT. The EXTRACT function can be applied to a DATE or TIMESTAMP value to retrieve the year, month, or day portion of the date. For example:

SELECT *
FROM Person
WHERE(EXTRACT(YEAR FROM DOB) = 1994)

Note: all of the above is common to SQL-92. These operations are not specific to local SQL.

2006. március 8., szerda

Hyphenation - Dividing Spanish words in syllables


Problem/Question/Abstract:

A simple hyphenation algorithm to syllabicate Spanish words.

Answer:

Sometimes we need to display or print a text, and we'd like to hyphenate long words that don't fit at the end of a line, to prevent them from falling entirely into the next line leaving too much space unused.

The main problem that arises is how to divide a Spanish word in syllables. If your are interested in syllabication for English words, read the note at the end of this article.

procedure Syllabify(Syllables: TStringList; s: string);
const
  Consonants = ['b', 'B', 'c', 'C', 'd', 'D', 'f', 'F', 'g', 'G',
    'h', 'H', 'j', 'J', 'k', 'K', 'l', 'L', 'm', 'M', 'n', 'N',
    '�', '�', 'p', 'P', 'q', 'Q', 'r', 'R', 's', 'S', 't', 'T',
    'v', 'V', 'w', 'W', 'x', 'X', 'y', 'Y', 'z', 'Z'];
  StrongVowels = ['a', 'A', '�', '�', 'e', 'E', '�', '�',
    '�', '�', 'o', '�', 'O', '�', '�', '�'];
  WeakVowels = ['i', 'I', 'u', 'U', '�', '�'];
  Vowels = StrongVowels + WeakVowels;
  Letters = Vowels + Consonants;
var
  i, j, n, m, hyphen: integer;
begin
  j := 2;
  s := #0 + s + #0;
  n := Length(s) - 1;
  i := 2;
  Syllables.Clear;
  while i <= n do
  begin
    hyphen := 0; // Do not hyphenate
    if s[i] in Consonants then
    begin
      if s[i + 1] in Vowels then
      begin
        if s[i - 1] in Vowels then
          hyphen := 1;
      end
      else if (s[i + 1] in Consonants) and
        (s[i - 1] in Vowels) then
      begin
        if s[i + 1] in ['r', 'R'] then
        begin
          if s[i] in ['b', 'B', 'c', 'C', 'd', 'D', 'f', 'F', 'g',
            'G', 'k', 'K', 'p', 'P', 'r', 'R', 't', 'T', 'v', 'V'] then
            hyphen := 1
          else
            hyphen := 2;
        end
        else if s[i + 1] in ['l', 'L'] then
        begin
          if s[i] in ['b', 'B', 'c', 'C', 'd', 'D', 'f', 'F', 'g',
            'G', 'k', 'K', 'l', 'L', 'p', 'P', 't', 'T', 'v', 'V'] then
            hyphen := 1
          else
            hyphen := 2;
        end
        else if s[i + 1] in ['h', 'H'] then
        begin
          if s[i] in ['c', 'C', 's', 'S', 'p', 'P'] then
            hyphen := 1
          else
            hyphen := 2;
        end
        else
          hyphen := 2;
      end;
    end
    else if s[i] in StrongVowels then
    begin
      if (s[i - 1] in StrongVowels) then
        hyphen := 1
    end
    else if s[i] = '-' then
    begin
      Syllables.Add(Copy(s, j, i - j));
      Syllables.Add('-');
      inc(i);
      j := i;
    end;
    if hyphen = 1 then
    begin // Hyphenate here
      Syllables.Add(Copy(s, j, i - j));
      j := i;
    end
    else if hyphen = 2 then
    begin // Hyphenate after
      inc(i);
      Syllables.Add(Copy(s, j, i - j));
      j := i;
    end;
    inc(i);
  end;
  m := Syllables.Count - 1;
  if (j = n) and (m >= 0) and (s[n] in Consonants) then
    Syllables[m] := Syllables[m] + s[n] // Last letter
  else
    Syllables.Add(Copy(s, j, n - j + 1)); // Last syllable
end;

To test the procedure yon can drop a Textbox and a Label on a form and in the Change event of the Textbox write:

procedure TForm1.Edit1Change(Sender: TObject);
var
  Syllables: TStringList;
begin
  Syllables := TStringList.Create;
  try
    Syllabify(Syllables, Edit1.Text);
    Label1.Caption := StringReplace(Trim(Syllables.Text),
      #13#10, '-', [rfReplaceAll]);
  finally
    Syllables.Free;
  end;
end;

Now that we have a syllabication procedure, we have to note that we can't hyphenate a word in any syllable break. It is usually correct and/or desirable to join small syllables at the left and/or right sides of a word to guarantee for example that there are at least two syllables on either side of the word when it gets hyphenated, or -like in the following example- to make sure that at least we have four characters in either side:

procedure ApplyRules(Syllables: TStringList);
// Guarantee there are at least four letters in the left
// and right parts of the word
begin
  with Syllables do
  begin
    if Count = 1 then
      exit;
    while Count > 1 do
    begin
      if Length(Strings[0]) >= 4 then
        break;
      Strings[0] := Strings[0] + Strings[1];
      Delete(1);
    end;
    while Syllables.Count > 1 do
    begin
      if Length(Strings[Count - 1]) >= 4 then
        break;
      Strings[Count - 2] := Strings[Count - 2]
        + Strings[Count - 1];
      Delete(Count - 1);
    end;
  end;
end;

Finally, it comes the time to parse the text separating the lines of a paragraph determining which words should be hyphenated. The following example does that with a text to be displayed in a Memo:

procedure Hyphenate(Memo: TMemo; OriginalText: TStrings);
var
  paragraph, i, j, k, m, n, MaxLineWidth: integer;
  s, line: string;
  Bitmap: TBitmap;
  Canvas: TCanvas;
  Syllables: TStringList;
begin
  Syllables := TStringList.Create;
  try
    // We need a canvas to use its TextWidth method to get the width
    // of the text to see if it fits in the client area or not. The
    // TMemo class doesn't have a Canvas property, so we have to
    // create one of our own.
    Bitmap := TBitmap.Create;
    Canvas := Bitmap.Canvas;
    try
      Canvas.Font := Memo.Font;
      MaxLineWidth := Memo.ClientWidth - 6; // Maximum width
      Memo.Lines.Clear;
      for paragraph := 0 to OriginalText.Count - 1 do
      begin
        // For each paragraph
        s := OriginalText[paragraph]; // Get the original paragraph
        // Get the lines in which we have to break the paragraph
        while Canvas.TextWidth(s) > MaxLineWidth do
        begin
          // First we find (in "j") the index of the start of the
          // first word that doesn't fit (the one to hyphenate)
          j := 1;
          n := Length(s);
          i := 2;
          while i <= n do
          begin
            if (s[i - 1] = ' ') and (s[i] <> ' ') then
              j := i; // last beginning of a word
            if Canvas.TextWidth(Copy(s, 1, i)) > MaxLineWidth then
              break; // reached a width that doesn't fit
            inc(i);
          end;
          // Where does the break occurs?
          if s[i] = ' ' then
          begin
            // Great! We break on a space
            Memo.Lines.Add(Copy(s, 1, i - 1)); // Add the line
            s := Copy(s, i + 1, n - i); // Remove the line
          end
          else
          begin
            // We break somewhere in a word. Now, we find (in "k")
            // the first space after the word (k)
            k := j + 1;
            while (k <= n) and (s[k] <> ' ') do
              inc(k);
            // Divide the word in Syllables
            Syllabify(Syllables, Copy(s, j, k - j));
            ApplyRules(Syllables);
            // Check (in "m") how many syllables fit
            m := 0;
            Line := Copy(s, 1, j - 1);
            while Canvas.TextWidth(Line + Syllables[m] + '-')
              <= MaxLineWidth do
            begin
              Line := Line + Syllables[m];
              inc(m);
            end;
            if (m <> 0) and (Syllables[m - 1] <> '-') then
            begin
              // Hyphenate
              Line := Line + '-';
              j := Length(Line);
              if Syllables[m] = '-' then
                inc(j);
            end;
            Memo.Lines.Add(Line); // Add the line
            s := Copy(s, j, n - j + 1); // Remove the line
          end;
        end;
        Memo.Lines.Add(s); // Add the last line (it fits)
      end;
    finally
      Bitmap.Free;
    end;
  finally
    Syllables.Free;
  end;
end;

To test the procedure, drop a Memo component on a form, align it for example to the top of the form (Align = alTop) and write the following code in the OnResize event of the form:

procedure TForm1.FormResize(Sender: TObject);
var
  OriginalText: TStringList;
begin
  OriginalText := TStringList.Create;
  try
    OriginalText.Add('Si se ha preguntado c�mo hacen los '
      + 'programas procesamiento de textos para dividir palabras '
      + 'con de guiones al final de una l�nea, he aqu� un '
      + 'ejemplo sencillo (en comparaci�n con los que usan las '
      + 'aplicaciones de procesamiento de textos).');
    OriginalText.Add('Este es un segundo p�rrafo que se provee '
      + 'con fines de ejemplo.');
    Hyphenate(Memo1, OriginalText);
  finally
    OriginalText.Free;
  end;
end;

NOTE:

English words are hyphenated phonetically, so the process would have two phases:

produce a phonetic representation of the word using pronunciation rules; and
perform the hyphenation of the phonetic representation using hyphenation rules (and parallelly apply that to the original word).

There are rules for both things, and also exceptions, so a small exceptions dictionary may be needed. Of course, it's all easier said than done. I realize it is somewhat complex, but I still believe it is possible to syllabicate English words algorithmically.

Copyright (c) 2001 Ernesto De Spirito
Visit: http://www.latiumsoftware.com/delphi-newsletter.php

2006. március 7., kedd

Get a table/SP list using ADO


Problem/Question/Abstract:

How can I receive the list of table names in ADO?

Answer:

If you needs to retrieve the list of available tables in ADO, you can call the GetTableNames method of your TADOConnection component:

{we want to receive the tables including the system tables}
boolSystemTables := True;
yourADOConnection.GetTableNames(yourListBox.Items, boolSystemTables);

if you want to recieve the names of stored procedures, you must call the GetProcedureNames method:

yourADOConnection.GetProcedureNames((yourListBox.Items);

2006. március 6., hétfő

How to iterate through a parent's child controls and enable/ disable them


Problem/Question/Abstract:

I have a question about disabling containers. In my case I have a panel containing a button. When disabling the panel the button is disabled, but not greyed. Why is it not greyed?

Answer:

You have several options. Perhaps the easiest is to use this small routine which iterates through a parent's children controls, enabling / disabling each in turn:


procedure EnableContainer(Parent: TWinControl; AEnabled: Boolean);
var
  I: Integer;
begin
  for I := 0 to Parent.ControlCount - 1 do
    Parent.Controls[I].Enabled := AEnabled;
  Parent.Enabled := AEnabled;
end;


So instead of doing this:


Panel1.Enabled := False;


do this instead:


EnableContainer(Panel1, False);

2006. március 5., vasárnap

MDI application without annoying ScrollBars


Problem/Question/Abstract:

I've been trying to create a MDI form without those annoying scrollbars when a child form is moved outside main form area and I couldn't find an easy way. Setting the scrollbars to visible := false won't work!
So, I found an example on a newsgroup... yeah! Here I show how to do it.

Answer:

It's a two step proccess.

Step one : put the code below inside the OnCreate event of the main form.

if ClientHandle <> 0 then
begin
  if (not (GetWindowLong(ClientHandle, GWL_USERDATA) <> 0)) then
  begin
    SetWindowLong(
      ClientHandle,
      GWL_USERDATA,
      SetWindowLong(ClientHandle, GWL_WNDPROC, integer
      (@ClientWindowProc))
      );
  end;
end;

Step two: Put this standalone function inside the unit that contains the main form, before the OnCreate event (once OnCreate references to this function).

function ClientWindowProc(wnd: HWND; msg: Cardinal; wparam, lparam: Integer): Integer;
  stdcall;
var
  f: Pointer;
begin
  f := Pointer(GetWindowLong(wnd, GWL_USERDATA));
  case msg of
    WM_NCCALCSIZE:
      begin
        if (
          GetWindowLong(wnd, GWL_STYLE) and
          (WS_HSCROLL or WS_VSCROLL)) <> 0 then
          SetWindowLong(
            wnd,
            GWL_STYLE,
            GetWindowLong(wnd, GWL_STYLE) and not
            (WS_HSCROLL or WS_VSCROLL)
            );
      end;
  end;
  Result := CallWindowProc(f, wnd, msg, wparam, lparam);
end;

That's it!!!

The code was originally posted by Peter Below in a newsgroup (borland.public.delphi.objectpascal). I've made some little changes.

2006. március 4., szombat

Display a modal form stored in a DLL


Problem/Question/Abstract:

Anyone know how to display a modal form in a DLL file? Whenever I try, the modal form shows up on the taskbar and stays on top, even when you switch to other windows.

Answer:

procedure ShowDLLForm(appHandle: HWND); stdcall;
begin
  if appHandle = 0 then
    apphandle := GetActiveWindow;
  application.handle := appHandle;
  try
    with TDLLForm.Create(Application) do
    try
      ShowModal
    finally
      free;
    end
  except
    on E: Exception do
      application.HandleException(E);
  end;
  application.handle := 0;
end;

2006. március 3., péntek

How to create a panel which resizes itself and all components on it at runtime


Problem/Question/Abstract:

How to create a panel which resizes itself and all components on it at runtime

Answer:

Here's the source code for a resizable panel. Give the panel an align property of alClient, throw some controls on it, and watch them resize at run time when you resize the form. There is some code that prohibits resizing during design time, but this can be taken out.

unit Elastic;

interface

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

type
  TElasticPanel = class(TPanel)
  private
    FHorz, FVert: boolean;
    nOldWidth, nOldHeight: integer;
    bResized: boolean;
  protected
    procedure WMSize(var message: TWMSize); message WM_SIZE;
  public
    nCount: integer;
    constructor Create(AOwner: TComponent); override;
  published
    property ElasticHorizontal: boolean read FHorz write FHorz default True;
    property ElasticVertical: boolean read FVert write FVert default True;
  end;

procedure Register;

implementation

constructor TElasticPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FHorz := True;
  FVert := True;
  nOldWidth := Width;
  nOldHeight := Height;
  bResized := False;
end;

procedure TElasticPanel.WMSize(var message: TWMSize);
var
  bResize: boolean;
  xRatio: real;
  i: integer;
  ctl: TWinControl;
begin
  Inc(nCount);
  if Align = alNone then
    bResize := TRUE
  else
    bResize := bResized;
  if not (csDesigning in ComponentState) and bResize then
  begin
    if FHorz then
    begin
      xRatio := Width / nOldWidth;
      for i := 0 to ControlCount - 1 do
      begin
        ctl := TWinControl(Controls[i]);
        ctl.Left := Round(ctl.Left * xRatio);
        ctl.Width := Round(ctl.Width * xRatio);
      end;
    end;
    if FVert then
    begin
      xRatio := Height / nOldHeight;
      for i := 0 to ControlCount - 1 do
      begin
        ctl := TWinControl(Controls[i]);
        ctl.Top := Round(ctl.Top * xRatio);
        ctl.Height := Round(ctl.Height * xRatio);
      end;
    end;
  end
  else
  begin
    nOldWidth := Width;
    nOldHeight := Height;
  end;
  bResized := TRUE;
  nOldWidth := Width;
  nOldHeight := Height;
end;

procedure Register;
begin
  RegisterComponents('Additional', [TElasticPanel]);
end;

end.

2006. március 2., csütörtök

How to calculate the week from a given date


Problem/Question/Abstract:

How to calculate the week from a given date

Answer:

The code below tells you which week the specified date is in, and also the corresponding day of the week. The date format it handles is "06/25/1996". You have to create a form named "Forma" with a TEdit named "Edit1", four labels and a button named "GetWeekBtn".


unit Forma;

interface

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

type
  TForma1 = class(TForm)
    Edit1: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    GetWeekBtn: TButton;
    Label4: TLabel;
    procedure GetWeekBtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    function HowManyDays(pYear, pMonth, pDay: word): Integer;
  public
    { Public declarations }
  end;

var
  Forma1: TForma1;

implementation

{$R *.DFM}

uses
  Inifiles;

procedure TForma1.FormCreate(Sender: TObject);
var
  WinIni: TInifile;
begin
  WinIni := TIniFile.Create('WIN.INI');
  WinIni.WriteString('intl', 'sShortDate', 'MM/dd/yyyy');
  WinIni.Free;
end;

function TForma1.HowManyDays(pYear, pMonth, pDay: Word): Integer;
var
  Sum: Integer;
  pYearAux: Word;
begin
  Sum := 0;
  if pMonth > 1 then
    Sum := Sum + 31;
  if pMonth > 2 then
    Sum := Sum + 28;
  if pMonth > 3 then
    Sum := Sum + 31;
  if pMonth > 4 then
    Sum := Sum + 30;
  if pMonth > 5 then
    Sum := Sum + 31;
  if pMonth > 6 then
    Sum := Sum + 30;
  if pMonth > 7 then
    Sum := Sum + 31;
  if pMonth > 8 then
    Sum := Sum + 31;
  if pMonth > 9 then
    Sum := Sum + 30;
  if pMonth > 10 then
    Sum := Sum + 31;
  if pMonth > 11 then
    Sum := Sum + 30;
  Sum := Sum + pDay;
  if ((pYear - (pYear div 4) * 4) = 30) and (pMonth > 2) then
    inc(Sum);
  HowManyDays := Sum;
end;

procedure TForma1.GetWeekBtnClick(Sender: TObject);
var
  ADate: TDateTime;
  EditAux: string;
  Week, year, month, day: Word;
begin
  EditAux := Edit1.Text;
  ADate := StrToDate(EditAux);
  Label1.Caption := DateToStr(ADate);
  DecodeDate(Adate, Year, Month, Day);
  case DayOfWeek(ADate) of
    1: Label4.Caption := 'Sunday';
    2: Label4.Caption := 'Monday';
    3: Label4.Caption := 'Tuesday';
    4: Label4.Caption := 'Wednesday';
    5: Label4.Caption := 'Thursday';
    6: Label4.Caption := 'Friday';
    7: Label4.Caption := 'Saturday';
  end;
  Week := (HowManyDays(year, month, day) div 7) + 1;
  Label3.Caption := 'Week No. ' + IntToStr(Week);
end;

end.

2006. március 1., szerda

Fill a TListView with all files of a given directory along with the system icons


Problem/Question/Abstract:

How to fill a TListView with all files of a given directory along with the system icons

Answer:

Here's some code from a recent project. FileList is a TListView. ScanDir() is a function from our product - it's basically a procedure that fills a TStrings object with a list of files matching a mask. You can ignore the TDirInfo(Node.Data) stuff - it's a small class that holds info on each folder as displayed in a TTreeView.TTreeNode.

This routine builds a TListView that's pretty much like the right pane in Windows Explorer, in that it supports both the list and report view, and displays the file type, size, and modified date in columns in report view.

{Gets files in a folder and displays them in the ListView}

procedure TExplorer.GetFilesInFolder(Node: TTreeNode);
var
  SL: TStringList;
  i: Integer;
  Dat: TDirInfo;
  AllSel: Boolean;
  NewItem: TListItem;
  FI: TSHFileInfo;
  Dt: TDateTime;
  TypeDesc: string;
begin
  if not Assigned(Node) then
    Exit;
  SL := TStringList.Create;
  if Screen.Cursor <> crHourglass then
    Screen.Cursor := crHourglass;
  try
    {Need easier access to Node.Data than TDirInfo(Node.Data) typecasts}
                {Grab a local reference to the pointer}
                Dat := TDirInfo(Node.Data);
    {Get files in this folder, but don't include subfolder files}
    ScanDir(Dat.FullPath, '*.*', SL, False);
    SL.Sorted := True;
    {See if this folder has already been fully selected.
                If so, we don't need to add it to the Folders list or increment selection count
                or bytes}
    AllSel := (Folders.IndexOf(Dat.FullPath) > -1) or (Dat.Status = dsFull);
    {Remove stuff that was previously displayed}
    FileList.Items.BeginUpdate;
    FileList.Items.Clear;
    {Is this an empty folder?}
    if SL.Count = 0 then
    begin
      FileList.SmallImages := StateImages;
      NewItem := FileList.Items.Add;
      NewItem.Caption := ' No files ';
      NewItem.ImageIndex := NoFilesIndex;
      FileList.Enabled := False;
      Exit;
    end;
    FileList.SmallImages := SysImages;
    {We have files. Add each one to the ListView}
    for i := 0 to SL.Count - 1 do
    begin
      {Create a new TListItem}
      NewItem := FileList.Items.Add;
      {Assign the filename portion}
      NewItem.Caption := ExtractFileName(SL[i]);
      FillChar(FI, SizeOf(TSHFileInfo), #0);
      {Get the icon for display, as well as the file type, with one function call.        
                        Note the flags:
      SHGFI_SMALLICON - we want the small icon
      SHGFI_SYSICONINDEX - we want the index into the system imagelist
      SHGFI_TYPENAME - we want the file type description if there is one}
      SHGetFileInfo(PChar(SL[i]), 0, FI, SizeOf(FI), SHGFI_ICON or SHGFI_SMALLICON
        or SHGFI_SYSICONINDEX or SHGFI_TYPENAME);
      {The subitems are only displayed in the 'detail' view, but they have
                  to be there all the time. See if Windows knows what type file this is}
      TypeDesc := FI.szTypeName;
      if TypeDesc = '' then
        {Windows doesn't know - handle like Explorer does}
        TypeDesc := Upper(ExtractFileExt(SL[i])) + ' file';
      {Delete the period if we need to}
      if Length(TypeDesc) > 1 then
      begin
        if TypeDesc[1] = '.' then
          Delete(TypeDesc, 1, 1);
      end;
      {Display the file type description}
      NewItem.SubItems.Add(TypeDesc);
      {Here's the 'Size' column ...}
      NewItem.SubItems.Add(Comma([GetFileSize(SL[i])], False));
      {Assign the system imagelist index to this item}
      NewItem.ImageIndex := FI.iIcon;
      {Grab the file's time and date stamp and convert to Delphi TDateTime}
      Dt := FileDateToDateTime(FileAge(SL[i]));
      {Add the date column}
      NewItem.SubItems.Add(DateToStr(Dt));
      {Add the time column}
      NewItem.SubItems.Add(FormatDateTime('hh:nn:ss ampm', Dt));
      {If folder was fully selected, or this file was selected in a
                        previous visit to this folder, check it}
      if AllSel or (Files.IndexOf(SL[i]) > -1) then
        NewItem.Checked := True;
    end;
    FileList.Enabled := True;
  finally
    SL.Free;
    FileList.Items.EndUpdate;
    if Screen.Cursor <> crDefault then
      Screen.Cursor := crDefault;
  end;
end;