2009. július 31., péntek

Putting a ProgressBar on a StatusBar


Problem/Question/Abstract:

Putting a ProgressBar on a StatusBar

Answer:

Many programs out there display a progress bar on the status bar. Internet Explorer is one of those. However, Delphi doesn't have component with that feature built-in. But that doesn't prevent us from having a progress bar inside a status bar panel. This is what this trick will tech you.

To make this tip work, create a form with a StatusBar (let's accept the default name: StatusBar1). Add a few panels to it.

To the public section of the form class declaration, add:

ProgressBar1: TProgressBar;

To the OnCreate event handler of the form, add:

var
  ProgressBarStyle: LongInt;
begin
  {create a run progress bar in the status bar}
  ProgressBar1 := TProgressBar.Create(StatusBar1);
  ProgressBar1.Parent := StatusBar1;
  {remove progress bar border}
  ProgressBarStyle := GetWindowLong(ProgressBar1.Handle, GWL_EXSTYLE);
  ProgressBarStyle := ProgressBarStyle - WS_EX_STATICEDGE;
  SetWindowLong(ProgressBar1.Handle, GWL_EXSTYLE, ProgressBarStyle);
  {set progress bar position and size - put in Panel[2]}
  ProgressBar1.Left := StatusBar1.Panels.Items[0].Width +
    StatusBar1.Panels.Items[1].Width + 4;
  ProgressBar1.Top := 4;
  ProgressBar1.Height := StatusBar1.Height - 6;
  ProgressBar1.Width := StatusBar1.Panels.Items[2].Width - 6;
  {set range and initial state}
  ProgressBar1.Min := 0;
  ProgressBar1.Max := 100;
  ProgressBar1.Step := 1;
  ProgressBar1.Position := 0;
end;
  
In the OnDestroy event handler of the form, add:

ProgressBar1.free;

If the position of the ProgressBar within the StatusBar doesn't please you, you can change the top, left, height and width properties of the ProgressBar. You can also change the Step, Max and Min properties of the ProgressBar. Within your program, work with the progress bar as you normally would, by accessing it's Position property.

2009. július 30., csütörtök

Set the wallpaper with your application in Win32


Problem/Question/Abstract:

Set the wallpaper with your application in Win32

Answer:

To the wallpaper in Windows 95/ Windows NT you must use the Win32 API function SystemParametersInfo.
SystemParametersInfo retrieves and sets system wide parameters including the wallpaper.


SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, 'c:\Windows\Seaside.bmp', 0)

2009. július 29., szerda

How to fill the undo buffer with the content of a TMemo


Problem/Question/Abstract:

When I type into a TMemo and then press ctrl+Z as usual, the last change is removed. But if I programmatically add text through memo1.seltext := 'newtext'; the ctrl+Z does not work. Why is this and is there a workaround?

Answer:

That's the way MS designed the multiline edit control to work. There is no way to tell it programmatically to save the current content to the undo buffer, you can only tell it to clear the undo buffer or to undo the last operation.

A hack that may work to trick it into filling the undo buffer is this.

{ ... }
with memo1 do
begin
  perform(WM_CHAR, 32, 0);
  sellength := -1;
  seltext := someText;
end;

2009. július 28., kedd

Get the path of a BDE alias at runtime


Problem/Question/Abstract:

I need to find out the path of a BDE alias from within an application. I would like to auto detect a backup path to the data.

Answer:

function GetAliasDir(alias: PChar): PChar;
var
  s: TStringList;
  i: integer;
  t: string;
  res: array[0..255] of char;
begin
  res := '';
  if Session.IsAlias(alias) then
  begin {Check if alias exists}
    s := TStringList.Create;
    try
      Session.GetAliasParams(Alias, s);
      t := '';
      if s.count > 0 then
      begin
        i := 0;
        while (i < s.count) and (Copy(s.Strings[i], 1, 5) <> 'PATH=') do
          inc(i);
        if (i < s.count) and (Copy(s.Strings[i], 1, 5) = 'PATH =') then
        begin
          t := Copy(s.Strings[i], 6, Length(s.Strings[i]) - 4);
          if t[length(t)] <> '\' then
            t := t + '\';
        end;
      end;
      StrPCopy(res, t);
    except
      StrPCopy(res, '');
    end;
    s.Free;
  end;
  result := res;
end;

2009. július 27., hétfő

How to fill a TDBListBox from a dataset


Problem/Question/Abstract:

How to fill a TDBListBox from a dataset

Answer:

procedure TForm1.FormCreate(Sender: TObject);
begin
  with table1 do
  begin
    open;
    while not EOF do
    begin
      DBlistbox1.items.add(FieldByName('name').AsString);
      next;
    end;
  end;
end;

2009. július 26., vasárnap

Setting Properties


Problem/Question/Abstract:

Setting Properties

Answer:

If you have many components of same type on a form and want to set properties to all of them. You don't need to select one by one. Just select them all and set properties you want and they'll be set to all of them.

2009. július 25., szombat

BDE limits


Problem/Question/Abstract:

BDE limits

Answer:

Table and Index Files
48
Clients in system
32
Sessions per client (3.5 and earlier, 16 Bit, 32 Bit)
256
Session per client (4.0, 32 Bit)
32
Open databases per session (3.5 and earlier, 16 Bit, 32 Bit)
2048
Open databases per session (4.0, 32 Bit)
32
Loaded drivers
64
Sessions in system (3.5 and earlier, 16 Bit, 32 Bit)
12288
Sessions in system (4.0, 32 Bit)
4000
Cursors per session
16
Entries in error stack
8
Table types per driver
16
Field types per driver
8
Index types per driver
48K
Size of configuration (IDAPI.CFG) file
64K
Size of SQL statement (RequestLive=False)
4K
Size of SQL statement (RequestLive=True)
16K
Record buffer size (SQL or ODBC)

2009. július 24., péntek

How to validate ISBN's?


Problem/Question/Abstract:

ISBNs (or International Standard Book Numbers) are mystical code numbers that uniquely identify books. The purpose of this article is to remove the mystery surrounding the structure of the ISBN, allowing applications to perform data validation on entered candidate ISBNs.

Answer:

ISBNs are composed of thirteen characters, limited to the number characters "0" through "9", the hyphen, and the letter "X". This thirteen-character code is divided into four parts, each separated by hyphens: group identifier, publisher identifier, book identification for the publisher, and the check digit. The first part (group identifier) is used to identify countries, geographical regions, languages, etc. The second part (publisher identifier) uniquely identifies the publisher. The third part (book identifier) uniquely identifies a given book within a publisher's collection. The fourth and final part (check digit) is used with the other digits in the code in an algorithm to derive a verifiable ISBN. The number of digits in the first three parts of an ISBN may contain a variable number of digits, but the check digit will always consist of a single character (between "0" and "9", or "X" for a value of 10) and the
ISBN as a whole will always consists of thirteen characters (ten numbers plus the three hyphens dividing the four parts of the ISBN).

The ISBN 3-88053-002-5 breaks down into the parts:

  Group:       3
  Publisher:   88053
  Book:        002
  Check Digit: 5

An ISBN can be verified to be a valid code using a simple mathematical algorithm. This algorithm takes each of the nine single digits from the first three parts if the ISBN (sans the non-numeric hyphens), multiplies each single digit by a number that is less than eleven the number of positions from the left each digit that is in the ISBN, adds together the result of each multiplication plus the check digit, and then divides that number by eleven. If that division by eleven results in no remainder (i.e., the number is modulo 11), the candidate ISBN is a valid ISBN. For example, using the previous sample ISBN 3-88053-002-5:

  ISBN:              3  8  8  0  5  3  0  0  2  5
  Digit Multiplier: 10  9  8  7  6  5  4  3  2  1
  Product:          30+72+64+00+30+15+00+00+04+05 = 220

Since 220 is evenly divisible by eleven, this candidate IDBN is a valid ISBN code.

This verification algorithm is easily translated into Pascal/Delphi code.
String manipulation functions and procedures are used to extract the check digit and the remainder of the ISBN from the String type value passed to a validation function. The check digit is converted to Integer type, which forms the start value of the aggregate variable onto which the multiplication of each digit in the remainder of the ISBN (the single digits that comprise the first three parts of the ISBN) will be added. A For loop is used to sequentially process each digit in the remainder, ignoring the hyphens, multiplying each digit times its position in the ISBN remainder relative to the other digits in the remainder. The final value of this aggregate variable is then checked to see whether it is evenly divisible by eleven (indicating a valid ISBN) or not (indicating an invalid candidate ISBN).

Here is an example of this methodology applied in a Delphi function:

function IsISBN(ISBN: string): Boolean;
var
  Number, CheckDigit: string;
  CheckValue, CheckSum, Err: Integer;
  i, Cnt: Word;

begin
  // Get check digit
  CheckDigit := Copy(ISBN, Length(ISBN), 1);
  // Get rest of ISBN, minus check digit and its hyphen
  Number := Copy(ISBN, 1, Length(ISBN) - 2);
  // Length of ISBN remainder must be 11 and check digit between 9 and 9 or X
  if (Length(Number) = 11) and (Pos(CheckDigit, '0123456789X') > 0) then
  begin
    // Get numeric value for check digit
    if (CheckDigit = 'X') then
      CheckSum := 10
    else
      Val(CheckDigit, CheckSum, Err);
    // Iterate through ISBN remainder, applying decode algorithm
    Cnt := 1;
    for i := 1 to 12 do
    begin
      // Act only if current character is between "0" and "9" to exclude hyphens
      if (Pos(Number[i], '0123456789') > 0) then
      begin
        Val(Number[i], CheckValue, Err);
        // Algorithm for each character in ISBN remainder, Cnt is the nth character
        //    so processed
        CheckSum := CheckSum + CheckValue * (11 - Cnt);
        Inc(Cnt);
      end;
    end;
    // Verify final value is evenly divisible by 11
    if (CheckSum mod 11 = 0) then
      IsISBN := True
    else
      IsISBN := False;
  end
  else
    IsISBN := False;
end;

This is a simplified example, kept simple to best demonstrate the algorithm to decode ISBNs. There are a number of additional features that would be desirable to add for use in a real-world application. For instance, this example function requires the candidate ISBN be passed as a Pascal String type value, with the hyphens dividing the four parts of the ISBN. Added functionality might accommodate evaluating candidate ISBNs entered without the hyphens. Another feature that might be added is checking that ensures three hyphens are properly included, as opposed to just thirteen number characters.

2009. július 23., csütörtök

Converting a integer containing millisecs to a nice formated string


Problem/Question/Abstract:

Converting a integer containing millisecs to a nice formated string.

Answer:

This routine formats an integer representing milliseconds into a nice formated string:  HH:MM:SS:Ms.
I use it in an audio application.

function MSecToStr(MSec: Integer): string;
begin
  Result := FormatFloat('00', MSec mod 1000 div 10); // msec
  MSec := MSec div 1000;
  Result := FormatFloat('00', MSec mod 60) + ':' + Result; // sec
  MSec := MSec div 60;
  Result := FormatFloat('00', MSec mod 60) + ':' + Result; // min
  MSec := MSec div 60;
  Result := IntToStr(MSec mod 60) + ':' + Result; // hour
end;

2009. július 22., szerda

Map Generator


Problem/Question/Abstract:

How to implement a map generator in Delphi whioch produces rectangular maps with land or sea.

Answer:

The algorithm is very simple- I throw a few land points onto an empty map constraining the initial points so they don't drop near the edge. I then throw a few sea points down as well. Next I draw circles around each point (one circle round all the land points then repeat for the sea points), and repeat up to 12 layers (the constant Max-Layers defines this).

Circle data is defined in the data file Data.pas. This contains data for 35 concentric circular layers about the point at coordinates 0,0. The array layerpoints holds the number of points in each layer, offset indicates where a layer starts in the array C (Yeah stupid name- ah well) which holds all the points as offsets - ie the first layer has 8 points and the first 8 points at offset 0 in C are -1,-1,0,-1,1,-1,-1,0,1,0,-1,1,0,1,1,1, ie (-1,-1), (0,-1) (1,-1) etc up to (1,1) . Just add these to the point to give the coordinates of all points round it- these are set to land or sea as long as they are empty- so when land hits sea it defines the coast. An optimisation here is that each original point is switched off when the object attempts to grow a layer of points around it. If it succeeds in placing one new point then the original point is switched back on.

Once the initial map has been produced it is tidied by removing small continents (any below a threshold size defined in mincontsize). Island and sea sizes are counted by using a recursive fill algorithm. Each terrain square has a continent number which is set during the count- basically the refill routine sets the continent number if it is land then calls itself for all 8 squares around it.

The final step is to output the count of those continents that haven't been removed due to being too small and then plonk the continent number on the map in the approx centre of the continent (I calculate the average centre coordinate).


Component Download: MapGen.zip

2009. július 21., kedd

BDE Safe Configuration check


Problem/Question/Abstract:

Running the BDE in a safe mode requires some settings in the BDE Administrator tool. This unit checks if the BDE has been configured correctly (LocalShare=True, NetDir=\\...). Also the PrivDir will be managed by this unit, a unique PrivDir will be created and cleaned up every time the (your) program is run.

Answer:

unit modBDETools;

{ module Borland Database Engine Tools
  Author: E.J.Molendijk
  Mail: erwin@delphi-factory.com

  When this unit is linked into the project the PrivDir of
  the global Session variable will be set to a
  unique directory within the (local) system temp dir.
  When the application ends, this private dir
  will be cleaned up.

  Also a routine CheckBDEConfig() can be called to check if the
  BDE is configured for safe opperation.

Hint, for best performance set the BDE to:

  Setting                             Value
  -------------------------------------------------------------
  \System\INIT\LANGDRIVER             'ascii' ANSI (DBWINUS0)
  \System\INIT\MAXBUFSIZE             16384 KB
  \System\INIT\MINBUFSIZE             128 KB
  \System\INIT\MAXFILEHANDLES         100
}

interface

uses
  DB, DBTables, BDE, SysUtils, Windows, FileCtrl, ComObj;

{ This function returns True if the BDE is configured
  with a NetWork directory with an UNC path (\\server\share)
  and has LocalShare set to True.
  The Msg param will be filled with a msg describing the problem. }
function CheckBDEConfig(var Msg: string): Boolean;

implementation

const
  { Here are the parameters used to pass into the cfg functions.  These are only
    a small portion of what types can be passed in.  You need to call
    DbiOpenCfgInfoList with '\' into pszCfgPath to get all possible options if
    it is not found below. }

  { Paradox Driver Settings...  }
  PARADOXNETDIR = '\DRIVERS\PARADOX\INIT\;NET DIR';
  PARADOXVERSION = '\DRIVERS\PARADOX\INIT\;VERSION';
  PARADOXTYPE = '\DRIVERS\PARADOX\INIT\;TYPE';
  PARADOXLANGDRIVER = '\DRIVERS\PARADOX\INIT\;LANGDRIVER';
  PARADOXLEVEL = '\DRIVERS\PARADOX\TABLE CREATE\;LEVEL';
  PARADOXBLOCKSIZE = '\DRIVERS\PARADOX\TABLE CREATE\;BLOCK SIZE';
  PARADOXFILLFACTOR = '\DRIVERS\PARADOX\TABLE CREATE\;FILL FACTOR';
  PARADOXSTRICTINTEGRITY = '\DRIVERS\PARADOX\TABLE CREATE\;STRICTINTEGRITY';

  { System Initialization Settings...  }
  AUTOODBC = '\SYSTEM\INIT\;AUTO ODBC';
  DATAREPOSITORY = '\SYSTEM\INIT\;DATA REPOSITORY';
  DEFAULTDRIVER = '\SYSTEM\INIT\;DEFAULT DRIVER';
  LANGDRIVER = '\SYSTEM\INIT\;LANGDRIVER';
  LOCALSHARE = '\SYSTEM\INIT\;LOCAL SHARE';
  LOWMEMORYUSAGELIMIT = '\SYSTEM\INIT\;LOW MEMORY USAGE LIMIT';
  MAXBUFSIZE = '\SYSTEM\INIT\;MAXBUFSIZE';
  MAXFILEHANDLES = '\SYSTEM\INIT\;MAXFILEHANDLES';
  MEMSIZE = '\SYSTEM\INIT\;MEMSIZE';
  MINBUFSIZE = '\SYSTEM\INIT\;MINBUFSIZE';
  SHAREDMEMLOCATION = '\SYSTEM\INIT\;SHAREDMEMLOCATION';
  SHAREDMEMSIZE = '\SYSTEM\INIT\;SHAREDMEMSIZE';
  SQLQRYMODE = '\SYSTEM\INIT\;SQLQRYMODE';
  SYSFLAGS = '\SYSTEM\INIT\;SYSFLAGS';
  VERSION = '\SYSTEM\INIT\;VERSION';

type
  pword = ^word;

function GetBDEConfigParameter(Param: string; Count: pword): string;
var
  hCur: hDBICur;
  rslt: DBIResult;
  Config: CFGDesc;
  Path, Option: string[254];
  Temp: array[0..255] of char;

begin
  Result := '';
  hCur := nil;

  if Count <> nil then
    Count^ := 0;

  try

    if Pos(';', Param) = 0 then
      raise EDatabaseError.Create('Invalid parameter passed to function.  There must '
        +
        'be a semi-colon delimited sting passed');

    Path := Copy(Param, 0, Pos(';', Param) - 1);
    Option := Copy(Param, Pos(';', Param) + 1, Length(Param) - Pos(';', Param));

    Check(DbiOpenCfgInfoList(nil, dbiREADONLY, cfgPERSISTENT, StrPCopy(Temp, Path),
      hCur));
    Check(DbiSetToBegin(hCur));

    repeat
      rslt := DbiGetNextRecord(hCur, dbiNOLOCK, @Config, nil);
      if rslt = DBIERR_NONE then
      begin
        if StrPas(Config.szNodeName) = Option then
          Result := Config.szValue;
        if Count <> nil then
          Inc(Count^);
      end
      else if rslt <> DBIERR_EOF then
        Check(rslt);
    until rslt <> DBIERR_NONE;

  finally
    if hCur <> nil then
      Check(DbiCloseCursor(hCur));
  end;
end;

procedure PrepareBDEPrivDir;
{
  The PrivDirID constant is used to create the Session.PrivDir
  Complete private path:  TempPath\PrivDirID\RandomStr

  The RandomStr (GUI) will ensure a unique path every time the
  program is started.
  The PrivDirID can be used (by batchfile) to delete all junk RandomStr's
  left over from abnormal program terminations.

  Note: CleanupBDEPrivDir cleans up the dir created by this routine.
}
const
  PrivDirID = 'CharonPrivDir';
var
  Temp: string;
  I: Integer;
begin
  // Get a temp directory name for private dir
  I := GetTempPath(0, pchar(Temp)); // get length
  SetLength(Temp, I); // prepare for this length
  GetTempPath(I, pchar(Temp)); // retreive temp path
  SetLength(Temp, I - 1); // remove #0
  Temp := IncludeTrailingBackSlash(Temp); // inlcude a trailing slash

  // construct a unique temppath
  Temp := Temp + PrivDirID + '\' + CreateClassID;

  // create the directory
  ForceDirectories(Temp);

  // Set the PrivDir
  Session.PrivateDir := Temp;

  //  ShowMessage('Private directory: '+Temp);
end;

procedure CleanupBDEPrivDir;
{ Cleansup the Private dir.
  (all database connections will be closed!)
}
var
  CleanUpOK: Boolean;
begin
  // Close the session -- this will empty the PrivDir
  Session.Close;

  // Remove the PrivDir
  CleanUpOK := RemoveDir(Session.PrivateDir);

  Assert(CleanUpOK);
end;

function CheckBDEConfig(var Msg: string): Boolean;
const
  strTrue = 'TRUE'; { do not localize }

var
  NetDir, LocalShare: string;

begin
  // Get BDE settings
  NetDir := GetBDEConfigParameter(PARADOXNETDIR, nil);
  LocalShare := Uppercase(Trim(GetBDEConfigParameter(modBDETools.LOCALSHARE, nil)));

  Msg := '';

  if Pos('\\', NetDir) <> 1 then
    Msg := 'Set the NetDir option in the BDE Administrator to an UNC path.';

  if LocalShare <> strTrue then
    Msg := 'Set the LocalShare option in the BDE Administrator to TRUE.';

  // Check them
  Result := Msg = '';
end;

initialization
  PrepareBDEPrivDir;

finalization
  CleanupBDEPrivDir;

end.

2009. július 20., hétfő

Executing TIBStoredProc with one line of code


Problem/Question/Abstract:

Running a TIBStoredProc as if it where a delphi procedure.

Answer:

{
Copyright (c) 2001 by E.J.Molendijk

TIBStoredProc is handy, but multiple lines of code are required to execute it. The routine in this article handles preparing, assigning params, execution and transactions for you.
}

{
  ExecSP
  Execute a InterBase Stored Procedure.
  Transaction gets Committed after excution.

  input:
    SP = InterBase Stored Procedure
    P  = Array with parameters for the SP. No param checking!

  output:
    Check the SP.Params for output (if any).
}

procedure TSPMod.ExecSP(SP: TIBStoredProc; P: array of Variant);
var
  A, B: Integer;
begin
  // make sure there's a transaction context
  if not SP.Transaction.Active then
    SP.Transaction.StartTransaction;

  try
    // make sure stored procedure is closed
    SP.Close;

    // prepare (attach params)
    if not SP.Prepared then
      SP.Prepare;

    // Set all Input params
    B := 0;
    for A := 0 to SP.ParamCount - 1 do
      if (SP.Params[A].ParamType in [ptInput, ptInputOutput]) then
      begin
        SP.Params[A].Value := P[B];
        Inc(B);
      end;

    // run the procedure on the server
    SP.ExecProc;
  finally
    // commit
    SP.Transaction.Commit;
  end;
end;

Examples:

Assume you have a datamodule called SPMod. And assume it contains some stored procedures:

SPMod.spOpenenSession
SPMod.spGetTicketNr

The following routines can be added to encapsulate the StoredProcs.

// Example without returning data:

procedure TSPMod.OpenSession(SessionID: Integer);
begin
  ExecSP(spOpenSession, [SessionID]);
end;

// Example with a integer as result

function TSPMod.GetTicketNr: Integer;
begin
  ExecSP(spGetTicketNr, [CurrentSessionID]);
  Result := spGetTicketNr.ParamByName('TicketNr').AsInteger;
end;

2009. július 19., vasárnap

Calculate the difference between two time values


Problem/Question/Abstract:

How to get the hours and minutes between two DateTime values?

Answer:

Solve 1:

In order to avoid future questions about rounding, I would consider the solution below (untested!):

{ ... }
var
  InTime, OutTime: TDateTime;
  InMinutes, OutMinutes, MinutesDiff: Int64;
  DiffHours, DiffMinutes: Integer;
begin
  { First, make sure that InTime and OutTime are relative to the same offset from GMT. Then:}
  InMinutes := round(InTime * 24 * 60);
  OutMinutes := round(OutTime * 24 * 60);
  MinutesDiff := OutMinutes - InMinutes;
  Assert(MinutesDiff >= 0);
  DiffHours := MinutesDiff div 60;
  DiffMinutes := MinutesDiff mod 60;
end;


Solve 2:

{ ... }
var
  InTime, OutTime, TimeDifference: TDateTime;
  DiffHours, DiffMinutes: Integer;
begin
  TimeDifference := OutTime - InTime;
  {ShowMessage(DateTimeToStr(TimeDifference);}
  DiffHours := trunc(TimeDifference * 24);
  { DiffMinutes := trunc((TimeDifference * 24 * 60) - (Trunc(TimeDifference * 24) * 60))}
  DiffMinutes := trunc((TimeDifference * 24 * 60) - (DiffHours * 60))
end;

2009. július 18., szombat

Strip illegal characters from a file name


Problem/Question/Abstract:

I am attempting to create a file from a title of a webpage. The title can contain illegal filename characters such as \ / : ? etc. What is the best way to filter out these characters? I want to remove them entirely, and not replace them with a space. Thus, I would like to keep alphanumeric, dashes, underscores, space, and a few other special characters only.

Answer:

Solve 1:

Something like the function below would do. In my code, the function replaces dodgy chars with a replacement, but I guess it would still work if you specificed the 'CunfriendlyReplacement' as an empty string.

{ ... }
const
{$IFDEF WIN32}
  CpathDelimiter = '\';
{$ELSE}
  CpathDelimiter = '/';
{$ENDIF}
  CdelimiterChar = '_';
  CunfriendlyChars = [CpathDelimiter, '.', ':', CdelimiterChar, '/', '<', '>', '|'];
  CunfriendlyReplacement = '-';
  { ... }

function makeNameFileFriendly(const inName: string): string;
var
  charIndex: Integer;
  thisChar: Char;
begin
  result := '';
  for charIndex := 1 to length(inName) do
  begin
    thisChar := inName[charIndex];
    if (thisChar in CunfriendlyChars) then
      result := result + CunfriendlyReplacement
    else
      result := result + thisChar;
  end;
end;


Solve 2:

function ValidateFilename(Filename: WideString): WideString;
var
  i: Integer;
begin
  Result := '';
  for i := 1 to Length(Filename) do
  begin
    if Pos(Filename[i], WideString('\/:*?<>|,' + #34)) = 0 then
      Result := Result + Filename[i];
  end;
  Result := Trim(Result);
end;

2009. július 17., péntek

Find a word in an Array of String


Problem/Question/Abstract:

How to find a word in an Array of String

Answer:

{ ... }
const
  StringsToSearch: array[0..7] of string = ('hello', 'earth', 'why', 'this', '12',
    'people', 'how', 'what');
var
  Found: Boolean;
  i: Integer;
begin
  Found := False;
  for i := 0 to 7 do
    if Pos(StringsToSearch[i], ALongLongLongString) > 0 then
    begin
      Found := True;
      break;
    end;
  if Found then
    ShowMessage('At least one word was found')
  else
    ShowMessage('No words found');
end;

2009. július 16., csütörtök

Increment a file name when the file already exists in a folder


Problem/Question/Abstract:

I am trying to write a simple backup utility for my projects. The utility recursivley searches folders looking for *.dpr files when it finds one it looks to see if a *.dof file is available and then extracts the FileVersion number from this, using this information it then creates a zip file with all the project files in it. It works so far and creates files like Project1-v1.0.0.0.zip. The problem is if the file already exists in the selected backup folder I wish to increment the file name so all versions are kept, e.g. if Project1-v1.0.0.0.zip already exists then generate a filename of Project1-v1.0.0.0-1.zip. If Project1-v1.0.0.0-1.zip exists then generate a file name Project1-v1.0.0.0-2.zip etc. to make sure no files are overwritten.

Answer:

Variants are the easiest way of dealing with these properties.

function GetNextBackupFileName(AFolder, AFile: string): string;
var
  v, v1: Integer;
  Body, Ext: string;
  sr: TSearchRec;

  function FileExt(FileName: string): string;
  begin
    Result := ExtractFileExt(FileName);
  end;

  function FileBody(FileName: string): string;
  begin
    Result := ChangeFileExt(FileName, '');
  end;

  function GetPostFix(FileName: string): Integer;
  begin
    Result := StrToIntDef(Copy(FileBody(FileName), Length(Body) + 1, 255), 0);
  end;

begin
  Result := AFile;
  v := 0;
  Body := FileBody(AFile);
  Ext := FileExt(AFile);
  if FindFirst(AFolder + Body + '*' + Ext, faAnyFile xor faDirectory, sr) = 0 then
  begin
    repeat
      v1 := GetPostFix(sr.Name);
      if v1 < v then
        v := v1;
    until
      FindNext(sr) <> 0;
    FindClose(sr);
    Result := Body + IntToStr(v - 1) + Ext;
  end;
end;

Used like this:

procedure TForm1.Button1Click(Sender: TObject);
var
  BackupFolder, BaseFileName: string;
begin
  BackupFolder := 'C:\BackupFolder\';
  BaseFileName := 'Project1-v1.0.0.0.zip';
  Label1.Caption := GetNextBackupFileName(BackupFolder, BaseFileName);
  FileClose(FileCreate(BackupFolder + Label1.Caption));
end;

2009. július 15., szerda

Easy way of creating a database application


Problem/Question/Abstract:

How can you create a database application without manually putting the visual components like DBEdit, DBImage etc., and attach that with a field in the table and setting other properties?

Answer:

You don&#8217;t need to write a single line of code for this. All starts with the TTable component.

Let me tell you the steps needed to do this:

Create a new application.
Put a TTable component on the form
Set the database and table name properties
Right click on the TTable component, you will be getting a menu
Select Fields editor
You will be getting a grid
Then right click on the fields Editor.
You will be getting another menu
Then you can either select the Add Fields or Add all fields option.
If you select Add all fields option, all the fields in the table will be added to the fields editor.
Then you just select the fields you want to show up on the form.
Then just drag all the fields on to the form; you are done.
Yes you will be getting the respective DBEdit,DBImage etc., on the form.
Also the datasource component will be added.
For navigation, you can put a Navigator control from the DataControls palette and assign the datasource property for it.
Now the simple database application is ready to use.
You can run the application and work with all the database operations like navigation, edit etc., thro&#8217; the navigator.

For Delphi beginners, this could be enough to start with database application.

The same thing you can do with putting separate control like DBEdit etc., and setting the properties;but here you dont need to set any properties for those controls; all will be set automatically once you drag the fields from the fields editor onto the form.

2009. július 14., kedd

Get a list of current print jobs


Problem/Question/Abstract:

Is there any way in Delphi to check for the printer queue or if a printer has received and printed a document correctly?

Answer:

No there is not any bullet-proof way of determining that a document has been printed correctly. Nevertheless here is a routine to collect all the running jobs of a printer. Keep in mind that the only way to retrieve this information is by pooling it from the spooler in standard intervals.

{ ... }
type
  PJobInfoArray = ^TJobInfoArray;
  TJobInfoArray = array[0..0] of winspool.JOB_INFO_2;

procedure GetJobs(APrinter: string);
var
  Size, Needed, Returned, CNT: Cardinal;
  Res: LongBool;
  Prn: Cardinal;
  PrnName: Pchar;
  vJobs: PJobInfoArray;
begin
  ReAllocMem(PrnName, Length(aPrinter) + 2);
  CopyMemory(PrnName, @aPrinter[1], Length(aPrinter));
  Res := OpenPrinter(PrnName, Prn, nil);
  ReAllocMem(PrnName, 0);
  if LongInt(Res) = 0 then
    RaiseLastOSError;
  Size := 0;
  Res := WinSpool.EnumJobs(Prn, 0, 999, 2, VJobs, 0, Needed, Returned);
  Size := Needed;
  reAllocMem(VJobs, Size);
  Res := EnumJobs(Prn, 0, 999, 2, vJobs, Size, Needed, Returned);
  if LongInt(Res) > 0 then
  begin
    reAllocMem(vJobs, 0);
    ClosePrinter(Prn);
    RaiseLastOSError;
  end;
  ReAllocMem(VJobs, 0);
  ClosePRinter(Prn);
end;

2009. július 13., hétfő

Changing standard Windows dialogs


Problem/Question/Abstract:

How to change text like "File name:", "File Type" and buttons' text in standard Windows dialogs?

Answer:

Some times we need to replace some text or something other in standard Windows Open/Save dialogs. Unfortunately, Delphi's dialogs components don't provide the access to all controls placed on Windows common dialogs. But we can perform this using Windows API.

Example below demonstrates the changing all embedded text controls in Open dialog.

First, we need to determine identifiers of dialog's controls, they are following:

const
  // LB_FOLDERS_ID = 65535;
  LB_FILETYPES_ID = 1089; // "File types:" label
  LB_FILENAME_ID = 1090; // "File name:" label
  LB_DRIVES_ID = 1091; // "Look in:" label

Second, we need to send message to dialog window for changing necessary controls, something like following:

procedure TForm1.OpenDialog1Show(Sender: TObject);
const
  // LB_FOLDERS_ID = 65535;
  LB_FILETYPES_ID = 1089;
  LB_FILENAME_ID = 1090;
  LB_DRIVES_ID = 1091;
  Str1 = 'Four';
  Str2 = 'Five';
  Str3 = 'One';
  Str4 = 'Two';
  Str5 = 'Three';
begin
  SendMessage(GetParent(OpenDialog1.Handle), CDM_SETCONTROLTEXT, IDOK,
    LongInt(Pchar(Str1)));
  SendMessage(GetParent(OpenDialog1.Handle), CDM_SETCONTROLTEXT, IDCANCEL,
    LongInt(Pchar(Str2)));
  SendMessage(GetParent(OpenDialog1.Handle), CDM_SETCONTROLTEXT, LB_FILETYPES_ID,
    LongInt(Pchar(Str3)));
  SendMessage(GetParent(OpenDialog1.Handle), CDM_SETCONTROLTEXT, LB_FILENAME_ID,
    LongInt(Pchar(Str4)));
  SendMessage(GetParent(OpenDialog1.Handle), CDM_SETCONTROLTEXT, LB_DRIVES_ID,
    LongInt(Pchar(Str5)));
end;

2009. július 12., vasárnap

Implement autoscroll for a TScrollbox when dragging


Problem/Question/Abstract:

I have a form with a TScrollBox on it. At runtime I dynamically add any number of a custom control I created. These controls need to interact via Drag and Drop, however, when I drag from one control and move to the edge of the TScrollBox it doesn't automatically scroll to reveal the additional controls.

Answer:

Add a handler to the forms OnDragOver event so you get aware when the user drags the mouse outside the scrollbox. You can the start a timer that fires scroll messages at the scrollbox to get it to move. In the example below all edits are on the scrollbox and share the edit drag handlers. The timer is set to 100 msecs and initially disabled.

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    ScrollBox1: TScrollBox;
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Edit4: TEdit;
    Edit5: TEdit;
    Edit6: TEdit;
    Edit7: TEdit;
    Edit8: TEdit;
    Edit9: TEdit;
    Edit10: TEdit;
    Edit11: TEdit;
    Edit12: TEdit;
    Edit13: TEdit;
    Label1: TLabel;
    Timer1: TTimer;
    procedure Edit1DragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState;
      var Accept: Boolean);
    procedure Edit1DragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure Timer1Timer(Sender: TObject);
    procedure FormDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState;
      var Accept: Boolean);
  private
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Edit1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  Accept := Source is TEdit and (Sender <> Source);
end;

procedure TForm1.Edit1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  (Sender as TEdit).SelText := (Source as TEdit).Text;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  pt: TPoint;
begin
  {figure out where the mouse is}
  GetCursorPos(pt);
  pt := ScreenToClient(pt);
  with scrollbox1.boundsrect, pt do
    if (x > left) and (x < right) then
    begin
      if y < top then
        scrollbox1.perform(WM_VSCROLL, SB_LINEUP, 0)
      else if y > bottom then
        scrollbox1.perform(WM_VSCROLL, SB_LINEDOWN, 0)
      else
        timer1.enabled := false;
    end
    else
      timer1.enabled := false;
end;

procedure TForm1.FormDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  accept := false;
  if State = dsDragLeave then
    timer1.enabled := false
  else if (source is TEdit) then
  begin
    {Figure if mouse is above or below the scrollbox, that determines
    whether we enable the scroll timer.}
    with scrollbox1.boundsrect do
      timer1.enabled := (x > left) and (x < right) and ((y < top) or (y > bottom));
  end;
end;

end.

2009. július 10., péntek

Delete a line in a TMemo in its OnChange event

Problem/Question/Abstract:

I want to use a TMemo as a FIFO, and every time a new string is written to the memo, the OnChange event will be triggered. In the OnChange event I will do my work on the string and as the last thing make a memo.delete(0) so that the next string in the memo will move to line 0. My question is now, is this a right thing to do? I suppose that it will make a recursive call to the OnChange event and that I in this way have a "system" that will empty my memo as fast as possible.

Answer:

The trick is to save, wipe, and restore the event value:

procedure TForm1.Memo1Change(Sender: TObject);
var
Save: TNotifyEvent;
begin
Save := Memo1.OnChange; {Save}
Memo1.OnChange := nil; {Erase}
Memo1.Lines.Delete(0);
Memo1.OnChange := Save; {Restore}
end;


2009. július 9., csütörtök

Synchronize TThreads

Problem/Question/Abstract:

I am using a thread that does some system checks and outputs the results to a TRichEdit, so I need to use a Syncronize(AMethod) to update the interface and the TRichEdit mentioned before. But I cannot do this if AMethod accepts parameters. I would like to pass some parameters to AMethod, but have not been able to do this. Is this possible?

Answer:

Below is the complete code to do this, using the function:

function ExecuteInMainThread(Proc: MainThreadProcType; var Parameter): boolean;

It will call Proc in the main Windows thread and wait for the procedure to finish before returning. Proc takes one untyped variable. If you want to pass more than one variable, put the data into a record and pass the record. You don't really have to code anything. Just copy this code to a unit, use the unit in your code, and call the function. There is no TThread method that does this.

unit ThreadLib;

interface

uses
Windows, Messages, ExtCtrls, Classes, Forms, SyncObjs;

type
MainThreadProcType = procedure(var Parameter) of object;

function ExecuteInMainThread(Proc: MainThreadProcType; var Parameter): boolean;

implementation

uses
SysUtils;

const
UM_EXECMAIN = WM_USER + 590;

type
WindowProcType = function(hWnd: HWND; Msg: UINT; wParam: WPARAM;
lParam: LPARAM): LRESULT; stdcall;

ProcInfoType = record
Method: MainThreadProcType;
Param: pointer;
end;

ProcInfoPtrType = ^ProcInfoType;

var
OrigThreadWndProc: WindowProcType;

function ExecuteInMainThread(Proc: MainThreadProcType; var Parameter): boolean;
var
ProcInfo: ProcInfoType;
begin
if GetCurrentThreadID = MainThreadID then
try
Proc(Parameter);
Result := true;
except
Result := false;
end
else
begin
ProcInfo.Method := Proc;
ProcInfo.Param := @Parameter;
Result := SendMessage(Application.Handle, UM_EXECMAIN, 0, longint(@ProcInfo)) =
ord(true);
{To send a message without waiting for it to return (PostMessage),
make sure that the message parameters do not include pointers. Otherwise,
the functions will return before the receiving thread has had a chance to
process the message and the sender will free the memory before it is used.}
end;
end;

function ParamThreadWndProc(Window: HWND; Message, wParam, lParam: longint):
longint; stdcall;
begin
if Message = UM_EXECMAIN then
try
with ProcInfoPtrType(pointer(lParam))^ do
Method(Param^);
Result := ord(true);
except
Result := ord(false);
end
else
Result := OrigThreadWndProc(Window, Message, wParam, lParam);
end;

begin
OrigThreadWndProc := WindowProcType(SetWindowLong(Application.Handle,
GWL_WNDPROC, longint(@ParamThreadWndProc)));
end.



2009. július 8., szerda

Create an mht (web page single file) file

Problem/Question/Abstract:

This article tells you how to create a web page archive single file that can be viewed in IE, all images are included in this file. This is the Web Archive, single file (*.mht) option in the IE save as.

Answer:

Solve 1:

Below are 2 versions of the source code to do this.  Also a test application from the component link.

procedure SaveToMHT(const URL, DestFileName: string);

This procedure can be used as long as the threading model has not been set to Multithreaded.  If you try, you will get an "Interface not supported" error.

But if you have already set COM to multithreaded through the CoInitializeEx function then use the other function:

This one:

procedure SaveToMHT_InCOThread(const URL, DestFileName: string);

The difference in this last function is that it runs the 1st function in a separate thread with CoInitialize(nil) called.  This allows you to still call the SaveToMHT when you have previously set COM to multithreaded.  (Its still blocking, so the function will only return when it is finished)

Beware, it is possible to get a "security" error when downloading from a secure https website.  The only workaround I am aware of is to remove the "s" and just use http.

In adition to the unit containing the 2 procedures, I have also included the import type libraries required (click the component link above).  This should save you about 30 minutes of hunting the internet trying to find which dll you have to import.

In the event you have to re-import it, the dll is cdosys.dll in the system32 directory.

unit SaveMHT;

interface
uses
CDO_TLB, ADODB_TLB, Classes, SysUtils, ActiveX;

procedure SaveToMHT(const URL, DestFileName: string);

// This should be used when you have already set the threading model to multithreaded
procedure SaveToMHT_InCOThread(const URL, DestFileName: string);

implementation

procedure SaveToMHT(const URL, DestFileName: string);
var
Msg: IMessage;
Conf: IConfiguration;
Stream: _Stream;
begin
Msg := CoMessage.Create;
Conf := CoConfiguration.Create;
Msg.Configuration := Conf;
Msg.CreateMHTMLBody(URL, cdoSuppressNone, '', '');
Stream := Msg.GetStream;
Stream.SaveToFile(DestFileName, adSaveCreateOverWrite);
end;

type
TCOMInitNullThread = class(TThread)
protected
FPage, FFile: string;
Ex: Exception;
procedure Execute; override;
end;

procedure SaveToMHT_InCOThread(const URL, DestFileName: string);
begin
with TCOMInitNullThread.Create(True) do
try
FPage := URL;
FFile := DestFileName;
Resume;
WaitFor;
if Ex <> nil then
raise Ex;
finally
Free;
end;
end;

{ TCOMInitNullThread }

procedure TCOMInitNullThread.Execute;
begin
CoInitialize(nil);
try
SaveToMHT(FPage, FFile);
except
on E: Exception do
begin
Ex := E.ClassType.Create as Exception;
Ex.Message := E.Message;
end;
end;
CoUninitialize;
end;

end.

Component Download: http://www.baltsoft.com/files/dkb/attachment/mht.ziphttp://www.baltsoft.com/files/dkb/attachment/mht.zip


Solve 2:

function SaveToMHT(const AUrl, AFileName: string;
AShowErrorMessage: boolean = false): boolean;
var
oMSG, oConfig: OleVariant;
sFileName: string;
Retvar: boolean;
begin
sFileName := ChangeFileExt(AFileName, '.mht');
DeleteFile(sFileName);

try
oConfig := CreateOleObject('CDO.Configuration');
oMSG := CreateOleObject('CDO.Message');
oMSG.Configuration := oConfig;
oMSG.CreateMHTMLBody(AUrl);
oMSG.GetStream.SaveToFile(sFileName);
Retvar := true;
except
on E: Exception do
begin
if AShowErrorMessage then
MessageDlg(E.Message, mtError, [mbOk], 0);
Retvar := false;
end;
end;

oMSG := VarNull;
oConfig := VarNull;

Result := Retvar;
end;


2009. július 7., kedd

Resize a TPanel at runtime

Problem/Question/Abstract:

How to resize a TPanel at runtime

Answer:

Solve 1:

You should add a SIZEBOX constant to the your panel window style:

TMyNewPanel = class(TPanel)
{ ... }
procedure CreateParams(var Params: TCreateParams); override;
{ ... }

procedure TMyNewPanel.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.Style := Params.Style or WS_SIZEBOX;
end;


Solve 2:

The best way to deal with this is to make a descendent of TPanel that incorporates the required behaviour. Copy the following to a file SizeablePanel.pas, and install that via Component -> Install component.

unit SizeablePanel;

interface

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

type
TSizeablePanel = class(TPanel)
private
FMoveable: Boolean;
procedure wmNCHittest(var msg: TWMNCHittest); message WM_NCHITTEST;
published
property Moveable: Boolean read FMoveable write FMoveable default false;
end;

procedure Register;

implementation

procedure Register;
begin
RegisterComponents('PBGoodies', [TSizeablePanel]);
end;

procedure TSizeablePanel.wmNCHittest(var msg: TWMNCHittest);
var
bottom, right: Integer;
pt: TPoint;
begin
if moveable then
msg.result := HTCAPTION
else
inherited;
pt := parent.ScreenToClient(SmallpointToPoint(msg.Pos));
bottom := Top + Height;
right := Left + Width;
if (pt.x - Left) < 10 then
if (pt.y - Top) < 10 then
msg.Result := HTTOPLEFT
else if (Bottom - pt.y) < 10 then
msg.result := HTBOTTOMLEFT
else
msg.result := HTLEFT
else if (right - pt.x) < 10 then
if (pt.y - Top) < 10 then
msg.Result := HTTOPRIGHT
else if (Bottom - pt.y) < 10 then
msg.result := HTBOTTOMRIGHT
else
msg.result := HTRIGHT
else if (pt.y - Top) < 10 then
msg.Result := HTTOP
else if (Bottom - pt.y) < 10 then
msg.result := HTBOTTOM;
end;

end.




2009. július 6., hétfő

Write a correct date in SQL


Problem/Question/Abstract:

How to write a correct date in SQL

Answer:

The SQL date format does not change with the system settings. The date is saved in "MM/DD/YY(YY)" format. You can use this code to save the current date in SQL:

var
  sSQLDate: string;
{..}
sSQLDate := '''' + FormatDateTime('mm"/"dd"/"yyyy', Now) + '''';

2009. július 5., vasárnap

Load and read a shortcut to see where it points to


Problem/Question/Abstract:

How to load and read a shortcut to see where it points to

Answer:

procedure GetShellLinkInfo(const LinkFile: WideString; var SLI: TShellLinkInfo);
{Retrieves information on an existing shell link}
var
  SL: IShellLink;
  PF: IPersistFile;
  FindData: TWin32FindData;
  AStr: array[0..MAX_PATH] of char;
begin
  OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER, IShellLink,
    SL));
  {The IShellLink implementer must also support the IPersistFile interface. Get an interface pointer to it}
  PF := SL as IPersistFile;
  { Load file into IPersistFile object }
  OleCheck(PF.Load(PWideChar(LinkFile), STGM_READ));
  {Resolve the link by calling the Resolve interface function}
  OleCheck(SL.Resolve(0, SLR_ANY_MATCH or SLR_NO_UI));
  {Get all the info}
  with SLI do
  begin
    OleCheck(SL.GetPath(AStr, MAX_PATH, FindData, SLGP_SHORTPATH));
    PathName := AStr;
    OleCheck(SL.GetArguments(AStr, MAX_PATH));
    Arguments := AStr;
    OleCheck(SL.GetDescription(AStr, MAX_PATH));
    Description := AStr;
    OleCheck(SL.GetWorkingDirectory(AStr, MAX_PATH));
    WorkingDirectory := AStr;
    OleCheck(SL.GetIconLocation(AStr, MAX_PATH, IconIndex));
    IconLocation := AStr;
    OleCheck(SL.GetShowCmd(ShowCmd));
    OleCheck(SL.GetHotKey(HotKey));
  end;
end;

2009. július 4., szombat

Converting roman notation to a numeric value


Problem/Question/Abstract:

How to convert roman notation to a numeric value

Answer:

function RomanToDec(const Value: string): integer;
var
  i, lastValue, curValue: integer;
begin
  Result := 0;
  lastValue := 0;
  for i := Length(Value) downto 1 do
  begin
    case UpCase(Value[i]) of
      'C':
        curValue := 100;
      'D':
        curValue := 500;
      'I':
        curValue := 1;
      'L':
        curValue := 50;
      'M':
        curValue := 1000;
      'V':
        curValue := 5;
      'X':
        curValue := 10;
    else
      raise Exception.CreateFmt('Invalid character: %s', [Value[i]]);
    end;
    if curValue < lastValue then
      Dec(Result, curValue)
    else
      Inc(Result, curValue);
    lastValue := curValue;
  end;
end;

2009. július 3., péntek

Download Url


Problem/Question/Abstract:

Download Url

Answer:

uses WinInet;

function DownloadFile(const Url: string): string;
var
  NetHandle: HINTERNET;
  UrlHandle: HINTERNET;
  Buffer: array[0..1024] of Char;
  BytesRead: dWord;
begin
  Result := '';
  NetHandle := InternetOpen('Delphi 5.x', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);

  if Assigned(NetHandle) then
  begin
    UrlHandle := InternetOpenUrl(NetHandle, PChar(Url), nil, 0, INTERNET_FLAG_RELOAD, 0);

    if Assigned(UrlHandle) then
      { UrlHandle valid? Proceed with download }
    begin
      FillChar(Buffer, SizeOf(Buffer), 0);
      repeat
        Result := Result + Buffer;
        FillChar(Buffer, SizeOf(Buffer), 0);
        InternetReadFile(UrlHandle, @Buffer, SizeOf(Buffer), BytesRead);
      until BytesRead = 0;
      InternetCloseHandle(UrlHandle);
    end
    else
      { UrlHandle is not valid. Raise an exception. }
      raise Exception.CreateFmt('Cannot open URL %s', [Url]);

    InternetCloseHandle(NetHandle);
  end
  else
    { NetHandle is not valid. Raise an exception }
    raise Exception.Create('Unable to initialize Wininet');
end;

//example

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(DownloadFile('http://www.yahoo.com/index.html'));
end;

2009. július 2., csütörtök

Count words in a memo


Problem/Question/Abstract:

Count words in a memo

Answer:

Insert 1 label, 1 button and 1 memo.

procedure TForm1.Button1Click(Sender: TObject);

  function Palabras(Link: string): integer;
  var
    n: integer;
    befspace: boolean;
  begin
    befspace := FALSE;
    if Link = '' then
      Result := 0
    else
      Result := 1;
    for n := 1 to Length(Link) do
    begin
      if befspace and
        (Link[n] <> ' ') and
        (Link[n] <> #13) and
        (Link[n] <> #10) then
        Inc(Result);

      befspace := (Link[n] = ' ') or
        (Link[n] = #13) or
        (Link[n] = #10);
    end;
  end;

begin
  Label1.caption := IntToStr(Palabras(Memo1.Text));
end;
end;

2009. július 1., szerda

Create your first console application to interact with forms


Problem/Question/Abstract:

Interaction between your Console applications and your forms

Answer:

This code demonstrates how to combine console API with usual forms

Create a new -> console application and save it as listing2
Copy the given code in to the console application
Create a form in the same project with name frmDialog1.dfm and dialog1.pas
In the form put three RadioButtons
Now build and run the application
Shift the focus to the console application and type 1 or 2 or 3

Based on the number typed the Radiobutton in the form will be clicked automatically When u press ctrl + c the application exits

program listing2;
{$APPTYPE CONSOLE}
uses
  SysUtils, Windows, Messages, Forms,
  Dialog1 in 'Dialog1.pas' {frmDialog1};

var
  hInput: THandle;
  inRec: TInputRecord;
  dwCount: DWORD;

begin
  {Create a Form in the usual way. The Forms unit ensures that
  the Application object is around to "own" the form.}

  Write('Creating the first Dialog Box...');
  frmDialog1 := TfrmDialog1.Create(Application);
  frmDialog1.Show;
  Writeln('done.');

  Writeln('Press 1, 2 or 3 to change the dialog box. Press Ctrl+ C to exit');

  {Handle the Console input till the user cancels}
  hInput := GetStdHandle(STD_INPUT_HANDLE);
  {GetStdHandle - Returns handle for Standard input/output device}

  while True do
  begin
    {Avoid blocking on user input, so the forms have a chance
    to operate as normal. If we had a message queue present, this
    would be a normal message dispatch loop.}
    Application.ProcessMessages;
    if WaitForSingleObject(hInput, 0) = WAIT_OBJECT_0 then
    begin
      ReadConsoleInput(hInput, inRec, 1, dwCount);
      if (inRec.EventType = KEY_EVENT) and inRec.Event.KeyEvent.bKeyDown then
      begin
        case inRec.Event.KeyEvent.AsciiChar of
          '1':
            begin
              Writeln('->1');
              frmDialog1.RadioButton1.Checked := True;
            end;

          '2':
            begin
              Writeln('->2');
              frmDialog1.RadioButton2.Checked := True;
            end;
          '3':
            begin
              Writeln('->3');
              frmDialog1.RadioButton3.Checked := True;
            end;
        end;
      end;
    end;
  end;
end.