2008. február 29., péntek

Change the font of all controls on a form at runtime


Problem/Question/Abstract:

How to change the font of all controls on a form at runtime

Answer:

By default all controls have ParentFont = true, so if you did not change that for specific controls you could just change the forms Font property, e.g. in code attached to the Screen.OnActiveFormChange event. If you cannot rely on all controls having Parentfont = true you would have to loop over all controls on the form and set the font property for each or at least for those that have ParentFont set to false. You can use the routines from unit TypInfo for that, they allow you to access published properties by name. The code, again sitting in a handler for Screen.onActiveFormChange, would be something like this:

ModifyFontsFor(Screen.ActiveControl);

where

procedure ModifyFontsFor(ctrl: TWinControl);

  procedure ModifyFont(ctrl: TControl);
  var
    f: TFont;
  begin
    if IsPublishedProp(ctrl, 'Parentfont') and (GetOrdProp(ctrl, 'Parentfont') =
      Ord(false)) and IsPublishedProp(ctrl, 'font') then
    begin
      f := TFont(GetObjectProp(ctrl, 'font', TFont));
      f.Name := 'Symbol';
    end;
  end;

var
  i: Integer;
begin
  ModifyFont(ctrl);
  for i := 0 to ctrl.controlcount - 1 do
    if ctrl.controls[i] is TWinControl then
      ModifyFontsfor(TWinControl(ctrl.controls[i]))
    else
      Modifyfont(ctrl.controls[i]);
end;

Remember to add TypInfo to your uses clause.

2008. február 28., csütörtök

How to use antialising


Problem/Question/Abstract:

You want to use the Antialising effect in your application, but you don't know how.

Answer:

First you have to know how Antialising work. For every pixel in the canvas and it's neighbors must be create the color difference between both color values.  That's all. You just have to go through all pixels of your canvas and do this.

With the following procedure you create your custom Antialising effect. The procedure needs the grade (Percent) of the Antialising effect. If Percent is 0, there will be no effekt, up to 100 there will be a more stronger effect.


procedure Antialising(C: TCanvas; Rect: TRect; Percent: Integer);
var
  l, p: Integer;
  R, G, B: Integer;
  R1, R2, G1, G2, B1, B2: Byte;
begin
  with c do
  begin
    for l := Rect.top to Rect.Bottom do
    begin
      for p := Rect.left to Rect.right do
      begin
        R1 := GetRValue(Pixels[p, l]);
        G1 := GetGValue(Pixels[p, l]);
        B1 := GetBValue(Pixels[p, l]);

        R2 := GetRValue(Pixels[p - 1, l]);
        G2 := GetGValue(Pixels[p - 1, l]);
        B2 := GetBValue(Pixels[p - 1, l]);

        if (R1 <> R2) or (G1 <> G2) or (B1 <> B2) then
        begin
          R := Round(R1 + (R2 - R1) * 50 / (Percent + 50));
          G := Round(G1 + (G2 - G1) * 50 / (Percent + 50));
          B := Round(B1 + (B2 - B1) * 50 / (Percent + 50));
          Pixels[p - 1, l] := RGB(R, G, B);
        end;

        R2 := GetRValue(Pixels[p + 1, l]);
        G2 := GetGValue(Pixels[p + 1, l]);
        B2 := GetBValue(Pixels[p + 1, l]);

        if (R1 <> R2) or (G1 <> G2) or (B1 <> B2) then
        begin
          R := Round(R1 + (R2 - R1) * 50 / (Percent + 50));
          G := Round(G1 + (G2 - G1) * 50 / (Percent + 50));
          B := Round(B1 + (B2 - B1) * 50 / (Percent + 50));
          Pixels[p + 1, l] := RGB(R, G, B);
        end;

        R2 := GetRValue(Pixels[p, l - 1]);
        G2 := GetGValue(Pixels[p, l - 1]);
        B2 := GetBValue(Pixels[p, l - 1]);

        if (R1 <> R2) or (G1 <> G2) or (B1 <> B2) then
        begin
          R := Round(R1 + (R2 - R1) * 50 / (Percent + 50));
          G := Round(G1 + (G2 - G1) * 50 / (Percent + 50));
          B := Round(B1 + (B2 - B1) * 50 / (Percent + 50));
          Pixels[p, l - 1] := RGB(R, G, B);
        end;

        R2 := GetRValue(Pixels[p, l + 1]);
        G2 := GetGValue(Pixels[p, l + 1]);
        B2 := GetBValue(Pixels[p, l + 1]);

        if (R1 <> R2) or (G1 <> G2) or (B1 <> B2) then
        begin
          R := Round(R1 + (R2 - R1) * 50 / (Percent + 50));
          G := Round(G1 + (G2 - G1) * 50 / (Percent + 50));
          B := Round(B1 + (B2 - B1) * 50 / (Percent + 50));
          Pixels[p, l + 1] := RGB(R, G, B);
        end;
      end;
    end;
  end;
end;


Note: There must be some lines or something else on the canvas, otherwise there is no effect.

2008. február 27., szerda

Check if Delphi is running


Problem/Question/Abstract:

How to check if Delphi is running

Answer:

function DelphiRunning: Boolean;
var
  H1, H2, H3, H4: HWnd;
const
  A1: array[0..12] of char = \ 'TApplication\'#0;
  A2: array[0..15] of char = \ 'TAlignPalette\'#0;
  A3: array[0..18] of char = \ 'TPropertyInspector\'#0;
  A4: array[0..11] of char = \ 'TAppBuilder\'#0;
  T1: array[0..6] of char = \ 'Delphi\'#0;
begin
  H2 := FindWindow(A2, nil);
  H3 := FindWindow(A3, nil);
  H4 := FindWindow(A4, nil);
  Result := (H2 <> 0) and (H3 <> 0) and (H4 <> 0);
end;

2008. február 26., kedd

When TCanvas.StretchDraw is not enough


Problem/Question/Abstract:

In some cases, the StretchDraw method of TCanvas can produce unsatisfying results. This article presents an alternative that can be better under some circumstances.

Answer:

One of the things I wanted the toolbar to do was to display the icons of the programs that it launches at a size smaller than that of ordinary icons. After retrieving the icon for a program and putting it in a bitmap.  I first tried to use TCanvas.StretchDraw to stretch the bitmap onto another bitmap which I would then assign to a TImage.  Although this worked, the icons came out looking bad.  It looked like StretchDraw droped pixels when it needed to make a bitmap smaller.  For large images this is probably a good idea but for my purpose, it wasn't adequate.  Instead I retrieved the color for each pixel in the source bitmap, calculated the fraction of each pixel in the destination bitmap that the source would cover and then assigned the final color of the pixel in the destination based on the proportions of pixels in the source that covered them.

unit ManipulateBitmaps;

interface

uses ShellAPI, Windows, SysUtils, Graphics, ExtCtrls;

procedure StretchBitmap(const Source, Destination: TBitmap);
{
This proceedure takes stretches the image in Source
and puts it in Destination.
The width and height of Destination must be specified before
calling StretchBitmap.
The PixelFormat of both Source and Destination are changed to pf32bit.
}

implementation

type
  PLongIntArray = ^TLongIntArray;
  TLongIntArray = array[0..16383] of longint;

procedure GetIndicies(const DestinationLength, SourceLength,
  DestinationIndex: integer;
  out FirstIndex, LastIndex: integer;
  out FirstFraction, LastFraction: double);
{
This proceedure compares the length of two pixel arrays and determines
which pixels in the destination are covered by those in the source.
It also determines what fraction of the first and last pixels are covered
in the destination.
}
var
  Index1A: double;
  Index2A: double;
  Index2B: integer;
begin
  Index1A := DestinationIndex / DestinationLength * SourceLength;
  FirstIndex := Trunc(Index1A);
  FirstFraction := 1 - Frac(Index1A);
  Index2A := (DestinationIndex + 1) / DestinationLength * SourceLength;
  Index2B := Trunc(Index2A);
  if Index2A = Index2B then
  begin
    LastIndex := Index2B - 1;
    LastFraction := 1;
  end
  else
  begin
    LastIndex := Index2B;
    LastFraction := Frac(Index2A);
  end;
  if FirstIndex = LastIndex then
  begin
    FirstFraction := FirstFraction - (1 - LastFraction);
    LastFraction := FirstFraction;
  end;
end;

procedure StretchBitmap(const Source, Destination: TBitmap);
{
This proceedure takes stretches the image in Source
and puts it in Destination.
The width and height of Destination must be specified
before calling StretchBitmap.
The PixelFormat of both Source and Destination are changed to pf32bit.
}
var
  P, P1, P2: PLongIntArray;
  X, Y: integer;
  FirstY, LastY, FirstX, LastX: integer;
  FirstYFrac, LastYFrac, FirstXFrac, LastXFrac: double;
  YFrac, XFrac: double;
  YIndex, XIndex: integer;
  AColor: TColor;
  Red, Green, Blue: integer;
  RedTotal, GreenTotal, BlueTotal, FracTotal: double;
begin
  Source.PixelFormat := pf32bit;
  Destination.PixelFormat := Source.PixelFormat;

  for Y := 0 to Destination.height - 1 do
  begin
    P := Destination.ScanLine[y];

    GetIndicies(Destination.Height, Source.Height, Y,
      FirstY, LastY, FirstYFrac, LastYFrac);

    for x := 0 to Destination.width - 1 do
    begin

      GetIndicies(Destination.width, Source.width, X,
        FirstX, LastX, FirstXFrac, LastXFrac);

      RedTotal := 0;
      GreenTotal := 0;
      BlueTotal := 0;
      FracTotal := 0;

      for YIndex := FirstY to LastY do
      begin
        P1 := Source.ScanLine[YIndex];
        if YIndex = FirstY then
        begin
          YFrac := FirstYFrac;
        end
        else if YIndex = LastY then
        begin
          YFrac := LastYFrac;
        end
        else
        begin
          YFrac := 1;
        end;

        for XIndex := FirstX to LastX do
        begin
          AColor := P1[XIndex];
          Red := AColor mod $100;
          AColor := AColor div $100;
          Green := AColor mod $100;
          AColor := AColor div $100;
          Blue := AColor mod $100;

          if XIndex = FirstX then
          begin
            XFrac := FirstXFrac;
          end
          else if XIndex = LastX then
          begin
            XFrac := LastXFrac;
          end
          else
          begin
            XFrac := 1;
          end;

          RedTotal := RedTotal + Red * XFrac * YFrac;
          GreenTotal := GreenTotal + Green * XFrac * YFrac;
          BlueTotal := BlueTotal + Blue * XFrac * YFrac;
          FracTotal := FracTotal + XFrac * YFrac;
        end;
      end;

      Red := Round(RedTotal / FracTotal);
      Green := Round(GreenTotal / FracTotal);
      Blue := Round(BlueTotal / FracTotal);

      AColor := Blue * $10000 + Green * $100 + Red;

      P[X] := AColor;
    end;
  end;
end;

end.


I recently wrote a freeware toolbar (http://www.mindspring.com/~rbwinston/launcher.htm)

2008. február 25., hétfő

How to rearrange items within a TListBox


Problem/Question/Abstract:

Can someone point me to a document on how to drag items around (reposition) within a TListbox?

Answer:

Solve 1:

It is easier than you might think. Set the DragMode property to dmAutomatic, then provide these event-handlers for OnDragDrop and OnDragOver:

procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  Accept := (Sender = Source);
end;

procedure TForm1.ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  DropIndex: Integer;
begin
  DropIndex := ListBox1.ItemAtPos(Point(X, Y), True);
  ListBox1.Items.Exchange(ListBox1.ItemIndex, DropIndex);
end;

Solve 2:

There is no build-in method. Try that:


procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  Accept := Sender is TListBox;
end;

procedure TForm1.ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  iTemp: integer;
  ptTemp: TPoint;
  szTemp: string;
begin
  { change the x, y coordinates into a TPoint record }
  ptTemp.x := x;
  ptTemp.y := y;
  { Use a while loop instead of a for loop due to items possible being removed
   from listboxes this prevents an out of bounds exception }
  iTemp := 0;
  while iTemp <= TListBox(Source).Items.Count - 1 do
  begin
    { look for the selected items as these are the ones we wish to move }
    if TListBox(Source).selected[iTemp] then
    begin
      { use a with as to make code easier to read }
      with Sender as TListBox do
      begin
        { need to use a temporary variable as when the item is deleted the indexing will change }
        szTemp := TListBox(Source).items[iTemp];
        { delete the item that is being dragged  }
        TListBox(Source).items.Delete(iTemp);
        { insert the item into the correct position in the listbox that it was dropped on }
        items.Insert(itemAtPos(ptTemp, true), szTemp);
      end;
    end;
    inc(iTemp);
  end;
end;

2008. február 24., vasárnap

Adding an icon to the Windows About dialog


Problem/Question/Abstract:

Adding an icon to the Windows About dialog

Answer:

If you want to bring up the standard Windows 'About..' dialog box, then you can use ShellAbout() from the ShellAPI unit and customize the appearance by adding your own text, application name and an icon.

The downside to this technique is that it will say '(c) Microsoft' in the box.

The upside is that you see the registered user and some system parameters (free space..). It's a quick-and-dirty solution for an About-box.

  
uses
  Windows, ShellAPI;

procedure TForm1.About1Click(Sender: TObject);
begin
  ShellAbout(Application.MainForm.Handle,
    'Address Book Application',
    'Version 1.23.3beta' + #13#10 +
    'Compiled 2001-08-03 15:25:10',
    Application.Icon.Handle);
end;

2008. február 23., szombat

Antialiased line drawer using scanline


Problem/Question/Abstract:

How to draw antialiased lines using a TBitmap's scanlines

Answer:

procedure AALine(x1, y1, x2, y2: single; color: tcolor; bitmap: tbitmap);
  function CrossFadeColor(FromColor, ToColor: TColor; Rate: Single): TColor;
  var
    r, g, b: byte;
  begin
    r := Round(GetRValue(FromColor) * Rate + GetRValue(ToColor) * (1 - Rate));
    g := Round(GetGValue(FromColor) * Rate + GetGValue(ToColor) * (1 - Rate));
    b := Round(GetBValue(FromColor) * Rate + GetBValue(ToColor) * (1 - Rate));
    Result := RGB(b, g, r);
  end;

type
  intarray = array[0..1] of integer;
  pintarray = ^intarray;

  procedure hpixel(x: single; y: integer);
  var
    FadeRate: single;
  begin
    FadeRate := x - trunc(x);
    with bitmap do
    begin
      if (x >= 0) and (y >= 0) and (height > y) and (width > x) then
        pintarray(bitmap.ScanLine[y])[trunc(x)] := CrossFadeColor(Color,
          pintarray(bitmap.ScanLine[y])[trunc(x)], 1 - FadeRate);
      if (trunc(x) + 1 >= 0) and (y >= 0) and (height > y) and (width > trunc(x) + 1)
        then
        pintarray(bitmap.ScanLine[y])[trunc(x) + 1] := CrossFadeColor(Color,
          pintarray(bitmap.ScanLine[y])[trunc(x) + 1], FadeRate);
    end;
  end;

  procedure vpixel(x: integer; y: single);
  var
    FadeRate: single;
  begin
    FadeRate := y - trunc(y);
    with bitmap do
    begin
      if (x >= 0) and (trunc(y) >= 0) and (height > trunc(y)) and (width > x) then
        pintarray(bitmap.ScanLine[trunc(y)])[x] := CrossFadeColor(Color,
          pintarray(bitmap.ScanLine[trunc(y)])[x], 1 - FadeRate);
      if (x >= 0) and (trunc(y) + 1 >= 0) and (height > trunc(y) + 1) and (width > x)
        then
        pintarray(bitmap.ScanLine[trunc(y) + 1])[x] := CrossFadeColor(Color,
          pintarray(bitmap.ScanLine[trunc(y) + 1])[x], FadeRate);
    end;
  end;

var
  i: integer;
  ly, lx, currentx, currenty, deltax, deltay, l, skipl: single;
begin
  if (x1 <> x2) or (y1 <> y2) then
  begin
    bitmap.PixelFormat := pf32Bit;
    currentx := x1;
    currenty := y1;
    lx := abs(x2 - x1);
    ly := abs(y2 - y1);

    if lx > ly then
    begin
      l := trunc(lx);
      deltay := (y2 - y1) / l;
      if x1 > x2 then
      begin
        deltax := -1;
        skipl := (currentx - trunc(currentx));
      end
      else
      begin
        deltax := 1;
        skipl := 1 - (currentx - trunc(currentx));
      end;
    end
    else
    begin
      l := trunc(ly);
      deltax := (x2 - x1) / l;
      if y1 > y2 then
      begin
        deltay := -1;
        skipl := (currenty - trunc(currenty));
      end
      else
      begin
        deltay := 1;
        skipl := 1 - (currenty - trunc(currenty));
      end;
    end;

    currentx := currentx + deltax * skipl;
    currenty := currenty + deltay * skipl; {}

    for i := 1 to trunc(l) do
    begin
      if lx > ly then
        vpixel(trunc(currentx), currenty)
      else
        hpixel(currentx, trunc(currenty));
      currentx := currentx + deltax;
      currenty := currenty + deltay;
    end;
  end;
end;

2008. február 22., péntek

How to search for a certain font style in a TRichEdit


Problem/Question/Abstract:

How to search for a certain font style in a TRichEdit

Answer:

Finding all bold-faced words in a TRichEdit control:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    RichEdit1: TRichEdit;
    ListBox1: TListBox;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var
  S: string;
  wordstart, wordend: Integer;
begin
  listbox1.clear;
  listbox1.setfocus;
  S := richedit1.text;
  wordstart := 0;
  repeat
    {find start of next word}
    repeat
      Inc(wordstart);
    until
      (wordstart > Length(S)) or IsCharAlpha(S[wordstart]);
    if wordstart <= Length(S) then
    begin
      {find end of word}
      wordend := wordstart;
      repeat
        Inc(wordend);
      until
        (wordend > Length(S)) or not IsCharAlpha(S[wordend]);
      {we have a word, select it in the rich edit}
      with richedit1 do
      begin
        selstart := wordstart - 1; {character index is 0 based!}
        sellength := wordend - wordstart;
        {check the attributes}
        if (fsBold in SelAttributes.Style) and (caBold in
          SelAttributes.ConsistentAttributes) then
          {we have a winna, add it to the listbox}
          listbox1.items.add(Copy(S, wordstart, wordend - wordstart));
      end;
      wordstart := wordend;
    end;
  until
    wordstart >= Length(S);
end;

end.

2008. február 21., csütörtök

Long file names - short file names


Problem/Question/Abstract:

Long file names - short file names

Answer:

Here's a way to convert between short (8.3 DOS file names) and long file names:


{$APPTYPE console}

program LongShrt;

uses
  Windows, SysUtils;

function GetShortName(sLongName: string): string;
var
  sShortName: string;
  nShortNameLen: integer;
begin
  SetLength(sShortName, MAX_PATH);

  nShortNameLen := GetShortPathName(PChar(sLongName),
    PChar(sShortName), MAX_PATH - 1);

  if nShortNameLen = 0 then
  begin
    { handle errors... }
  end;

  SetLength(sShortName, nShortNameLen);

  Result := sShortName;
end;

function GetLongName(sShortName: string; var bError: boolean): string;
var
  bAddSlash: boolean;
  SearchRec: TSearchRec;
  nStrLen: integer;
begin
  bError := False;
  Result := sShortName;
  nStrLen := Length(sShortName);
  bAddSlash := False;

  if sShortName[nStrLen] = '\' then
  begin
    bAddSlash := True;
    SetLength(sShortName, nStrLen - 1);
    dec(nStrLen);
  end;

  if ((nStrLen - Length(ExtractFileDrive(sShortName))) > 0) then
  begin
    if FindFirst(sShortName, faAnyFile, SearchRec) = 0 then
    begin
      Result := ExtractFilePath(sShortName) + SearchRec.name;
      if bAddSlash then
      begin
        Result := Result + '\';
      end;
    end
    else
    begin
      // handle errors...       bError := True;
    end;
    FindClose(SearchRec);
  end;
end;

function GetLongName(sShortName: string): string;
var
  s: string;
  p: integer;
  bError: boolean;
begin
  Result := sShortName;

  s := '';
  p := Pos('\', sShortName);
  while (p > 0) do
  begin
    s := GetLongName(s + Copy(sShortName, 1, p), bError);
    Delete(sShortName, 1, p);
    p := Pos('\', sShortName);

    if (bError) then
      Exit;
  end;
  if sShortName <> '' then
  begin
    s := GetLongName(s + sShortName, bError);
    if bError then
      Exit;
  end;
  Result := s;
end;

const
  csTest = 'C:\program Files';

var
  sShort,
    sLong: string;

begin
  sShort := GetShortName(csTest);
  WriteLn('Short name for "' + csTest +
    '" is "' + sShort + '"');

  WriteLn;

  sLong := GetLongName(sShort);
  WriteLn('Long name for "' + sShort + '" is "' + sLong + '"');
end.

2008. február 20., szerda

Sort Order of Internet Explorer Favorites


Problem/Question/Abstract:

You can easily get the list of favorites from the directory, but how can you emulate the same sort order showing in Internet Explorer?

Answer:

I could not find this information anywhere on the Microsoft site or on Google Groups, so I had to just start digging in the registry and in the binary.

The registry path for favorites is

HKey_Current_User\Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Favorites\

containing the directory structure mirroring the structure in your favorites.

The registry key "order" is a binary containing the visible name, order number, and DOS filename.  The start of the registry binary looks like:

08 00 00 00 02 00 00 00 24 06 00 00 01 00 00 00
16 00 00 00 48 00 00 0D 00 00 00 00 39 00 32 00
98 00 00 00 B1 2C 74 B5 20 00 42 65 6E 20 5A 65
69 67 6C 65 72 27 73 20 44 65 6C 70 68 69 20 50
61 67 65 2E 75 72 6C 00 42 45 4E 5A 45 49 7E 31
2E 55 52 4C 00 00 00 00 05 ...(continues)

Position 16 contains a count of the number of items, 22 in this case. (This is Hex, remember!)

08 00 00 00 02 00 00 00 24 06 00 00 01 00 00 00
16 00 00 00 48 00 00 0D 00 00 00 00 39 00 32 00
98 00 00 00 B1 2C 74 B5 20 00 42 65 6E 20 5A 65
69 67 6C 65 72 27 73 20 44 65 6C 70 68 69 20 50
61 67 65 2E 75 72 6C 00 42 45 4E 5A 45 49 7E 31
2E 55 52 4C 00 00 00 00 05 ...(continues)

Position 17 starts data, with 3 nulls and the count of total data (Hex 48 or 68 in decimal) and 3 more nulls and the order number (position 7, relative to the data start and "D" or 13).

08 00 00 00 02 00 00 00 24 06 00 00 01 00 00 00
16 00 00 00 48 00 00 0D 00 00 00 00 39 00 32 00
98 00 00 00 B1 2C 74 B5 20 00 42 65 6E 20 5A 65
69 67 6C 65 72 27 73 20 44 65 6C 70 68 69 20 50
61 67 65 2E 75 72 6C 00 42 45 4E 5A 45 49 7E 31
2E 55 52 4C 00 00 00 00 05 ...(continues)

Relative position 20 is the count of the name, including the DOS filename.

08 00 00 00 02 00 00 00 24 06 00 00 01 00 00 00
16 00 00 00 48 00 00 0D 00 00 00 00 39 00 32 00
98 00 00 00 B1 2C 74 B5 20 00 42 65 6E 20 5A 65
69 67 6C 65 72 27 73 20 44 65 6C 70 68 69 20 50
61 67 65 2E 75 72 6C 00 42 45 4E 5A 45 49 7E 31
2E 55 52 4C 00 00 00 00 05 ...(continues)

Relative position 25 begins the name, terminated by a null

08 00 00 00 02 00 00 00 24 06 00 00 01 00 00 00
16 00 00 00 48 00 00 0D 00 00 00 00 39 00 32 00
98 00 00 00 B1 2C 74 B5 20 00 42 65 6E 20 5A 65
69 67 6C 65 72 27 73 20 44 65 6C 70 68 69 20 50
61 67 65 2E 75 72 6C 00 42 45 4E 5A 45 49 7E 31
2E 55 52 4C 00 00 00 00 05 ...(continues)

Then a DOS filename, 3 nulls, then a hex value 5 as a terminator.

08 00 00 00 02 00 00 00 24 06 00 00 01 00 00 00
16 00 00 00 48 00 00 0D 00 00 00 00 39 00 32 00
98 00 00 00 B1 2C 74 B5 20 00 42 65 6E 20 5A 65
69 67 6C 65 72 27 73 20 44 65 6C 70 68 69 20 50
61 67 65 2E 75 72 6C 00 42 45 4E 5A 45 49 7E 31
2E 55 52 4C 00 00 00 00 05 ...(continues)

And the next record starts after the #5 terminator.

An ugly little chunk of code to pull this data and put it in a TMemo out would be:

const
  REGLEN = 5000;
var
  I, A, B: Integer;
  reg: TRegistry;
  buf: array[0..REGLEN] of char;
  itembuf: array[0..1000] of char;
  lastpos: Integer;
  order: Integer;
  name, dosfile: array[0..200] of char;
  namecount, count: Integer;

begin
  reg := TRegistry.Create;
  reg.RootKey := HKEY_CURRENT_USER;
  reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Favorites\Delphi', FALSE);
  reg.ReadBinaryData('Order', buf, REGLEN);

  count := ord(buf[16]);
  Memo1.Lines.Add('How Many: ' + intToStr(count));

  lastpos := 17;
  for I := 0 to count - 1 do // Iterate
  begin
    for a := lastpos to lastpos + 999 do // Iterate
    begin
      itembuf[a - lastpos] := buf[a];
    end; // for

    order := ord(itembuf[7]);
    Memo1.Lines.Add('This order ' + intToStr(order));

    namecount := ord(itembuf[20]);
    for a := 25 to namecount + 25 do // Iterate
    begin
      name[a - 25] := itembuf[a];
      if itembuf[a] = #0 then
        break;
    end; // for
    Memo1.Lines.Add('Name ' + name);

    for b := a to a + 13 do // Iterate
    begin
      dosfile[b - a - 1] := itembuf[b];
    end; // for
    if dosfile = '' then
      dosfile := name;

    Memo1.Lines.Add('DOS File ' + dosfile);

    lastpos := ord(itembuf[3]) + lastpos;
  end; // for
  reg.free;

2008. február 19., kedd

How to set all events of an object to NIL at runtime


Problem/Question/Abstract:

Is there a way to enumerate all of an objects events at runtime and set them to nil?

Answer:

You can use RTTI to accomplish your goal, but only for published, not public, events. Using RTTI is pretty complex, so I've written a working utility procedure for you which takes any object instance and assigns nil to its published events:

unit uNilEvent;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses
  TypInfo;

procedure NilEvents(Instance: TObject);
var
  TypeInfo: PTypeInfo;
  I, Count: Integer;
  PropList: PPropList;
  PropInfo: PPropInfo;
  Method: TMethod;
begin
  TypeInfo := Instance.ClassInfo;
  Method.Code := nil;
  Method.Data := nil;
  Count := GetPropList(TypeInfo, [tkMethod], nil);
  GetMem(PropList, Count * SizeOf(Pointer));
  try
    GetPropList(TypeInfo, [tkMethod], PropList);
    for I := 0 to Count - 1 do
    begin
      PropInfo := PropList^[I];
      SetMethodProp(Instance, PropInfo, Method);
    end;
  finally
    FreeMem(PropList);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
const
  sText = 'The 2nd time you click Button1 the event will not fire';
begin
  NilEvents(Button1);
  ShowMessage(sText);
end;

end.

2008. február 18., hétfő

How to change the node order of a TTreeNode


Problem/Question/Abstract:

Say I have a TOutline with these nodes:

Parent1
Node1
Node2
Node3
Parent2
Parent3

And I need to change the order of the nodes. How can I for example step Node2 down one step at a time and disable any movement when there are no more nodes to move past at that level (just after Node3). I need to restrict the stepping inside a group/ level and that it don't move to another parent.

Answer:

Look in the help file for the TTreeNode methods GetNextSibling, GetPrevSibling and MoveTo. Say you have a form with a tree view and two buttons, labelled up and down. The code for the onclick events of the up button would look something like this:

procedure UpOnClick
var
  PrevSibling: TTreeNode;
begin
  {If no node is selected, exit the procedure}
  if MyTreeView.Selected = nil then
    Exit;
  {If the node the user is trying to move is not a child node, exit the procedure}
  if MyTreeView.Selected.Level <> 1 then
    Exit;
  with MyTreeView.Selected do
  begin
    PrevSibling := GetPrevSibling;
    if PrevSibling <> nil then
      MoveTo(PrevSibling, naInsert);
  end;
end;

procedure DownOnClick
var
  NextSibling: TTreeNode;
begin
  {If no node is selected, exit the procedure}
  if MyTreeView.Selected = nil then
    Exit;
  {If the node the user is trying to move is not a child node, exit the procedure}
  if MyTreeView.Selected.Level <> 1 then
    Exit;
  with MyTreeView.Selected do
  begin
    NextSibling := GetNextSibling;
    if NextSibling <> nil then
      NextSibling.MoveTo(MyTreeView.Selected, naInsert);
  end;
end;

2008. február 17., vasárnap

How to print the contents of a TRichEdit to a printer canvas


Problem/Question/Abstract:

I have a TRichEdit Control that I want to print as part of a document. There is other information that needs to go on the printed page. The Print method seems to start a separate document. How do I print the rich edits contents to the printer canvas of my document. As well I need to anticipate that there could be one or two pages of printed depending on the information in the TRichEdit.

Answer:

You have to use the EM_FORMATRANGE message to print the richedits content in code. Printing rich edit contents using EM_FORMATRANGE:


procedure TForm1.Button2Click(Sender: TObject);
var
  printarea: TRect;
  x, y: Integer;
  richedit_outputarea: TRect;
  printresX, printresY: Integer;
  fmtRange: TFormatRange;
begin
  Printer.beginDoc;
  try
    with Printer.Canvas do
    begin
      printresX := GetDeviceCaps(handle, LOGPIXELSX);
      printresY := GetDeviceCaps(handle, LOGPIXELSY);
      Font.Name := 'Arial';
      Font.Size := 14;
      Font.Style := [fsBold];
      printarea :=
        Rect(printresX, {1 inch left margin}
        printresY * 3 div 2, {1.5 inch top margin}
        Printer.PageWidth - printresX, {1 inch right margin}
        Printer.PageHeight - printresY * 3 div 2 {1.5 inch bottom margin}
        );
      x := printarea.left;
      y := printarea.top;
      TextOut(x, y, 'A TRichEdit print example');
      y := y + TextHeight('Ag');
      Moveto(x, y);
      Pen.Width := printresY div 72; {1 point}
      Pen.Style := psSolid;
      Pen.Color := clBlack;
      LineTo(printarea.Right, y);
      Inc(y, printresY * 5 div 72);
      {Define a rectangle for the rich edit text. The height is set to the maximum.
                        But we need to convert from device units to
                 twips, 1 twip = 1/1440 inch or 1/20 point.}
      richedit_outputarea := Rect((printarea.left + 2) * 1440 div printresX,
        y * 1440 div printresY, (printarea.right - 4) * 1440 div printresX,
        (printarea.bottom) * 1440 div printresY);
      {Tell rich edit to format its text to the printer.
                         First set up data record for message:}
      fmtRange.hDC := Handle; {printer handle}
      fmtRange.hdcTarget := Handle; {ditto}
      fmtRange.rc := richedit_outputarea;
      fmtRange.rcPage := Rect(0, 0, Printer.PageWidth * 1440 div printresX,
        Printer.PageHeight * 1440 div printresY);
      fmtRange.chrg.cpMin := 0;
      fmtRange.chrg.cpMax := richedit1.GetTextLen - 1;
      {first measure the text, to find out how high the format rectangle will be.
                        The call sets fmtrange.rc.bottom to the actual height required,
                        if all characters in the selected range will fit into a smaller rectangle}
      richedit1.Perform(EM_FORMATRANGE, 0, Longint(@fmtRange));
      {Draw a rectangle around the format rectangle}
      Pen.Width := printresY div 144; {0.5 points}
      Brush.Style := bsClear;
      Rectangle(printarea.Left, y - 2, printarea.right, fmtrange.rc.bottom * printresY div 1440 + 2);
      {Now render the text}
      richedit1.Perform(EM_FORMATRANGE, 1, Longint(@fmtRange));
      y := fmtrange.rc.bottom * printresY div 1440 + printresY * 5 div 72;
      {Free cached information}
      richedit1.Perform(EM_FORMATRANGE, 0, 0);
      TextOut(x, y, 'End of example.');
    end;
  finally
    Printer.EndDoc;
  end;
end;


This example assumes that anything will fit on one page but it is no problem to extend it to multiple pages. The richedit1.perform( EM_FORMATRANGE) call returns the index of the last character that could be fitted into the passed fmtrange.rc, + 1. So if multiple pages are required one repeats with fmtrange.chrg.cpMin set to this value, until all characters have been printed.

Note that the rich edit control strips blanks and linebreaks off the end of the text so the number of characters to output may be < richedit.gettextLen!

2008. február 16., szombat

How to get the width and height of a MDI child form while dragging


Problem/Question/Abstract:

I need to know how to get the width and height of a MDI child window (or of an aligned component) before (!) and after scaling it with mouse dragging (e.g. dragging on the right bottom corner of the window). Which event provides these values at which time?

Answer:

There is no event directly usable for this but it can be done with a bit of API mixed in. When the user starts to drag on the border the window gets a WM_ENTERSIZEMOVE message, when the mouse goes up again it gets a WM_EXITSIZEMOVE message. So these are ideally suited to record old and new size. Note that the messages (as their name implies) are also send when the user moves the window by dragging on the caption. In that case the two sizes will simply be equal, so that is easy to test.

{ ... }
private
FOldSize, FNewSize: TRect;

procedure WMEnterSizeMove(var msg: TMessage); message WM_ENTERSIZEMOVE;
procedure WMExitSizeMove(var msg: TMessage); message WM_EXITSIZEMOVE;
{ ... }

procedure TProdBuilderMainForm.WMEnterSizeMove(var msg: TMessage);
begin
  FOldSize := BoundsRect;
end;

procedure TProdBuilderMainForm.WMExitSizeMove(var msg: TMessage);
begin
  FNewSize := BoundsRect;
  { ... do something with the sizes}
end;

2008. február 15., péntek

How to open a CSV file and assign each of its lines to a variable


Problem/Question/Abstract:

What is the easiest way to import a comma delimited text file and assign the different elements of a line of data in that file to variables?

Answer:

function GetNextField(var Line: string; Sep: Char = '|'): string;
{Extracts the first field from Line, delimited by Sep, using the
pipe character as the default delimeter}
var
  SepPos: Integer;
begin
  {Finds the position of the first occurrence of Sep in Line}
  SepPos := Pos(Sep, Line);
  {If found...}
  if SepPos > 0 then
  begin
    {There are fields; copy the first to Result}
    Result := Copy(Line, 1, SepPos - 1);
    {Delete first field from Line, including the delimeter}
    Delete(Line, 1, SepPos);
  end
  else
  begin
    {No more fields; copy entire Line to Result}
    Result := Line;
    {Return a null Line}
    Line := '';
  end;
end;

This function can be used with TextFiles, FileStreams, MemoryStreams, StringLists, arrays of strings, etc. I will give you a very basic example of how I write and then read back text files. Here is a writer:

procedure WriteToFile;
var
  Line: string;
  TempFile: TextFile;
begin
  {Initialize the file}
  AssignFile(TempFile, 'Some\Path\Here');
  {Open the file for output}
  Rewrite(TempFile);
  try
    {Scan source table until EOF}
    MyTable.First;
    while not MyTable.EOF do
    begin
      {Build the line}
      Line := MyTableAINTEGERFIELD.AsString + '|';
      Line := Line + MyTableAFLOATFIELD.AsString + '|';
      Line := Line + MyTableASTRINGFIELD.AsString + '|';
      {Write the line}
      Writeln(TempFile, Line);
      {Move to next record}
      MyTable.Next;
    end;
  finally
    {Close the file}
    CloseFile(TempFile);
  end;
end;

And here is a reader:

procedure ReadFromFile;
var
  AInteger: Integer;
  AFloat: Extended;
  AString, Line: string;
  TempFile: TextFile;
begin
  {Initialize the file}
  AssignFile(TempFile, 'Some\Path\Here');
  {Open the file for input}
  Reset(TempFile);
  try
    {Read lines until EOF}
    while not Eof(TempFile) do
    begin
      {Read a line}
      Readln(TempFile, Line);
      {Assign fields to variables}
      AInteger := StrToInt(GetNextField(Line));
      AFloat := StrToFloat(GetNextField(Line));
      AString := GetNextField(Line);
    end;
  finally
    {Close the file}
    CloseFile(ArqTexto);
  end;
end;

These are only basic examples. You must fine tune the error handling to your needs. And, of course, the examples assume your table is allready open and you know in advance how many fields there are in a line.

2008. február 14., csütörtök

How to open the printer properties window


Problem/Question/Abstract:

Is there any API call that opens the printer properties window? You can open it clicking the right mouse button in a printer icon and choosing properties from the context menu.

Answer:

uses
  WinSpool;

procedure TForm1.Button2Click(Sender: TObject);
var
  hPrinter: THandle;
  Device: array[0..255] of char;
  Driver: array[0..255] of char;
  Port: array[0..255] of char;
  hDeviceMode: THandle;
begin
  {can use other index than default or omit this statement if printer
                already selected}
  Printer.PrinterIndex := -1;
  Printer.GetPrinter(Device, Driver, Port, hDeviceMode);
  if WinSpool.OpenPrinter(@Device, hPrinter, nil) then
  try
    PrinterProperties(Handle, hPrinter);
  finally
    WinSpool.ClosePrinter(hPrinter);
  end;
end;

2008. február 13., szerda

InterBase: What is the SQL command to create new users?


Problem/Question/Abstract:

InterBase: What is the SQL command to create new users?

Answer:

There is no SQL command to create new users in InterBase.

The only way to create users is with the Server Manager:

(Tasks | User Security).

2008. február 12., kedd

Catch the TPageControl.HotTrack event


Problem/Question/Abstract:

When the HotTrack property of a TPageControl is True the tabsheet captions light blue for example when the mouse hovers over them. How can I display the TabSheets hint when this event occurs (the TabSheet hint should be displayed only when the mouse hovers over the TabSheet caption)?

Answer:

Use the pagecontrol's OnMouseMove event:

{tabindex may be <> pageindex if some pages have tabvisible = false!}

function FindPageforTabIndex(pagecontrol: TPageControl; tabindex: Integer): TTabSheet;
var
  i: Integer;
begin
  Assert(Assigned(pagecontrol));
  Assert((tabindex >= 0) and (tabindex < pagecontrol.pagecount));
  Result := nil;
  for i := 0 to pagecontrol.pagecount - 1 do
    if pagecontrol.pages[i].tabVisible then
    begin
      Dec(tabindex);
      if tabindex < 0 then
      begin
        result := pagecontrol.pages[i];
        break;
      end;
    end;
end;

function HintForTab(pc: TPageControl; tabindex: Integer): string;
var
  tabsheet: TTabsheet;
begin
  tabsheet := FindPageforTabIndex(pc, tabindex);
  if assigned(tabsheet) then
    result := tabsheet.hint
  else
    result := '';
end;

procedure TForm1.PageControl1MouseMove(Sender: TObject; Shift: TShiftState; X, Y:
  Integer);
var
  tabindex: Integer;
  pc: TPageControl;
  newhint: string;
begin
  pc := Sender as TPageControl;
  tabindex := pc.IndexOfTabAt(X, Y);
  if tabindex >= 0 then
  begin
    newhint := HintForTab(pc, tabindex);
    if newhint <> pc.Hint then
    begin
      pc.Hint := newhint;
      application.CancelHint;
    end;
  end
  else
    pc.Hint := '';
end;

{Attach this to every tabsheets OnMouseMove event}

procedure TForm1.TabSheetMouseMove(Sender: TObject; Shift: TShiftState; X, Y:
  Integer);
begin
  pagecontrol1.Hint := '';
end;

2008. február 11., hétfő

How to enable / disable single items in a TRadioGroup


Problem/Question/Abstract:

How can I set single Items.Strings in RadioGroups to Enabled := True or Enabled := False ?

Answer:

Solve 1:

TControl(RadioGroup1.Components[0]).Enabled := false;
TControl(RadioGroup1.Components[1]).Enabled := true;


Solve 2:

This function allows you to modify TRadioButtons in a given RadioGroup. Of course you can modify this to search not for a caption but for an index:

function ButtonOfGroup(rg: TRadioGroup; SearchCaption: string): TRadioButton;
var
  i: Integer;
begin
  Result := nil;
  for i := 0 to rg.ComponentCount - 1 do
    if (rg.Components[i] is TRadioButton) and
      (CompareStr(TRadioButton(rg.Components[i]).Caption, SearchCaption) = 0) then
    begin
      Result := TRadioButton(rg.Components[i]);
      Break;
    end;
end;

2008. február 10., vasárnap

How to create a modal form that does not stop the execution of the program


Problem/Question/Abstract:

I would like to create a form in my program that is a please wait type of form. I need the form to have the same behaviour as a modal form, but not to stop the execution of the program.

Answer:

So use Show to show the dialog and disable all other forms in your application using the same function a modal dialog uses:

function DisableTaskWindows(ActiveWindow: HWnd): Pointer;
procedure EnableTaskWindows(WindowList: Pointer);

Both are exported by the forms unit but are not documented. You use them like this:

var
  p: Pointer;

  waitform.show;
  application.processmessages; {needed to get form to paint}
  p := DisableTaskWindows(waitform.handle);
try
  { ... do stuff here }
finally
  EnableTaskWindows(p);
  waitform.close;
end;

2008. február 8., péntek

How to fix the incorrect painting of an ActiveX control, which occurs when a web page is scrolled


Problem/Question/Abstract:

How to fix the incorrect painting of an ActiveX control, which occurs when a web page is scrolled

Answer:

In Delphi 4, when an ActiveForm is larger than the browser window the control is on top of IE's scroll bars. In Delphi 5 they changed the code to fix this but didn't get it quite right resulting in the painting problem when scrolling. You need to edit the Delphi 5 AxCtrls unit as follows:


function TActiveXControl.SetObjectRects(const rcPosRect: TRect;
  const rcClipRect: TRect): HResult;
var
  WinRect: TRect;
begin
  try
    IntersectRect(WinRect, rcPosRect, rcClipRect);
    {BEGIN FIX}
    WinRect := Bounds(rcPosRect.left, rcPosRect.Top, WinRect.Right - WinRect.Left +
      rcClipRect.Left - rcPosRect.Left, WinRect.Bottom - WinRect.Top +
      rcClipRect.Top - rcPosRect.Top);
    {END FIX}
    FWinControl.BoundsRect := WinRect;
    Result := S_OK;
  except
    Result := HandleException;
  end;
end;

2008. február 7., csütörtök

Reading Unix ASCII files


Problem/Question/Abstract:

Reading Unix ASCII files

Answer:

Do you need to read ASCII files that originate from a UNIX system? While DOS/ Windows environments separate lines with a #10#13 combination (^J^M), in UNIX systems only a #10 is inserted.

The regular Readln() does not recognize these line breaks.

A quick-and-dirty solution is loading the file into a TStringList. The TStringList.LoadFromFile() method will break up the lines - see below:


with TStringlist.Create do
begin
  LoadFromFile(myfile);
  SaveToFile(myfile);
end;

2008. február 6., szerda

Read and write I/O ports


Problem/Question/Abstract:

Read and write I/O ports

Answer:

In Borland Pascal and Delphi 1, there is a predefined pseudo variable Port.

In the 32bit versions of Delphi you need 2 lines of assembler code..


function InPort(PortAddr: word): byte;
{$IFDEF WIN32}
assembler; stdcall;
asm
  mov dx,PortAddr
  in al,dx
end;
{$ELSE}
begin
  Result := Port[PortAddr];
end;
{$ENDIF}

2008. február 5., kedd

Minimize an application when modal forms are present (2)


Problem/Question/Abstract:

My application is a non-MDI application. We use form.showmodal to call our application forms because we do not want more than one window at a time to be open. Problem: When our users minimize a window opened with showmodal, instead of minimizing the application, the showmodal window is minimized on the desktop. We need to restrict multiple windows from being open simultaneously in this application.

Answer:

Have a look at this unit, this form will do what you want. When minimizing the modal form it will minimize the app, and when restoring from the taskbar it will restore the app and bring the modal window back to front.

unit testunit;

interface

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

type
  TFormTest = class(TForm)
  private
    { Private declarations }
    OldRestore: TNotifyEvent;
    procedure MyMinimize(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
    procedure DoRestore(Sender: TObject);
  public
    { Public declarations }
  end;

implementation

{$R *.DFM}

procedure TFormTest.MyMinimize(var msg: TWMSysCommand);
begin
  if (msg.cmdtype and $FFF0) = SC_MINIMIZE then
  begin
    { the following line only for D5, not for D3, due to a bug(?) in forms.pas }
    EnableWindow(Application.handle, true);
    Application.Minimize;
    OldRestore := Application.OnRestore;
    Application.OnRestore := DoRestore;
    msg.Result := 0;
  end
  else
    inherited;
end;

procedure TFormTest.DoRestore(Sender: TObject);
begin
  Application.OnRestore := OldRestore;
  SetForegroundWindow(Handle);
end;

end.

2008. február 4., hétfő

Emulating a console on TForms


Problem/Question/Abstract:

Implementing a console within a windows application without resorting to an external console application.

Answer:

Consoles are usefull for giving a user access to an application's more complex features without cluttering the interface. If you've ever coded a windowed console, you realise the "messiness" of the code involved. This class allows you to forget about all input/output routines with a few lines of code. The console supports most of the input/output routines available in console (dos) applications such as WriteLn, ReadLn, ReadKey, GotoXY and many, many more.

Using it is simple, Create a TConsole variable and pass it the form on witch you want to display the console. The console's default colors will be the same as the form's color and font.color.

Simply place a "with Console do begin end;" block and put all your console application code in it. I've placed an example with a string parser at the end of the article.

There are also some great features:

cutomizable width/height(in characters), borders
easily load and copy displays with CopyContext and SetContext
user can copy text by dragging the mouse over it like mIRC
user can paste into a read or readln input with CTRL+V
form's properties are adjusted on Create and restored on Free
form's event handler are still processed

and there are some quirks:

you cannot create a TConsole on it's form's OnCreate event
if the form has visible components they will hide the console
you cannot close the form while a read/readln is in progress
read/readln only allow up to 250 chars to avoid glitches
extended characters are not supported for input
text copying with the mouse provides no visual feedback


NOTES

GotoXY,GotoEndOfLine,GetX,GetY,GetLastLine,GetChar,GetText(y:byte), and ClearLn all refer to x,y coordinates starting at position 1,1 (like in console applications)
TConsole has not been tested with other fonts. If you want to tinker with different fonts you should set all properties of Canvas.Font (in the Create procedure) and constants CONSOLE_FONT_HEIGHT, CONSOLE_FONT_WIDTH accordingly.
I was unable to code a suitable visual feedback such as highlighting for the auto-text-copying feature. The main problem is the TForm.OnMouseMove event is only called once. Running a loop through the OnMouseDown even did not work either. I could have implemented the loop in a seperate thread but that seems like overkill. Besides, I want all TConsole functions suspended until the mouse is released so the user isn't fumbled by the application changing the displayed text. If anyone knows how mIRC did it, please email me and I'll add it in.

Here is unit Console.pas
(please forgive the broken lines)

unit Console;

interface
uses Forms, Graphics, SysUtils, ExtCtrls, Classes, Controls, ClipBrd;

const
  CONSOLE_WIDTH = 70;
  CONSOLE_HEIGHT = 25;
  CONSOLE_CARET_SPEED = 500;
  CONSOLE_OFFSET_X = 5;
  CONSOLE_OFFSET_Y = 5;
  CONSOLE_FONT_HEIGHT = 14;
  CONSOLE_FONT_WIDTH = 7;

type
  TConsoleContext = record
    Name: string;
    Lines: array[0..CONSOLE_HEIGHT - 1] of string[CONSOLE_WIDTH];
    PosX, PosY, CaretPosX, CaretPosY: word;
    LastKey: char;
    ShiftKeys: TShiftState;
    KeyPressed: boolean;
    ShowCaret: boolean;
  end;
  PConsoleContext = ^TConsoleContext;

  TConsole = class
    constructor Create(AForm: TForm);
    destructor Destroy; override;
  private
    Context: PConsoleContext;
    Caret: TTimer;
    Canvas: TCanvas;
    Form: TForm;
    Background, Forground: TColor;
    StartDragX, StartDragY: word;
    PreviousOnPaint: TNotifyEvent;
    PreviousOnKeyPress: TKeyPressEvent;
    PreviousOnMouseDown, PreviousOnMouseUp: TMouseEvent;
    PreviousWidth, PreviousHeight: word;
    procedure PaintLine(y: byte);
    procedure Refresh(Sender: TObject);
    procedure EraseCaret;
    procedure PaintCaret;
    procedure ToggleCaret(Sender: TObject);
    procedure KeyPress(Sender: TObject; var Key: char);
    procedure OnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState;
      x, y: Integer);
    procedure OnMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; x,
      y: Integer);
  public
    procedure CopyContext(var AContext: TConsoleContext);
    procedure SetContext(var AContext: TConsoleContext);
    procedure Update;
    procedure SetColors(FgColor, BgColor: TColor);
    procedure GotoXY(x, y: byte);
    procedure GotoEndOfLine(y: byte);
    function GetX: byte;
    function GetY: byte;
    function GetLastLine: byte;
    function GetChar(x, y: byte): char;
    function GetText(y: byte): string;
    procedure Clear;
    procedure ClearLn(y: byte);
    procedure LineFeed;
    procedure Write(Str: string);
    procedure WriteLn(Str: string);
    function ReadKey: char;
    function ReadLength(Len: byte): string;
    function Read: string;
    function ReadLn: string;
    function ReadLnLength(Len: byte): string;
  end;

implementation

constructor TConsole.Create(AForm: TForm);
begin
  Form := AForm;
  Canvas := Form.Canvas;
  Canvas.Font.Name := 'Courier New';
  Canvas.Font.Size := 8;
  Canvas.Font.Height := -11;
  Canvas.Brush.Color := Form.Color;
  Canvas.Font.Color := Form.Font.Color;

  Background := Form.Color;
  Forground := Form.Font.Color;
  PreviousOnPaint := Form.OnPaint;
  PreviousOnKeyPress := Form.OnKeyPress;
  PreviousOnMouseDown := Form.OnMouseDown;
  PreviousOnMouseUp := Form.OnMouseUp;
  Form.OnMouseDown := OnMouseDown;
  Form.OnMouseUp := OnMouseUp;

  GetMem(Context, Sizeof(TConsoleContext));

  PreviousWidth := AForm.ClientWidth;
  PreviousHeight := AForm.ClientHeight;
  Form.ClientWidth := (CONSOLE_OFFSET_X * 2) + (CONSOLE_WIDTH * CONSOLE_FONT_WIDTH);
  Form.ClientHeight := (CONSOLE_OFFSET_Y * 2) + (CONSOLE_HEIGHT *
    CONSOLE_FONT_HEIGHT);
  Form.OnPaint := Refresh;

  Caret := TTimer.Create(nil);
  with Caret do
  begin
    Enabled := false;
    Interval := CONSOLE_CARET_SPEED;
    OnTimer := ToggleCaret;
  end;
  Context^.ShowCaret := false;

  Clear;
end;

destructor TConsole.Destroy;
begin
  Caret.Free;
  FreeMem(Context);
  Form.OnPaint := PreviousOnPaint;
  Form.OnKeyPress := PreviousOnKeyPress;
  Form.OnMouseDown := PreviousOnMouseDown;
  Form.OnMouseUp := PreviousOnMouseUp;
  Form.ClientWidth := PreviousWidth;
  Form.ClientHeight := PreviousHeight;
  inherited;
end;

procedure TConsole.PaintLine(y: byte);
begin
  Canvas.FillRect(Rect(CONSOLE_OFFSET_X, CONSOLE_OFFSET_Y + (y *
    (CONSOLE_FONT_HEIGHT)), CONSOLE_OFFSET_X + (CONSOLE_WIDTH) * (CONSOLE_FONT_WIDTH),
    CONSOLE_OFFSET_Y + (y * (CONSOLE_FONT_HEIGHT)) + CONSOLE_FONT_HEIGHT));
  Canvas.TextOut(CONSOLE_OFFSET_X, CONSOLE_OFFSET_Y + (y * (CONSOLE_FONT_HEIGHT)),
    Context^.Lines[y]);
end;

procedure TConsole.Refresh(Sender: TObject);
var
  y: byte;
begin
  if (CONSOLE_OFFSET_X <> 0) and (CONSOLE_OFFSET_Y <> 0) then
  begin
    Canvas.FillRect(Rect(0, 0, Canvas.ClipRect.Right, CONSOLE_OFFSET_Y));
    Canvas.FillRect(Rect(0, CONSOLE_OFFSET_Y, CONSOLE_OFFSET_X, CONSOLE_OFFSET_Y +
      ((CONSOLE_HEIGHT - 1) * (CONSOLE_FONT_HEIGHT)) + CONSOLE_FONT_HEIGHT));
    Canvas.FillRect(Rect(0, CONSOLE_OFFSET_Y + ((CONSOLE_HEIGHT - 1) *
      (CONSOLE_FONT_HEIGHT)) + CONSOLE_FONT_HEIGHT, Canvas.ClipRect.Right,
      Canvas.ClipRect.Bottom));
    Canvas.FillRect(Rect(CONSOLE_OFFSET_X + (CONSOLE_WIDTH) * (CONSOLE_FONT_WIDTH),
      CONSOLE_OFFSET_Y, Canvas.ClipRect.Right, CONSOLE_OFFSET_Y + ((CONSOLE_HEIGHT - 1)
      * (CONSOLE_FONT_HEIGHT)) + CONSOLE_FONT_HEIGHT));
  end;
  with Context^ do
    for y := 0 to CONSOLE_HEIGHT - 1 do
      PaintLine(y);
  PaintCaret;
  if Assigned(PreviousOnPaint) then
    PreviousOnPaint(Sender);
end;

procedure TConsole.EraseCaret;
begin
  with Context^ do
    if Length(Lines[CaretPosY]) > CaretPosX then
      Canvas.TextOut(CONSOLE_OFFSET_X + (CaretPosX * (CONSOLE_FONT_WIDTH)),
        CONSOLE_OFFSET_Y + (CaretPosY * (CONSOLE_FONT_HEIGHT)), Lines[CaretPosY,
        CaretPosX + 1])
    else
      Canvas.TextOut(CONSOLE_OFFSET_X + (CaretPosX * (CONSOLE_FONT_WIDTH)),
        CONSOLE_OFFSET_Y + (CaretPosY * (CONSOLE_FONT_HEIGHT)), ' ');
end;

procedure TConsole.PaintCaret;
begin
  with Context^ do
  begin
    if Caret.Enabled = false then
      Exit;
    if ShowCaret = true then
    begin
      if (CaretPosX <> PosX) or (CaretPosY <> PosY) then
        EraseCaret;
      Canvas.Brush.Color := Forground;
      Canvas.FillRect(Rect(CONSOLE_OFFSET_X + (PosX * (CONSOLE_FONT_WIDTH)),
        CONSOLE_OFFSET_Y + (PosY * (CONSOLE_FONT_HEIGHT)) + 10, CONSOLE_OFFSET_X + (PosX
        * (CONSOLE_FONT_WIDTH)) + CONSOLE_FONT_WIDTH, CONSOLE_OFFSET_Y + (PosY *
        (CONSOLE_FONT_HEIGHT)) + 13));
      Canvas.Brush.Color := Background;
      CaretPosX := PosX;
      CaretPosY := PosY;
    end
    else
      EraseCaret;
  end;
end;

procedure TConsole.ToggleCaret(Sender: TObject);
begin
  with Context^ do
    ShowCaret := not ShowCaret;
  PaintCaret;
end;

procedure TConsole.KeyPress(Sender: TObject; var Key: char);
begin
  with Context^ do
  begin
    LastKey := Key;
    KeyPressed := true;
  end;
  if Assigned(PreviousOnKeyPress) then
    PreviousOnKeyPress(Form, Key);
end;

procedure TConsole.OnMouseDown(Sender: TObject; Button: TMouseButton; Shift:
  TShiftState; x, y: Integer);
begin
  if Button <> mbLeft then
    Exit;
  StartDragX := (X - CONSOLE_OFFSET_X) div CONSOLE_FONT_WIDTH;
  StartDragY := (Y - CONSOLE_OFFSET_Y) div CONSOLE_FONT_HEIGHT;
  if StartDragX >= CONSOLE_WIDTH then
    StartDragX := CONSOLE_WIDTH - 1;
  if StartDragY >= CONSOLE_HEIGHT then
    StartDragY := CONSOLE_HEIGHT - 1;
  if Assigned(PreviousOnMouseDown) then
    PreviousOnMouseDown(Sender, Button, Shift, x, y);
end;

procedure TConsole.OnMouseUp(Sender: TObject; Button: TMouseButton; Shift:
  TShiftState; x, y: Integer);
var
  EndDragX, EndDragY, Temp: word;
  Str: string;
begin
  if Button <> mbLeft then
    Exit;
  EndDragX := (x - CONSOLE_OFFSET_X) div CONSOLE_FONT_WIDTH;
  EndDragY := (y - CONSOLE_OFFSET_Y) div CONSOLE_FONT_HEIGHT;
  if EndDragX >= CONSOLE_WIDTH then
    EndDragX := CONSOLE_WIDTH - 1;
  if EndDragY >= CONSOLE_HEIGHT then
    EndDragY := CONSOLE_HEIGHT - 1;
  if (StartDragX = EndDragX) and (StartDragY = EndDragY) then
    Exit;
  if EndDragY < StartDragY then
  begin
    Temp := EndDragX;
    EndDragX := StartDragX;
    StartDragX := Temp;
    Temp := EndDragY;
    EndDragY := StartDragY;
    StartDragY := Temp;
  end
  else if (EndDragY = StartDragY) and (EndDragX < StartDragX) then
  begin
    Temp := EndDragX;
    EndDragX := StartDragX;
    StartDragX := Temp;
  end;
  Inc(StartDragX, 1);
  Inc(EndDragX, 1);

  with Context^ do
  begin
    if StartDragY = EndDragY then
      Str := Copy(Lines[StartDragY], StartDragX, EndDragX - StartDragX + 1)
    else
    begin
      Str := Copy(Lines[StartDragY], StartDragX, CONSOLE_WIDTH - StartDragX);
      if EndDragY - StartDragY > 1 then
        for y := StartDragY + 1 to EndDragY - 1 do
          Str := Str + Lines[y];
      Str := Str + Copy(Lines[EndDragY], 1, EndDragX);
    end;
  end;
  ClipBoard.SetTextBuf(PChar(Str));
  if Assigned(PreviousOnMouseUp) then
    PreviousOnMouseUp(Sender, Button, Shift, x, y);
end;

procedure TConsole.CopyContext(var AContext: TConsoleContext);
begin
  Move(Context^, AContext, Sizeof(TConsoleContext));
end;

procedure TConsole.SetContext(var AContext: TConsoleContext);
begin
  Move(AContext, Context^, Sizeof(TConsoleContext));
  Update;
end;

procedure TConsole.Update;
begin
  Refresh(Form);
end;

procedure TConsole.SetColors(FgColor, BgColor: TColor);
begin
  Forground := FgColor;
  Background := BgColor;
  Canvas.Font.Color := FgColor;
  Canvas.Brush.Color := BgColor;
  Canvas.FillRect(Canvas.ClipRect);
  Update;
end;

procedure TConsole.GotoXY(x, y: byte);
begin
  with Context^ do
  begin
    if x > CONSOLE_WIDTH then
      x := CONSOLE_WIDTH
    else if x = 0 then
      Inc(x, 1);
    if y > CONSOLE_HEIGHT then
      y := CONSOLE_HEIGHT
    else if y = 0 then
      Inc(y, 1);
    PosX := x - 1;
    PosY := y - 1;
  end;
end;

procedure TConsole.GotoEndOfLine(y: byte);
begin
  if y > CONSOLE_HEIGHT then
    y := CONSOLE_HEIGHT
  else if y = 0 then
    Inc(y, 1);
  with Context^ do
  begin
    PosY := y - 1;
    PosX := Length(Lines[PosY]);
  end;
end;

function TConsole.GetX: byte;
begin
  Result := Context^.PosX + 1;
end;

function TConsole.GetY: byte;
begin
  Result := Context^.PosY + 1;
end;

function TConsole.GetLastLine: byte;
begin
  Result := CONSOLE_HEIGHT;
end;

function TConsole.GetChar(x, y: byte): char;
begin
  with Context^ do
  begin
    if (x > CONSOLE_WIDTH) or (x = 0) or (y > CONSOLE_HEIGHT) or (y = 0) then
      Result := #0
    else
    begin
      Dec(y, 1);
      if x > Length(Lines[y]) then
        Result := ' '
      else
        Result := Lines[y - 1, x];
    end;
  end;
end;

function TConsole.GetText(y: byte): string;
begin
  if (y > CONSOLE_HEIGHT) or (y = 0) then
    Result := ''
  else
    Result := Context^.Lines[y - 1];
end;

procedure TConsole.Clear;
var
  y: byte;
begin
  with Context^ do
  begin
    for y := 0 to CONSOLE_HEIGHT - 1 do
      Lines[y] := '';
    PosX := 0;
    PosY := 0;
    KeyPressed := false;
    LastKey := #0;
    Canvas.FillRect(Rect(0, 0, (CONSOLE_OFFSET_X * 2) + (CONSOLE_FONT_WIDTH *
      CONSOLE_WIDTH), (CONSOLE_OFFSET_Y * 2) + (CONSOLE_FONT_HEIGHT * CONSOLE_HEIGHT)));
  end;
end;

procedure TConsole.ClearLn(y: byte);
begin
  if y > CONSOLE_HEIGHT then
    y := CONSOLE_HEIGHT
  else if y = 0 then
    Inc(y, 1);
  Dec(y, 1);
  with Context^ do
  begin
    Canvas.FillRect(Rect(0, CONSOLE_OFFSET_Y + (y * (CONSOLE_FONT_HEIGHT)),
      (CONSOLE_OFFSET_X * 2) + (CONSOLE_WIDTH - 1) * (CONSOLE_FONT_WIDTH + 1),
      (CONSOLE_OFFSET_Y * 2) + (y * (CONSOLE_FONT_HEIGHT)) + CONSOLE_FONT_HEIGHT));
    Lines[y] := '';
    PosX := 0;
    PosY := y;
  end;
end;

procedure TConsole.LineFeed;
var
  y: byte;
begin
  with Context^ do
  begin
    PosX := 0;
    if PosY = CONSOLE_HEIGHT - 1 then
    begin
      for y := 0 to CONSOLE_HEIGHT - 2 do
        Lines[y] := Lines[y + 1];
      Lines[CONSOLE_HEIGHT - 1] := '';
      Update;
    end
    else
      Inc(PosY, 1);
  end;
end;

procedure TConsole.Write(Str: string);
var
  StrLen, SubPos, SubLen, y, StartPosY: word;
begin
  with Context^ do
  begin
    StartPosY := PosY;
    StrLen := Length(Str);
    SubPos := 1;
    if StrLen + PosX < CONSOLE_WIDTH then
    begin
      SetLength(Lines[PosY], PosX + StrLen);
      Move(Str[1], Lines[PosY, PosX + 1], StrLen);
      Inc(PosX, StrLen);
    end
    else if StrLen + PosX = CONSOLE_WIDTH then
    begin
      SetLength(Lines[PosY], CONSOLE_WIDTH);
      Move(Str[1], Lines[PosY, PosX + 1], StrLen);
      LineFeed;
    end
    else
    begin
      SubLen := CONSOLE_WIDTH - Length(Lines[PosY]);
      repeat
        if PosX + 1 + SubLen > Length(Lines[PosY]) then
          SetLength(Lines[PosY], PosX + SubLen);
        Move(Str[SubPos], Lines[PosY, PosX + 1], SubLen);
        Inc(SubPos, SubLen);
        if SubPos < StrLen then
        begin
          LineFeed;
          if (StartPosY <> 0) and (PosY = CONSOLE_HEIGHT - 1) then
            Dec(StartPosY, 1);
        end
        else
          Inc(PosX, SubLen);
        SubLen := StrLen - SubPos + 1;
        if SubLen > CONSOLE_WIDTH then
          SubLen := CONSOLE_WIDTH;
      until ((SubLen + Length(Lines[PosY]) <= CONSOLE_WIDTH) and (SubPos >= StrLen))
        or (SubLen = 0);
      if SubPos < StrLen then
      begin
        SetLength(Lines[PosY], PosX + SubLen);
        Move(Str[SubPos], Lines[PosY, PosX + 1], SubLen);
        Inc(PosX, SubLen);
      end;
    end;
    for y := StartPosY to PosY do
      PaintLine(y);
  end;
end;

procedure TConsole.WriteLn(Str: string);
begin
  Write(Str);
  LineFeed;
end;

function TConsole.ReadKey: char;
begin
  with Context^ do
  begin
    KeyPressed := false;
    repeat
      Application.HandleMessage;
    until KeyPressed = true;
    Result := LastKey;
  end;
end;

function TConsole.ReadLength(Len: byte): string;
var
  StartPosX, StartPosY: byte;
  ClipBoardStr: array[0..255] of char;
  Key: char;
begin
  with Context^ do
  begin
    Form.OnKeyPress := KeyPress;
    Caret.Enabled := true;
    StartPosX := PosX;
    StartPosY := PosY;
    Result := '';
    repeat
      Key := ReadKey;
      if Key = #8 then
      begin
        if PosY > StartPosY then
        begin
          if PosX > 0 then
          begin
            Dec(PosX, 1);
            SetLength(Lines[PosY], Length(Lines[PosY]) - 1);
            SetLength(Result, Length(Result) - 1);
            Canvas.TextOut(CONSOLE_OFFSET_X + (PosX * (CONSOLE_FONT_WIDTH)),
              CONSOLE_OFFSET_Y + (PosY * (CONSOLE_FONT_HEIGHT)), ' ');
          end
          else
          begin
            Lines[PosY] := '';
            Dec(Posy, 1);
            PosX := CONSOLE_WIDTH - 1;
            SetLength(Lines[PosY], CONSOLE_WIDTH - 1);
            SetLength(Result, Length(Result) - 1);
            Canvas.TextOut(CONSOLE_OFFSET_X + (PosX * (CONSOLE_FONT_WIDTH)),
              CONSOLE_OFFSET_Y + (PosY * (CONSOLE_FONT_HEIGHT)), ' ');
          end;
        end
        else if PosX > StartPosX then
        begin
          Dec(PosX, 1);
          SetLength(Lines[PosY], Length(Lines[PosY]) - 1);
          SetLength(Result, Length(Result) - 1);
          Canvas.TextOut(CONSOLE_OFFSET_X + (PosX * (CONSOLE_FONT_WIDTH)),
            CONSOLE_OFFSET_Y + (PosY * (CONSOLE_FONT_HEIGHT)), ' ');
        end;
      end
      else if Key = #22 then
      begin
        ClipBoard.GetTextBuf(@ClipBoardStr, Len - Length(Result));
        Result := Result + StrPas(ClipBoardStr);
        Write(StrPas(ClipBoardStr));
      end
      else if (Key <> #13) and (Length(Result) <= Len) and (Key > #31) and (Key < #127)
        then
      begin
        Result := Result + Key;
        Lines[PosY] := Lines[PosY] + Key;
        Canvas.TextOut(CONSOLE_OFFSET_X + (PosX * (CONSOLE_FONT_WIDTH)),
          CONSOLE_OFFSET_Y + (PosY * (CONSOLE_FONT_HEIGHT)), Key);
        Inc(PosX, 1);
        if PosX = CONSOLE_WIDTH then
        begin
          if StartPosY <> 0 then
            Dec(StartPosY, 1)
          else
            StartPosX := 0;
          LineFeed;
          Refresh(Canvas);
        end;
      end;
      PaintCaret;
    until Key = #13;
    ShowCaret := false;
    Caret.Enabled := false;
    Form.OnKeyPress := PreviousOnKeyPress;
  end;
end;

function TConsole.Read: string;
begin
  Result := ReadLength(250);
end;

function TConsole.ReadLn: string;
begin
  Result := ReadLength(250);
  LineFeed;
end;

function TConsole.ReadLnLength(Len: byte): string;
begin
  if Len > 250 then
    Len := 250;
  Result := ReadLength(Len);
  LineFeed;
end;

end. //UNIT CONSOLE.PAS FINISHED

//*************************************************************************
//*************************** EXAMPLE ***************************************
//*************************************************************************

//Call: AConsole:=TConsole.Create(Form1); before calling TForm1.CommandPrompt;

procedure TForm1.CommandPrompt;
var
  Command: string;
  Parameters: array[0..9] of string;
  ParameterCount: byte;

  procedure ParseLine(c: string);
  var
    i: byte;
    Param: byte;
    Brackets: boolean;
  begin
    try
      Brackets := false;
      Param := 0;
      for i := 0 to 9 do
        Parameters[i] := '';
      for i := 1 to Length(c) do
      begin
        if c[i] = '"' then
        begin
          Brackets := not Brackets;
          if Brackets = false then
            Inc(Param, 1);
        end
        else if Brackets = true then
          Parameters[Param] := Parameters[Param] + c[i]
        else if (c[i] = ' ') and (c[i - 1] <> ' ') then
        begin
          Inc(Param, 1);
          if Param = 10 then
            Exit;
        end
        else
          Parameters[Param] := Parameters[Param] + c[i];
      end;
    finally
      ParameterCount := Param + 1;
      Parameters[0] := LowerCase(Parameters[0]);
    end;
  end;

  procedure CommandRun;
  begin
    with AConsole do
    begin
      if ParameterCount < 2 then
      begin
        Writeln('Use: run <path>');
        Writeln('   ex: run "c:\program files\myprogram.exe"');
        Writeln('');
        Exit;
      end;
      case WinExec(PChar(Parameters[1]), SW_SHOWNORMAL) of
        0: Writeln('The system is out of memory or resources.');
        ERROR_BAD_FORMAT:
          Writeln('The .EXE file is invalid (non-Win32 .EXE or error in .EXE image).');
        ERROR_FILE_NOT_FOUND: Writeln('The specified file was not found.');
        ERROR_PATH_NOT_FOUND: Writeln('The specified path was not found.');
      end;
    end;
  end;

  procedure CommandOpen;
  begin
    with AConsole do
    begin
      if ParameterCount < 2 then
      begin
        Writeln('Use: open <path>');
        Writeln('   ex: open "c:\my documents\finance.doc"');
        Writeln('');
        Exit;
      end;
      case ShellExecute(Application.Handle, 'open', PChar(Parameters[1]), nil, nil,
        SW_NORMAL) of
        0: Writeln('The operating system is out of memory or resources.');
        ERROR_FILE_NOT_FOUND: Writeln('The specified file was not found.');
        ERROR_PATH_NOT_FOUND: Writeln('The specified path was not found.');
        ERROR_BAD_FORMAT:
          Writeln('The .EXE file is invalid (non-Win32 .EXE or error in .EXE image).');
        SE_ERR_ACCESSDENIED:
          Writeln('The operating system denied access to the specified file.');
        SE_ERR_ASSOCINCOMPLETE:
          Writeln('The filename association is incomplete or invalid.');
        SE_ERR_DDEBUSY:
          Writeln('The DDE transaction could not be completed because other DDE transactions were being processed.');
        SE_ERR_DDEFAIL: Writeln('The DDE transaction failed.');
        SE_ERR_DDETIMEOUT:
          Writeln('The DDE transaction could not be completed because the request timed out.');
        SE_ERR_DLLNOTFOUND:
          Writeln('The specified dynamic-link library was not found.');
        SE_ERR_NOASSOC:
          Writeln('There is no application associated with the given filename extension.');
        SE_ERR_OOM: Writeln('There was not enough memory to complete the operation.');
        SE_ERR_SHARE: Writeln('A sharing violation occurred.');
      end;
    end;
  end;

  procedure CommandHelp;
  begin
    with AConsole do
    begin
      Writeln('The following commands are available:');
      Writeln('   run <path>     (starts an application)');
      Writeln('   open <path>    (opens a file with the associated application)');
      Writeln('   help           (displays this message)');
      Writeln('   exit           (ends the console session)');
      Writeln('');
    end;
  end;

begin
  with AConsole do
  begin
    GotoXY(0, GetLastLine);
    WriteLn('Welcome to DrMungkee''s demo console.');
    WriteLn('   Type ''help'' for a list of available commands.');
    repeat
      Write('>');
      Command := ReadLn;
      ParseLine(Command);
      if Parameters[0] = 'clear' then
        Clear
      else if Parameters[0] = 'run' then
        CommandRun
      else if Parameters[0] = 'open' then
        CommandOpen
      else if Parameters[0] = 'help' then
        CommandHelp
      else if Parameters[0] <> 'exit' then
      begin
        Writeln('Unknow Command (' + Parameters[0] + ')');
      end;
    until Parameters[0] = 'exit';
    AConsole.Free;
  end;
end;

2008. február 3., vasárnap

How to set the port for a specific printer


Problem/Question/Abstract:

I want to change the default printer and its settings (port) under Win 9x so that it affects all other applications for automated document printing to files (not from my application, from others like CorelDraw and Word...). I tried to this by changing the registry entries for the printers, but these changes only take effect after rebooting the system. Is there an API function that causes windows to update the printer settings from the registry? Or any other API function that directly affects the system wide printer settings?

Answer:

Setting a port for a specific printer:

uses
  WinSpool;

{ Function SetPrinterToPort
  Parameters :
    hPrinter: handle of printer to change, obtained from OpenPrinter
    port: port name to use, e.g. LPT1:, COM1:, FILE:
  Returns:
    The name of the previous port the printer was attached to.
  Description:
    Changes the port a printer is attached to using Win32 API functions.
                The changes made are NOT local to this process, they will affect all
                other processes that try to use this printer! It is recommended to set the
                port back to the old port returned by this function after
                the end of the print job.
  Error Conditions:
   Will raise EWin32Error exceptions if SetPrinter or GetPrinter fail.
  Created:
    21.10.99 by P. Below}

function SetPrinterToPort(hPrinter: THandle; const port: string): string;
var
  pInfo: PPrinterInfo2;
  bytesNeeded: DWORD;
begin
  {Figure out how much memory we need for the data buffer. Note that GetPrinter is
  supposed to fail with a specific error code here. The amount of memory will
        be larger than Sizeof(TPrinterInfo2) since variable amounts of data are appended
        to the record}
  SetLastError(NO_ERROR);
  GetPrinter(hPrinter, 2, nil, 0, @bytesNeeded);
  if GetLastError <> ERROR_INSUFFICIENT_BUFFER then
    RaiseLastWin32Error;
  pInfo := AllocMem(bytesNeeded);
  try
    if not GetPrinter(hPrinter, 2, pInfo, bytesNeeded, @bytesNeeded) then
      RaiseLastWin32Error;
    with pInfo^ do
    begin
      Result := pPortname;
      pPortname := @port[1];
    end;
    if not SetPrinter(hPrinter, 2, pInfo, 0) then
      RaiseLastWin32Error;
  finally
    FreeMem(pInfo);
  end;
end;

function GetCurrentPrinterHandle: THandle;
var
  Device, Driver, Port: array[0..255] of char;
  hDeviceMode: THandle;
begin
  Printer.GetPrinter(Device, Driver, Port, hDeviceMode);
  if not OpenPrinter(@Device, Result, nil) then
    RaiseLastWin32Error;
end;

2008. február 2., szombat

Optimization of work with standard TTreeView/TListView components


Problem/Question/Abstract:

Have a bad performance with standard TTreeView/TListView? Read this tip and implement the hints...

Answer:

If you uses the TTreeView and/or TListView from Win32 page of default component palette, then you must know that if you have the large amount nodes, you have a very bad performance...

Of course, at same moment you'll try to find a some other third-party component that allow to work with your very large data but I want to give you the few hints which allows to increase a performance without any third-party components. Only using of optimized code.

Tip1:

if you need add a lot of nodes in same time (for example, after button click to load the 10000 nodes in tree from some source) then you must call:

yourTreeView.Items.BeginUpdate;

yourTreeView.Items.EndUpdate;

This constuction will disable a repainting when you append the nodes - it's save a lot of time!

Tip2:

if you uses the some navigation by nodes, you must use the GetFirst and GetNext methods instead Items[i] using!

For example:

var
  node: TTreeNode;
begin
  node := yourTreeView.Items.GetFirstNode;
  repeat
    node := Result.GetNext;
  until node = nil;
end;

It's save a lot of time too! The GetFirstNode/GetNext is faster than standard

for i := 0 to yourTreeView.Items.Count - 1 do
begin
  node := yourTreeView.Items[i];
end;

Tip3:

Also, when adding lots of items, you could do

MyObj.AllocBy := 10000;

This will cause fewer allocations as it allocates more items each time.

For example, in own warehouse system I have a treeview with 5000 nodes which I load from Oracle resultset. After applying of these tips, the time of execution of procedure with append was decreased from 4-5 minutes to 15-20
seconds! Trust me :-)

I don't sure but I think that it's a bad work of Borland when team developed the envelope for Win's treeview/listview. But maybe I'm wrong.

PS: of course, if you have a very-very large data with few billions of nodes or after applying of tips above all the same you have a bad performance, you must use the virtual mode of control or really select the other third-party
control. But I suggest to change the your interface logic - to navigate thru few billions of nodes in same time is very hard task for user! Not only for you :-)

2008. február 1., péntek

How to lock a CD-ROM drive


Problem/Question/Abstract:

How can I prevent a CD from being ejected from a CD-ROM drive through code?

Answer:

The code below only works with Windows NT 4, 2000 and XP:

{NTStyle}

function CTL_Code(DeviceType, _Function, Method, Access: Integer): DWord;
begin
  Result := (DeviceType shl 16) or (Access shl 14) or (_Function shl 2) or Method;
end;

type
  TPreventMediaRemoval = packed record
    PreventMediaRemoval: Boolean;
  end;

const
  METHOD_BUFFERED = 0;
  FILE_READ_ACCESS = 1;
  IOCTL_STORAGE_BASE = $2D;
  IOCTL_STORAGE_MEDIA_REMOVAL_FUNCTION = $201;

procedure NTStyleTrayLock(Drive: Char; Lock: Boolean);
var
  Device: THandle;
  IOCTL_STORAGE_MEDIA_REMOVAL: DWord;
  BytesReturned: Cardinal;
  InBuffer: TPreventMediaRemoval;
begin
  IOCTL_STORAGE_MEDIA_REMOVAL := CTL_Code(IOCTL_STORAGE_BASE,
    IOCTL_STORAGE_MEDIA_REMOVAL_FUNCTION,
    METHOD_BUFFERED, FILE_READ_ACCESS);

  Device := CreateFile(PChar(Format('\\.\%s:', [UpCase(Drive)])), GENERIC_ALL,
    FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
  if Device = INVALID_HANDLE_VALUE then
    RaiseLastWin32Error;
  try
    InBuffer.PreventMediaRemoval := Lock;
    Win32Check(DeviceIoControl(Device, IOCTL_STORAGE_MEDIA_REMOVAL, @InBuffer,
      sizeof(InBuffer), nil, 0, BytesReturned, nil));
  finally
    FileClose(Device);
  end;
end;

{UI (here: Drive W:)}

procedure TForm1.btnLockClick(Sender: TObject);
begin
  NTStyleTrayLock('W', True);
end;

procedure TForm1.btnUnLockClick(Sender: TObject);
begin
  NTStyleTrayLock('W', False);
end;