2008. november 14., péntek

Color TDBGrid


Problem/Question/Abstract:

How to color TDBGrid

Answer:

Function to color a DBGrid (declared as private)

procedure TForm1.ColorGrid(dbgIn: TDBGrid; qryIn: TQuery; const Rect: TRect;
  DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
  iValue: LongInt;
begin
  // color only the first field
  if (DataCol = 0) then
  begin
    // Check the field value and assign a color
    iValue := qryIn.FieldByName('HINWEIS_COLOR').AsInteger;
    case iValue of
      1: dbgIn.Canvas.Brush.Color := clGreen;
      2: dbgIn.Canvas.Brush.Color := clLime;
      3: dbgIn.Canvas.Brush.Color := clYellow;
      4: dbgIn.Canvas.Brush.Color := clRed;
    end;
    // Draw the field
    dbgIn.DefaultDrawColumnCell(Rect, DataCol, Column, State);
  end;
end;

procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject;
  const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState);
begin
  ColorGrid(DBGrid1, Query1, Rect, DataCol, Column, State);
end;

2008. november 13., csütörtök

How to check italian code for companies


Problem/Question/Abstract:

In Italy companies have a code for use with fiscal transactions. There's a checksum digit.
This function not only checks for its validity, but also retrieves some interesting information
from the code: the progressive number a company has in his state (provincia) and the country
that generated that code.

Answer:

const
  Provincie: array[0..102] of string =
  ('Torino', 'Vercelli', 'Novara', 'Cuneo', 'Asti',
    'Alessandria', 'Aosta', 'Imperia', 'Savona', 'Genova',
    'La Spezia', 'Varese', 'Como', 'Sondrio', 'Milano',
    'Bergamo', 'Brescia', 'Pavia', 'Cremona', 'Mantova',
    'Bolzano-Bozen', 'Trento', 'Verona', 'Vicenza', 'Belluno',
    'Treviso', 'Venezia', 'Padova', 'Rovigo', 'Udine',
    'Gorizia', 'Trieste', 'Piacenza', 'Parma', 'Reggio nell''Emilia',
    'Modena', 'Bologna', 'Ferrara', 'Ravenna', 'Forli''-Cesena',
    'Pesaro e Urbino', 'Ancona', 'Macerata', 'Ascoli Piceno', 'Massa-Carrara',
    'Lucca', 'Pistoia', 'Firenze', 'Livorno', 'Pisa',
    'Arezzo', 'Siena', 'Grosseto', 'Perugia', 'Terni',
    'Viterbo', 'Rieti', 'Roma', 'Latina', 'Frosinone',
    'Caserta', 'Benevento', 'Napoli', 'Avellino', 'Salerno',
    'L''Aquila', 'Teramo', 'Pescara', 'Chieti', 'Campobasso',
    'Foggia', 'Bari', 'Taranto', 'Brindisi', 'Lecce',
    'Potenza', 'Matera', 'Cosenza', 'Catanzaro', 'Reggio di Calabria',
    'Trapani', 'Palermo', 'Messina', 'Agrigento', 'Caltanissetta',
    'Enna', 'Catania', 'Ragusa', 'Siracusa', 'Sassari',
    'Nuoro', 'Cagliari', 'Pordenone', 'Isernia', 'Oristano',
    'Biella', 'Lecco', 'Lodi', 'Rimini', 'Prato',
    'Crotone', 'Vibo Valentia', 'Verbano-Cusio-Ossola'
    );

function PartitaIVA(code: string; var Progressive: integer; var Provincia: string):
  boolean;
  function ReduceSum(n: Integer): Integer;
  var
    i: Integer;
    s: string;
  begin
    s := inttostr(n);
    if (length(s) = 1) then
    begin
      result := n;
      exit;
    end;
    result := 0;
    for i := 1 to length(s) do
    begin
      result := result + strtointdef(s[i], 0);
    end;
  end;
  function ReduceNum(n: Integer): Integer;
  var
    s: string;
  begin
    result := n;
    s := inttostr(n);
    if (length(s) > 1) then
    begin
      result := strtointdef(s[length(s)], 0)
    end;
  end;
var
  i: Integer;
  c: Integer;
begin
  result := false;
  if (length(code) <> 11) then
  begin
    provincia := '11 numeric-characters needed!';
    raise exception.Create(provincia);
    exit;
  end;
  for i := 1 to 11 do
  begin
    if (not (code[i] in ['0'..'9'])) then
    begin
      provincia := '"' + code[i] + '" is not a numeric value!';
      raise exception.Create(provincia);
      exit;
    end;
  end;
  // Returns the town.
  i := strtointdef(copy(code, 8, 3), 0) - 1;
  if ((i < 0) or (i > 102)) then
  begin
    provincia := 'Value out of set!';
    raise exception.create(provincia);
    exit;
  end
  else
    provincia := provincie[i];
  // Returns the progressive number.
  progressive := strtointdef(copy(code, 1, 7), 0);
  // Calculates if is valid.
  c := 0;
  for i := 1 to 10 do
  begin
    if ((i mod 2) = 0) then
      inc(c, reducesum(strtointdef(code[i], 0) * 2))
    else
      inc(c, strtointdef(code[i], 0));
  end;
  result := ((10 - ReduceNum(c)) = strtointdef(code[11], -1));
end;

2008. november 12., szerda

Text to GIF


Problem/Question/Abstract:

Text to GIF

Answer:

procedure TxtToGif(txt, FileName: string);
var
  temp: TBitmap;
  GIF: TGIFImage;
begin

  temp := TBitmap.Create;
  try
    temp.Height := 400;
    temp.Width := 60;
    temp.Transparent := True;
    temp.Canvas.Brush.Color := colFondo.ColorValue;
    temp.Canvas.Font.Name := Fuente.FontName;
    temp.Canvas.Font.Color := colFuente.ColorValue;
    temp.Canvas.TextOut(10, 10, txt);
    Imagen.Picture.Assign(nil);

    GIF := TGIFImage.Create;
    try
      // Convert the bitmap to a GIF
      GIF.Assign(Temp);
      // Save the GIF
      GIF.SaveToFile(FileName);
      // Display the GIF
      Imagen.Picture.Assign(GIF);
    finally
      GIF.Free;
    end;

  finally

    temp.Destroy;
  end;
end;

2008. november 11., kedd

Boyer-Moore string searching


Problem/Question/Abstract:

Boyer-Moore string searching

Answer:

Solve 1:

unit BMSearch;

interface

type
{$IFDEF WINDOWS}
  size_t = Word;
{$ELSE}
  size_t = LongInt;
{$ENDIF}

type
  TTranslationTable = array[char] of char; { translation table }
  TSearchBM = class(TObject)
  private
    FTranslate: TTranslationTable; { translation table }
    FJumpTable: array[char] of Byte; { Jumping table }
    FShift_1: integer;
    FPattern: pchar;
    FPatternLen: size_t;
  public
    procedure Prepare(Pattern: pchar; PatternLen: size_t; IgnoreCase: Boolean);
    procedure PrepareStr(const Pattern: string; IgnoreCase: Boolean);
    function Search(Text: pchar; TextLen: size_t): pchar;
    function Pos(const S: string): integer;
  end;

implementation

uses
  SysUtils;

{Ignore Case Table Translation}

procedure CreateTranslationTable(var T: TTranslationTable; IgnoreCase: Boolean);
var
  c: char;
begin
  for c := #0 to #255 do
    T[c] := c;
  if not IgnoreCase then
    exit;
  for c := 'a' to 'z' do
    T[c] := UpCase(c);

  { Mapping all accented characters to their uppercase equivalent }

  T['�'] := 'A';
  T['�'] := 'A';
  T['�'] := 'A';
  T['�'] := 'A';

  T['�'] := 'A';
  T['�'] := 'A';
  T['�'] := 'A';
  T['�'] := 'A';

  T['�'] := 'E';
  T['�'] := 'E';
  T['�'] := 'E';
  T['�'] := 'E';

  T['�'] := 'E';
  T['�'] := 'E';
  T['�'] := 'E';
  T['�'] := 'E';

  T['�'] := 'I';
  T['�'] := 'I';
  T['�'] := 'I';
  T['�'] := 'I';

  T['�'] := 'I';
  T['�'] := 'I';
  T['�'] := 'I';
  T['�'] := 'I';

  T['�'] := 'O';
  T['�'] := 'O';
  T['�'] := 'O';
  T['�'] := 'O';

  T['�'] := 'O';
  T['�'] := 'O';
  T['�'] := 'O';
  T['�'] := 'O';

  T['�'] := 'U';
  T['�'] := 'U';
  T['�'] := 'U';
  T['�'] := 'U';

  T['�'] := 'U';
  T['�'] := 'U';
  T['�'] := 'U';
  T['�'] := 'U';

  T['�'] := '�';
end;

{Preparation of the jumping table}

procedure TSearchBM.Prepare(Pattern: pchar; PatternLen: size_t; IgnoreCase: Boolean);
var
  i: integer;
  c, lastc: char;
begin
  FPattern := Pattern;
  FPatternLen := PatternLen;
  if FPatternLen < 1 then
    FPatternLen := strlen(FPattern);
  {This algorythm is based on a character set of 256}
  if FPatternLen > 256 then
    exit;
  {1. Preparing translating table}
  CreateTranslationTable(FTranslate, IgnoreCase);
  {2. Preparing jumping table}
  for c := #0 to #255 do
    FJumpTable[c] := FPatternLen;
  for i := FPatternLen - 1 downto 0 do
  begin
    c := FTranslate[FPattern[i]];
    if FJumpTable[c] >= FPatternLen - 1 then
      FJumpTable[c] := FPatternLen - 1 - i;
  end;
  FShift_1 := FPatternLen - 1;
  lastc := FTranslate[Pattern[FPatternLen - 1]];
  for i := FPatternLen - 2 downto 0 do
    if FTranslate[FPattern[i]] = lastc then
    begin
      FShift_1 := FPatternLen - 1 - i;
      break;
    end;
  if FShift_1 = 0 then
    FShift_1 := 1;
end;

procedure TSearchBM.PrepareStr(const Pattern: string; IgnoreCase: Boolean);
var
  str: pchar;
begin
  if Pattern <> '' then
  begin
{$IFDEF Windows}
    str := @Pattern[1];
{$ELSE}
    str := pchar(Pattern);
{$ENDIF}
    Prepare(str, Length(Pattern), IgnoreCase);
  end;
end;

{Searching Last char & scanning right to left}

function TSearchBM.Search(Text: pchar; TextLen: size_t): pchar;
var
  shift, m1, j: integer;
  jumps: size_t;
begin
  result := nil;
  if FPatternLen > 256 then
    exit;
  if TextLen < 1 then
    TextLen := strlen(Text);
  m1 := FPatternLen - 1;
  shift := 0;
  jumps := 0;
  {Searching the last character}
  while jumps <= TextLen do
  begin
    Inc(Text, shift);
    shift := FJumpTable[FTranslate[Text^]];
    while shift <> 0 do
    begin
      Inc(jumps, shift);
      if jumps > TextLen then
        exit;
      Inc(Text, shift);
      shift := FJumpTable[FTranslate[Text^]];
    end;
    { Compare right to left FPatternLen - 1 characters }
    if jumps >= m1 then
    begin
      j := 0;
      while FTranslate[FPattern[m1 - j]] = FTranslate[(Text - j)^] do
      begin
        Inc(j);
        if j = FPatternLen then
        begin
          result := Text - m1;
          exit;
        end;
      end;
    end;
    shift := FShift_1;
    Inc(jumps, shift);
  end;
end;

function TSearchBM.Pos(const S: string): integer;
var
  str, p: pchar;
begin
  result := 0;
  if S <> '' then
  begin
{$IFDEF Windows}
    str := @S[1];
{$ELSE}
    str := pchar(S);
{$ENDIF}
    p := Search(str, Length(S));
    if p <> nil then
      result := 1 + p - str;
  end;
end;

end.


Solve 2:

Here's a demo program of the Boyer-Moore search algorithm. The basic idea is to first create a Boyer-Moore index table for the string you want to search for, and then call the BMsearch routine. Remember to turn-off Range Checking {$R-} in your finished program, otherwise the BMSearch will take 3-4 times longer than it should.


{Public-domain demo of Boyer-Moore search algorithm.
Guy McLoughlin - May 1, 1993.}

program DemoBMSearch;

{Boyer-Moore index table data definition}
type
  BMTable = array[0..127] of byte;

  {Create a Boyer-Moore index table to search with.}

procedure Create_BMTable(Pattern: string; var BMT: BMTable);
var
  Index: byte;
begin
  fillchar(BMT, sizeof(BMT), length(Pattern));
  for Index := 1 to length(Pattern) do
    BMT[ord(Pattern[Index])] := (length(Pattern) - Index)
end;

{Boyer-Moore Search function. Returns 0 if string is not found. Returns 65,535 if
BufferSize is too large, ie: greater than 65,520 bytes.}

function BMsearch(var Buffer; BuffSize: word; var BMT: BMTable; Pattern: string): word;
var
  Buffer2: array[1..65520] of char absolute Buffer;
  Index1, Index2, PatSize: word;
begin
  if (BuffSize > 65520) then
  begin
    BMsearch := $FFFF;
    exit
  end;
  PatSize := length(Pattern);
  Index1 := PatSize;
  Index2 := PatSize;
  repeat
    if (Buffer2[Index1] = Pattern[Index2]) then
    begin
      dec(Index1);
      dec(Index2)
    end
    else
    begin
      if (succ(PatSize - Index2) > (BMT[ord(Buffer2[Index1])])) then
        inc(Index1, succ(PatSize - Index2))
      else
        inc(Index1, BMT[ord(Buffer2[Index1])]);
      Index2 := PatSize
    end;
  until
    (Index2 < 1) or (Index1 > BuffSize);
  if (Index1 > BuffSize) then
    BMsearch := 0
  else
    BMsearch := succ(Index1)
end;

type
  arby_64K = array[1..65520] of byte;

var
  Index: word;
  st_Temp: string[10];
  Buffer: ^arby_64K;
  BMT: BMTable;

begin
  new(Buffer);
  fillchar(Buffer^, sizeof(Buffer^), 0);
  st_Temp := 'Gumby';
  move(st_Temp[1], Buffer^[65516], length(st_Temp));
  Create_BMTable(st_Temp, BMT);
  Index := BMSearch(Buffer^, sizeof(Buffer^), BMT, st_Temp);
  writeln(st_Temp, ' found at offset ', Index)
end.

2008. november 10., hétfő

Trap the OnEnter and OnLeave events


Problem/Question/Abstract:

This code shows how to get the OnEnter and OnLeave event from components without changing the component.

Answer:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    FFocusControl: TControl;
    procedure ApplicationIdle(Sender: TObject; var Done: Boolean);
  public
    { Public declarations }
    procedure OnEnter(Sender: TObject);
    procedure OnExit(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FFocusControl := nil;
  Application.OnIdle := ApplicationIdle;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Application.OnIdle := nil;
end;

procedure TForm1.ApplicationIdle(Sender: TObject; var Done: Boolean);
var
  CurControl: TControl;
  P: TPoint;
begin
  GetCursorPos(P);
  CurControl := FindDragTarget(P, True);
  if FFocusControl <> CurControl then
  begin
    if FFocusControl <> nil then
      OnExit(FFocusControl);
    FFocusControl := CurControl;
    if FFocusControl <> nil then
      OnEnter(FFocusControl);
  end;
end;

procedure TForm1.OnEnter(Sender: TObject);
begin
  //OnEnter code
  if sender = Button1 then
  begin
    Label1.caption := 'Hello';
    Button1.Caption := 'Exit';
  end;
end;

procedure TForm1.OnExit(Sender: TObject);
begin
  //OnExit code
  if sender = Button1 then
  begin
    Label1.caption := 'Godbye';
    Button1.Caption := 'Enter';
  end;
end;

end.

2008. november 9., vasárnap

How to check what type of component is the Sender


Problem/Question/Abstract:

The piece of code below is typical of some routines we have. This example only uses 2 component types but sometimes there could be 7 or 8 and then the code is really bad.

procedure TEN1FORM.myonEnter(Sender: TObject);
begin
  if Sender is TDBComboBox then
    enterstr := TDBComboBox(Sender).text
  else if Sender is TOVCDBPICTUREFIELD then
    enterstr := TOVCdbPICTUREFIELD(Sender).text;
end;

Answer:

Solve 1:

function CheckIsType(AObject: TObject; const ClassArray: array of TClass): Integer;
{Return an index indicative of a type match on the object, and array of types passed}
begin
  for Result := 0 to High(ClassArray) do
    if AObject is ClassArray[Result] then
      Exit;
  Result := -1;
end;

{ ... }
case CheckIsType(Sender, [TdbComboBox, TOVCDBPctureField, TSpeedButton, TEdit]) of
  0: { ... }; {TDBComboBox}
  1: { ... }; {TOVCDBPctureField}
  2: { ... }; {TSpeedButton}
  3: { ... }; {TEdit}
end;
{ ... }

I also use the following routine which does an exact type match:

function CheckType(AObject: TObject; const ClassArray: array of TClass): Integer;
var
  Index: Integer;
begin
  Result := -1;
  if Assigned(AObject) then
    for Index := 0 to High(ClassArray) do
      if AObject.ClassType = ClassArray[Index] then
      begin
        Result := Index;
        Exit;
      end;
end;


Solve 2:

Let me add one more way to tackle this: use run-time type information. Text is a published property so RTTI exists for it. So you can use the stuff in Unit TypInfo to access it.

uses
  TypInfo;

function GetStrProperty(anObj: TObject; const propname: string): string;
var
  PInfo: PPropInfo;
begin
  result := EmptyStr;
  PInfo := GetPropInfo(anObj.ClassInfo, propname);
  if PInfo <> nil then
    {found aproperty with this name, check if it has the correct type}
    if PInfo^.Proptype^.Kind in [tkString, tkLString] then
    begin
      {it has! Get the string value from theproperty}
      Result := GetStrProp(anObj, PInfo);
    end;
end;

Using this function your handler becomes

procedure TEN1FORM.myonEnter(Sender: TObject);
begin
  enterstr := GetStrProperty(sender, 'text');
  { ... }

2008. november 8., szombat

How to limit the number of characters per line and the number of lines in a TMemo (2)


Problem/Question/Abstract:

Is there a way to place a maximum number of lines in a TMemo?

Answer:

procedure TForm1.Memo1Change(Sender: TObject);
var
  i: Integer;
  s: string;
begin
  i := Length(Memo1.Lines.Text);
  {Limit the number of lines to 3}
  if Memo1.Lines.Count > 3 then
  begin
    s := Memo1.Lines.Text;
    Delete(s, i, 1);
    {Remove line wrap}
    i := Length(s);
    while (Ord(s[i]) = 10) or (Ord(s[i]) = 13) do
    begin
      Delete(s, i, 1);
      i := Length(s);
    end;
    Memo1.Lines.Text := s;
    {posistion cursor at end of memo}
    Memo1.SelStart := Length(s);
    Memo1.SelLength := 0;
    ShowMessage('Over Max Lines');
  end;
end;

2008. november 7., péntek

How to create a TProgressBar inside a TListView


Problem/Question/Abstract:

I would like to display a progress bar as a sub item in a TListView (vsReport mode). How can I do that?

Answer:

Well, you can in fact parent a live progressbar to a listview, you just have to do it at run-time. The sample below has timer that randomly steps the progress bars. The bar is added to the last column of the listview.

There are some gotchas here: the DisplayRect method of a listitem does not return the correct position unless the control is visible, thus the hack used at the top of the method to ensure this. And of course you will have to adjust the width and left bound of the progress bars if the user resizes columns. And you need to add new bars if the user can add items and destroy bars if the user can delete item.

procedure TForm1.FormCreate(Sender: TObject);
var
  pb: TProgressBar;
  r: TRect;
  i, k: Integer;
begin
  Show;
  Application.ProcessMessages;
  for i := 0 to listview1.items.count - 1 do
  begin
    r := listview1.items[i].DisplayRect(drBounds);
    {last column is to take progress bar}
    for k := 1 to listview1.columns.Count - 1 do
      r.left := r.left + listview1.columns[k - 1].Width;
    r.right := r.Left + listview1.columns[listview1.columns.Count - 1].Width;
    pb := TProgressBar.Create(self);
    pb.Parent := listview1;
    pb.BoundsRect := r;
    listview1.items[i].Data := pb;
  end;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  i: Integer;
  pb: TProgressbar;
begin
  i := Random(listview1.items.count);
  pb := TProgressBar(listview1.Items[i].Data);
  if assigned(pb) then
    if pb.Position = pb.Max then
      pb.Position := 0
    else
      pb.StepBy(pb.Max div 10);
end;

2008. november 6., csütörtök

Converting MS-Word DOC to RTF using OLE


Problem/Question/Abstract:

I want to load a Word document into a RitchText control

Answer:

Open a new Application and place:

  a button named Button3,
  a RitchText object named WordEditor
  and an OpenDialog component.

From now on, you can browse for any *.doc file and load it into the RitchText object.

NOTE: Format:=6 instructs Word to save the file as RTF. Extension is not enough.

Other File Formats:

Argument Format  
File Format
0
Normal (Word format)
1
Document Template
2
Text Only (extended characters saved in ANSI character set)
3
Text+Breaks (plain text with line breaks; extended characters saved in ANSI character set)
4
Text Only (PC-8) (extended characters saved in IBM PC character set)
5
Text+Breaks (PC-8) (text with line breaks; extended characters saved in IBM PC character set)
6
Rich-text format (RTF)



procedure TImport_Form.ToolButton3Click(Sender: TObject);
var
  WordApp: Variant;
begin
  if OpenDialog1.Execute then
  begin
    Edit1.Text := ExtractFileName(OpenDialog1.FileName);
    StatusBar1.SimpleText := OpenDialog1.FileName;
    WordApp := CreateOleObject('Word.Basic');
    if not VarIsEmpty(WordApp) then
    begin
      WordApp.FileOpen(OpenDialog1.FileName);
      WordApp.FileSaveAs(Name := 'c:\temp_bb.rtf', Format := 6);
      WordApp.AppClose;
      WordApp := Unassigned;
      WordEditor.Lines.LoadFromFile('c:\temp_bb.rtf');
    end
    else
      ShowMessage('Could not start MS Word');
  end;

end;

How to prevent word from opening password-protected files or resume wizard files and sometimes causing application to hang ?

The sollution is to add the folowing query before openning the document:

if WordApp.ActiveDocument.HasPassword = True then
  MsgBox("Password Protected");

You can even preset the password propery as:

WordApp.Password := 'mypassword";

NOTE: If the above code generates an "Undefined property: ActiveDocument" change the:

CreateOleObject('Word.Basic');

with

CreateOleObject('Word.Application');

2008. november 5., szerda

How to detect a sound device


Problem/Question/Abstract:

Is there a way to detect a sound device (personally, I want to detect a sound card) to know if such a device is present on the computer my application is running?

Answer:

Solve 1:

{ ... }
if WaveOutGetNumDevs > 0 then
  ShowMessage('Wave-Device present')
else
  ShowMessage('No Wave-Device present');
{ ... }


Solve 2:

function IsSoundCardInstalled: Boolean;
type
  SCFunc = function: UInt; stdcall;
var
  LibInst: LongInt;
  EntryPoint: SCFunc;
begin
  Result := False;
  LibInst := LoadLibrary(PChar('winmm.dll'));
  try
    if LibInst <> 0 then
    begin
      EntryPoint := GetProcAddress(LibInst, 'waveOutGetNumDevs');
      if (EntryPoint <> 0) then
        Result := True;
    end;
  finally
    if (LibInst <> 0) then
      FreeLibrary(LibInst);
  end;
end;

2008. november 4., kedd

Replace Text in Bookmarks in WORD


Problem/Question/Abstract:

How can I replace Text in Bookmarks in WORD?

Answer:

When Word is connected via OLE, you can use bookmarks to fill in text into an existing template or document.

The first step is connecting to WORD, either with an OLE-Object or in an OLE-Control.

The Server You connect to should be WORD.Document, not WORD.Application. With this, it is easier to control that You always word on the right Document.

Finding an Replacing a bookmark goes like that:

var
  Doc: Variant;
  Result: string;
  Bookmark: string;
  Startpos, Endpos: longint;
begin
  // You already are connected to a WORD.Document Object!
  //
  Result := 'anything';
  Bookmark := 'bookmark 20';
  // Replace the Text:
  Doc.Bookmarks.Items('bookmark 20').range.text := Result;
  // done this, You have lost the Bookmark, but integrated the Text
end;

2008. november 3., hétfő

Sending an email from Delphi with Outlook


Problem/Question/Abstract:

Sending an email from Delphi with Outlook

Answer:

Outlook can be easily controlled through OLE. Try the sample procedure SendOutlookMail() from below.

This does not work with Outlook Express.


program MailWithOutlook;

procedure SendOutlookMail;
const
  olMailItem = 0;
var
  Outlook: OleVariant;
  vMailItem: variant;
begin
  try
    Outlook := GetActiveOleObject('Outlook.Application');
  except
    Outlook := CreateOleObject('Outlook.Application');
  end;
  vMailItem := Outlook.CreateItem(olMailItem);
  vMailItem.Recipients.Add('dummy@hotmail.com');
  vMailItem.Subject := 'test email';
  vMailItem.Body := 'This is a test';
  vMailItem.Attachments.Add('C:\temp\sample.txt');
  vMailItem.Send;

  VarClear(Outlook);
end;

end.

2008. november 2., vasárnap

How to save / load a ScanLine to / from a stream


Problem/Question/Abstract:

How to save / load a ScanLine to / from a stream

Answer:

I won't claim this is fastest in any way, but it shows how to write scanlines to a stream and then read the stream to fill in the scanlines.

Example of flipping a bitmap by writing to MemoryStream and loading second bitmap in flipped order.

unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var
  j: Integer;
  Bitmap1: TBitmap;
  Bitmap2: TBitmap;
  row: pByteArray;
  ScanlineBytes: Integer;
  Stream: TMemoryStream;
begin
  Bitmap1 := TBitmap.Create;
  try
    {Assume Image1 and Image2 are the same size}
    Bitmap1.Width := Image1.Width;
    Bitmap1.Height := Image1.Height;
    Bitmap1.PixelFormat := pf24bit;
    {Make something assymmetric in picture}
    Bitmap1.Canvas.Pen.Color := clRed;
    Bitmap1.Canvas.MoveTo(0, 0);
    Bitmap1.Canvas.LineTo(Bitmap1.Width, Bitmap1.Height);
    Bitmap1.Canvas.MoveTo(Bitmap1.Width div 2, 0);
    Bitmap1.Canvas.LineTo(0, Bitmap1.Height div 2);
    Image1.Picture.Graphic := Bitmap1;
    Stream := TMemoryStream.Create;
    try
      ScanlineBytes := ABS(Integer(Bitmap1.Scanline[1]) - Integer(Bitmap1.Scanline[0]));
      for j := 0 to Bitmap1.Height - 1 do
      begin
        row := Bitmap1.Scanline[j];
        Stream.Write(row[0], ScanlineBytes);
      end;
      Bitmap2 := TBitmap.Create;
      try
        Bitmap2.Width := Bitmap1.Width;
        Bitmap2.Height := Bitmap1.Height;
        Bitmap2.PixelFormat := pf24bit;
        {position stream pointer at beginning}
        Stream.Position := 0;
        {Flip bitmap by reading scanlines from stream and placing them in flipped row}
        for j := Bitmap2.Height - 1 downto 0 do
        begin
          row := Bitmap2.Scanline[j];
          Stream.Read(row[0], ScanlineBytes)
        end;
        Image2.Picture.Graphic := Bitmap2
      finally
        Bitmap2.Free
      end
    finally
      Stream.Free
    end
  finally
    Bitmap1.Free
  end;
end;

end.

2008. november 1., szombat

Perform a file search including subdirectories


Problem/Question/Abstract:

How to perform a file search including subdirectories

Answer:

Solve 1:

Recursively scanning all drives:

{excerpt from form declaration, form has a listbox1 for the  results, a label1 for progress, a button2 to start the scan, an edit1 to get the search mask from, a button3 to stop the scan.}
private
{ Private declarations }
FScanAborted: Boolean;
public
{ Public declarations }

function ScanDrive(root, filemask: string; hitlist: TStrings): Boolean;

function TForm1.ScanDrive(root, filemask: string; hitlist: TStrings): Boolean;

  function ScanDirectory(var path: string): Boolean;
  var
    SRec: TSearchRec;
    pathlen: Integer;
    res: Integer;
  begin
    label1.caption := path;
    pathlen := Length(path);
    { first pass, files }
    res := FindFirst(path + filemask, faAnyfile, SRec);
    if res = 0 then
    try
      while res = 0 do
      begin
        hitlist.Add(path + SRec.Name);
        res := FindNext(SRec);
      end;
    finally
      FindClose(SRec)
    end;
    Application.ProcessMessages;
    Result := not (FScanAborted or Application.Terminated);
    if not Result then
      Exit;
    {second pass, directories}
    res := FindFirst(path + ' *.* ', faDirectory, SRec);
    if res = 0 then
    try
      while (res = 0) and Result do
      begin
        if ((Srec.Attr and faDirectory) = faDirectory) and (Srec.name <> ' . ')
          and (Srec.name <> ' .. ') then
        begin
          path := path + SRec.name + '\';
          Result := ScanDirectory(path);
          SetLength(path, pathlen);
        end;
        res := FindNext(SRec);
      end;
    finally
      FindClose(SRec)
    end;
  end;

begin
  FScanAborted := False;
  Screen.Cursor := crHourglass;
  try
    Result := ScanDirectory(root);
  finally
    Screen.Cursor := crDefault
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  ch: Char;
  root: string;
begin
  root := 'C:\';
  for ch := 'A' to 'Z' do
  begin
    root[1] := ch;
    case GetDriveType(Pchar(root)) of
      DRIVE_FIXED, DRIVE_REMOTE:
        if not ScanDrive(root, edit1.text, listbox1.items) then
          Break;
    end;
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin {aborts scan}
  fScanAborted := True;
end;


Solve 2:

procedure TFrmRecurseDirTree.RecurseDirTree(APath: string; AList: TStrings);
var
  searchRec: TSearchRec;
  thePath: string;
begin
  if (Length(thePath) > 0) then
    Exit;
  {Riffle through the subdirectories and find the file(s) there}
  thePath := APath;
  if (thePath[Length(thePath)] <> '\') then
    thePath := thePath + '\';
  if FindFirst(thePath + '*.*', faDirectory, searchRec) = 0 then
  try
    repeat
      if (searchRec.Attr and faDirectory > 1) and (searchRec.Name <> '.') and
        (searchRec.Name <> '..') then
      begin
        AList.Add(thePath + searchRec.Name);
        RecurseDirTree(thePath + searchRec.Name + '\', AList);
        Application.ProcessMessages;
      end;
    until
      FindNext(searchRec) <> 0;
  finally
    SysUtils.FindClose(searchRec);
  end;
end;


Solve 3:

Here is a procedure to scan for all bitmaps below the current directory and add them to a list. It can easily be modified to add all sub-directories to the list, just add "List.Add..." just before "ScanDirectory..." and delete the part that adds the bitmap filenames. Maybe it's better to change faAnyFile to faDirecory, but I am not sure if this will return all directories including hidden ones etc.

procedure TForm1.ScanDirectory(Path: string; List: TStringList; SubDirFlag: Boolean);
var
  SearchRec: TSearchRec;
  Ext: string;
begin
  if Path[Length(Path)] <> '\' then
    Path := Path + '\';
  if FindFirst(Path + '*.*', faAnyFile, SearchRec) = 0 then
  begin
    repeat
      if SearchRec.Attr = faDirectory then
      begin
        if SubDirFlag and (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
          ScanDirectory(Path + SearchRec.Name, List, SubDirFlag);
      end
      else
      begin
        Ext := UpperCase(ExtractFileExt(SearchRec.Name));
        if (Ext = '.BMP') then
        begin
          List.Add(Path + SearchRec.Name);
        end;
      end;
    until
      FindNext(SearchRec) <> 0;
  end;
end;

Use it as follows:

ScanDirectory(GetCurrentDir, YourStringList, False);


Solve 4:

procedure TForm1.Button1Click(Sender: TObject);
var
  SearchRec: TSearchRec;
begin
  if FindFirst('c:\images\*.jpg', faAnyFile, SearchRec) = 0 then
  try
    repeat
      listbox1.items.add(searchrec.name);
    until
      Findnext(SearchRec) <> 0;
  finally
    FindClose(SearchRec);
  end;
end;

Note: if you are displaying many items, you will probably want to wrap the code within listbox1.items.BeginUpdate/EndUpdate.


Solve 5:

Searching for a file in a directory:

function FileExistsExt(const aPath, aFilename: string): Boolean;
var
  DSearchRec: TSearchRec;
begin
  Result := FileExists(IncludeTrailingPathDelimiter(aPath) + aFilename);
  if not Result then
  begin
    if FindFirst(APath + '\*', faDirectory, DSearchRec) = 0 then
    begin
      repeat
        if (DSearchRec.Name <> '.') and (DSearchRec.Name <> '..') then
          Result := FileExistsExt(IncludeTrailingPathDelimiter(aPath) +
                                         DSearchRec.Name, aFilename);
      until
        FindNext(DSearchRec) <> 0;
    end;
    FindClose(DSearchRec);
  end;
end;

Usage:

{ ... }
if FileExistsExt('C:', 'Testfile.dat') then
  { ... }


Solve 6:

The following function receives as parameters a file specification (like for example 'C:\My Documents\*.xls' or 'C:\*' if you want to search the entire hard disk) and optionally a set of attributes (exactly as Delphi's FindFirst function), and it returs a StringList with the full pathnames of the found files. You should free the StringList after using it.

interface

function FindFile(const filespec: TFileName; attributes: integer
  = faReadOnly or faHidden or faSysFile or faArchive): TStringList;

implementation

function FindFile(const filespec: TFileName;
  attributes: integer): TStringList;
var
  spec: string;
  list: TStringList;

  procedure RFindFile(const folder: TFileName);
  var
    SearchRec: TSearchRec;
  begin
    // Locate all matching files in the current
    // folder and add their names to the list
    if FindFirst(folder + spec, attributes, SearchRec) = 0 then
    begin
      try
        repeat
          if (SearchRec.Attr and faDirectory = 0) or
            (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
            list.Add(folder + SearchRec.Name);
        until FindNext(SearchRec) <> 0;
      except
        FindClose(SearchRec);
        raise;
      end;
      FindClose(SearchRec);
    end;
    // Now search the subfolders
    if FindFirst(folder + '*', attributes
      or faDirectory, SearchRec) = 0 then
    begin
      try
        repeat
          if ((SearchRec.Attr and faDirectory) <> 0) and
            (SearchRec.Name <> '.') and (SearchRec.Name <> '..') then
            RFindFile(folder + SearchRec.Name + '\');
        until FindNext(SearchRec) <> 0;
      except
        FindClose(SearchRec);
        raise;
      end;
      FindClose(SearchRec);
    end;
  end; // procedure RFindFile inside of FindFile

begin // function FindFile
  list := TStringList.Create;
  try
    spec := ExtractFileName(filespec);
    RFindFile(ExtractFilePath(filespec));
    Result := list;
  except
    list.Free;
    raise;
  end;
end;

Sample call

You can try this function placing a ListBox and a button on a form and adding this code to the OnClick event of the button:

procedure TForm1.Button1Click(Sender: TObject);
var
  list: TStringList;
begin
  list := FindFile('C:\Delphi\*.pas');
  ListBox1.Items.Assign(list);
  list.Free;
end;


Solve 7:

I thought if there was a way to create a function that does not recursively call itself to list all the files in the harddisk, so that there might be some improvement in speed, other than making the function more complex there were no speed improvements. Here is the code of the function any way.

type
  PRecInfo = ^TRecInfo;
  Trecinfo = record
    prev: PRecInfo;
    fpathname: string;
    srchrec: Tsearchrec;
  end;

function TForm1.RecurseDirectory1(fname: string): tstringlist;
var
  f1, f2: Tsearchrec;
  p1, tmp: PRecInfo;
  fwc: string;
  fpath: string;
  fbroke1, fbroke2: boolean;
begin
  result := tstringlist.create;
  fpath := extractfilepath(fname);
  fwc := extractfilename(fname);
  new(p1);
  p1.fpathname := fpath;
  p1.prev := nil;
  fbroke1 := false;
  fbroke2 := false;
  while (p1 <> nil) do
  begin
    if (fbroke1 = false) then
      if (fbroke2 = false) then
      begin
        if (findfirst(fpath + '*', faAnyfile, f1) <> 0) then
          break;
      end
      else if (findnext(f1) <> 0) then
      begin
        repeat
          findclose(f1);
          if (p1 = nil) then
            break;
          fpath := p1.fpathname;
          f1 := p1.srchrec;
          tmp := p1.prev;
          dispose(p1);
          p1 := tmp;
        until (findnext(f1) = 0);
        if (p1 = nil) then
          break;
      end;
    if ((f1.Name <> '.') and (f1.name <> '..') and ((f1.Attr and fadirectory) =
      fadirectory)) then
    begin
      fbroke1 := false;
      new(tmp);
      with tmp^ do
      begin
        fpathname := fpath;
        srchrec.Time := f1.time;
        srchrec.Size := f1.size;
        srchrec.Attr := f1.attr;
        srchrec.Name := f1.name;
        srchrec.ExcludeAttr := f1.excludeattr;
        srchrec.FindHandle := f1.findhandle;
        srchrec.FindData := f1.FindData;
      end;
      tmp.prev := p1;
      p1 := tmp;
      fpath := p1.fpathname + f1.name + '\';
      if findfirst(fpath + fwc, faAnyfile, f2) = 0 then
      begin
        result.add(fpath + f2.Name);
        while (findnext(f2) = 0) do
          result.add(fpath + f2.Name);
        findclose(f2);
      end;
      fbroke2 := false;
    end
    else
    begin
      if (findnext(f1) <> 0) then
      begin
        findclose(f1);
        fpath := p1.fpathname;
        f1 := p1.srchrec;
        fbroke1 := false;
        fbroke2 := true;
        tmp := p1.prev;
        dispose(p1);
        p1 := tmp;
      end
      else
      begin
        fbroke1 := true;
        fbroke2 := false;
      end;
    end;
  end;
  fpath := extractfilepath(fname);
  if findfirst(fname, faAnyfile, f1) = 0 then
  begin
    result.add(fpath + f2.Name);
    while (findnext(f1) = 0) do
      result.add(fpath + f2.Name);
    findclose(f1);
  end;
end;