2005. szeptember 30., péntek

How to create a countdown timer


Problem/Question/Abstract:

I was wondering if anyone knew of a way in which you were able to create a timer in which you could set a time (of about 20 minutes) and have it countdown by seconds and be able to stop it.

Answer:

Drop a TTimer onto your form. Set the INTERVAL using the object inspector to 1000. Set the control's Enabled property to False. Use another control, say a TEdit or TSpinEdit to set a variable with the total number of seconds you wish to wait. Use a TButton control to enable the timer. Use a second button to disable the timer. Double-click on the timer to create an OnTimer event handler. In the event handler, decrement the total time counter and check to see if it hit zero.

procedure TForm1.Edit1Change(Sender: TObject);
begin
  {the time is entered in seconds.  If you wish the time to be entered in "hh:mm:ss",
  you will have to parse it and put it into a total seconds format.}
  TotalTime := StrToInt(Edit1.Text);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Timer1.Enabled := true;
  Edit1.Enabled := false; {disable the ability to set the time}
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Timer1.Enabled := false;
  Edit1.Enabled := true; {re-enable the ability to set the time}
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  dec(TotalTime); {decrement the total time counter}
  Edit2.Text := IntToStr(TotalTime); {put the value in an edit box so he can see it}
  if TotalTime = 0 then {have we timed out?}
    {... Do something ...}
end;

Remark:

Rather than decrement a counter in the OnTimer event handler, it's better to compare the current system time to the original start time and calculate the difference. The reason for this is that timer messages are low priority, and it's very likely that some will be lost before being processed, causing any countdown scheme to be inaccurate.

2005. szeptember 29., csütörtök

Save a complete directory


Problem/Question/Abstract:

Is there an API function which gives all the subdirectories and all the files of one particular directory (in order to save a whole directory for example )?

Answer:

You can copy a whole directory with one instruction using the ShFileOperation API function:

procedure TForm1.Button2Click(Sender: TObject);
var
  OpStruc: TSHFileOpStruct;
  frombuf, tobuf: array[0..128] of Char;
begin
  FillChar(frombuf, Sizeof(frombuf), 0);
  FillChar(tobuf, Sizeof(tobuf), 0);
  StrPCopy(frombuf, 'd:\brief\*.*');
  StrPCopy(tobuf, 'd:\temp\brief');
  with OpStruc do
  begin
    Wnd := Handle;
    wFunc := FO_COPY;
    pFrom := @frombuf;
    pTo := @tobuf;
    fFlags := FOF_NOCONFIRMATION or FOF_RENAMEONCOLLISION;
    fAnyOperationsAborted := False;
    hNameMappings := nil;
    lpszProgressTitle := nil;
  end;
  ShFileOperation(OpStruc);
end;

If you need a list of all files and subdirs you have to do a recursive scan using FindFirst/ FindNext.

2005. szeptember 28., szerda

How to use SQL in combination with a TCheckListBox


Problem/Question/Abstract:

On a form, I have a TCheckListBox with records (fields code and name, strings) of a TTable (emp.DB).

[ ] 001 - aaaaaaaaaaa
[ ] 002 - bbbbbbbbbbb
[ ] 003 - ccccccccccc
etc.

How to build an SQL with only the checked items of the CheckListBox? For example, SELECT * FROM emp WHERE code = (???)...

Answer:

You need to inspect all of the items in the TCheckListBox and, for each one checked, add the text of the item (with quotation marks) to a string to be used for an IN predicate in the WHERE clause of your statement.

Written manually, your SQL statement might look like this (for the first two items checked):

SELECT *
FROM emp
WHERE code in ("aaaaaaaaaaa", "bbbbbbbbbbb")

Done programmatically, it would look something like this:

var
  InPredicate: string;
  i: Integer;
begin
  InPredicate := '';
  with Query1 do
  begin
    for i := 0 to (CheckListBox1.Items.Count - 1) do
      if CheckListBox1.Checked[i] then
        InPredicate := InPredicate + '"' + CheckListBox1.Items[i] + '",';
    System.Delete(InPredicate, Length(InPredicate), 1);
    Close;
    SQL[2] := Format('WHERE State IN (%s)', [InPredicate]);
    Open;
  end;
end;

Of course, this assumes the SQL statement starts out with a WHERE clause and this filter will always be on the third line. At any rate, that routine demonstrates dynamically building the values list for the IN predicate.

If you have too many items in that TCheckListBox, it might be possible to exceed the maximum length of a line in the TQuery.SQL property (255 characters). In such cases, you would need to add checking for this and account for building the filter across multiple lines in the SQL statement.

2005. szeptember 27., kedd

Too many programs create files but never cleanup after them self why?


Problem/Question/Abstract:

Yes that is a good question. In this short article I will try to motivate programmers to cleanup after their programs.

Answer:

In this short article I will try to motivate programmers to cleanup after their programs.

A program has several of files to keep track off in this busy world.  A program has supports files like the Ini file type.  The rule here is to remove old stuff from the file it is no longer is using.  You have a new release and you change the topic from one type to another.  Please remove the old one you know where and what - the user does not.

A program can create files as an output or function of the program.  In general the rule is that the program that creates the program gives it to someone else (another program).  In a good world the "other" program now owns the files and should be the one that removes the files when no longer needed or outdated.

A program can create log files.  This is to me always a real good idea to create log files.  The program should be able to run in three different modes: Full debug mode, log error mode, and absolute no logging at all.  One smart way of doing this is to create a folder structure lets say under the Exe location or user defined under setup.  Under the Log folder or whatever you call it create daily folders with the folder name of YYYYMMDD this way your program can easily delete older folders by simply reading the folder name.  You can select to keep all log files, delete all log files that is older than 30 - 60 - 90 days, or you can say I only want the last 7 folders.  The last option is great for programs that may only be used on weekly bases.

If you are in full debug mode you can even let you program email you the log files, so you can monitor the progress of the program.  You can take this to a profiling level where you log every function and then you can see that your clients are really using and what is not that heavily used.  Very good for upgrades information.

A trick regarding log files is to create them as ASCII comma delimited files (you can use the Commatext property in the TStringList).  With a CSV file you can use most database manager to massage the data in the file.  If you are not in the consulting business the CSV file can help you with your client.  If a client want a special report you can guide them to Excel and the book "Excel for dummies" and you clients can create reports till the paper runs out of the printer.

Again please have a function that will cleanup old files.  Here is another solution.

The DeleteAllFilesOlderThan function takes either a path like "C:\MyProgram\" or a full filename like "C:\MyProgram\Tmp\*.Txt".  If the Date is "Now" then all the files in the path or with the filename will be deleted.

{====================================================================}

function DeleteAllFilesOlderThan(const FileName: string; Date: TDateTime): Boolean;
{====================================================================}
var
  SearchRec: TSearchRec;
  sFile, sPath: string;

begin
  Result := True;
  sFile := ExpandFileName(FileName);
  sPath := ExtractFilePath(sFile);
  if FindFirst(sFile, faAnyFile, SearchRec) = 0 then
  begin
    if (SearchRec.Name <> '') and (SearchRec.Name <> '.') and (SearchRec.Name <> '..')
      then
    begin
      if FileDateToDateTime(FileAge(sPath + SearchRec.Name)) < Date then
      begin
        if not SysUtils.DeleteFile(sPath + SearchRec.Name) then
        begin
          Result := False;
        end;
      end;
    end;
    while FindNext(SearchRec) = 0 do
    begin
      if (SearchRec.Name <> '') and (SearchRec.Name <> '.') and (SearchRec.Name <>
        '..') then
      begin
        if FileDateToDateTime(FileAge(sPath + SearchRec.Name)) < Date then
        begin
          if not SysUtils.DeleteFile(sPath + SearchRec.Name) then
          begin
            Result := False;
          end;
        end;
      end;
    end;
  end;
  SysUtils.FindClose(SearchRec);
end;

I use this function as a base function for other functions like:

{====================================================================}

function DeleteAllFilesOlderThan30Days(const FileName: string): Boolean;
{====================================================================}
begin
  Result := DeleteAllFilesOlderThan(FileName, IncMonth(Now, -1));
end;

{====================================================================}

function DeleteAllFilesOlderThan60Days(const FileName: string): Boolean;
{====================================================================}
begin
  Result := DeleteAllFilesOlderThan(FileName, IncMonth(Now, -2));
end;

{====================================================================}

function DeleteAllFilesOlderThan90Days(const FileName: string): Boolean;
{====================================================================}
begin
  Result := DeleteAllFilesOlderThan(FileName, IncMonth(Now, -3));
end;

The Delphi IncMonth works also with negative numbers so if "Now" is May 13 and you are using -2 you will be looking at March 13.

So now your program should know.  Cleanup all the old files and files that the program no longer is using.

2005. szeptember 26., hétfő

How to avoid flicker when switching a TForm from fsNormal to fsStayOnTop


Problem/Question/Abstract:

Is there any way to make a form StayOnTop whitout the little flickering it makes when switching from fsNormal to fsStayOnTop?

Answer:

It could be done, but requires some code from your side:

SetWindowPos(MyFormHandle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE +
  SWP_NOMOVE + SWP_NOACTIVATE);

I think you have to call this again when your application is restored from minimized state, or after closing a modal form.

2005. szeptember 25., vasárnap

Keystroke recording and playing back in the IDE


Problem/Question/Abstract:

Keystroke recording and playing back in the IDE

Answer:

If you write a lot of code, you probably have come across a situation where you need to record some keystrokes and play them back a number of times.

You can now do this in the Delphi IDE by pressing [Ctrl][Shift][R] to start recording, type in the keystrokes you want repeated, and press [Ctrl][Shift][R] to stop recording. To Play back, press [Ctrl][Shift][P].

Works with Default and Classic keymapping, Delphi 2.x and higher.

2005. szeptember 24., szombat

How to store the HTML source of a TWebBrowser programmatically


Problem/Question/Abstract:

Using the TWebBrowser component (Delphi 5), I am looking for a way to store the HTML code of the TWebBrowser. When I use the right mouse button, I can store the HTML code, but I would like to do this programmatically.

Answer:

uses
  ActiveX;

{Saves the HTML document - referenced through 'Document' - to a stream}

procedure SaveDocumentSourceToStream(Document: IDispatch; Stream: TStream);
var
  PersistStreamInit: IPersistStreamInit;
  StreamAdapter: IStream;
begin
  {Delete content of stream}
  Stream.Size := 0;
  Stream.Position := 0;
  {IPersistStreamInit - get document interface}
  if Document.QueryInterface(IPersistStreamInit, PersistStreamInit) = S_OK then
  begin
    {Use StreamAdapter to get the IStream interface for our stream}
    StreamAdapter := TStreamAdapter.Create(Stream, soReference);
    {Save data from document into stream}
    PersistStreamInit.Save(StreamAdapter, False);
    {Destroy StreamAdapter. Optional.}
    StreamAdapter := nil;
  end;
end;

2005. szeptember 23., péntek

Credit Card Validation


Problem/Question/Abstract:

Credit Card Validation

Answer:

Are you in need to validate a credit card? The following routine does some basic checking and returns the type of the credit card as a number - or use the const array to get the type of credit card by name. (E.g. 'Mastercard').

This code does not check that the credit card is actually valid, that it is good for a purchase or whether it belongs to a certain person. To accept any kind of orders, you need to do an address verification, combined with checking the expiration date.

The routine is still handy as an input validator on forms. You may download it here.


program CardTest;

uses
  Dialogs,
  SysUtils;

{$R *.RES}

const
  CardType: array[0..4] of string = ('Invalid', 'Amex', 'Visa', 'Mastercard',
    'Discover');

function Vc(C: string): Integer;
var
  Card: string[21];
  VCard: array[0..21] of Byte absolute Card;
  XCard: Integer;
  Cstr: string[21];
  y,
    x: Integer;
begin
  Cstr := '';
  FillChar(VCard, 22, #0);
  Card := C;
  for x := 1 to 20 do
    if (VCard[x] in [48..57]) then
      Cstr := Cstr + Chr(VCard[x]);
  Card := '';
  Card := Cstr;
  XCard := 0;
  if not odd(Length(Card)) then
    for x := (Length(Card) - 1) downto 1 do
    begin
      if odd(x) then
        y := ((VCard[x] - 48) * 2)
      else
        y := (VCard[x] - 48);
      if (y >= 10) then
        y := ((y - 10) + 1);
      XCard := (XCard + y)
    end
  else
    for x := (Length(Card) - 1) downto 1 do
    begin
      if odd(x) then
        y := (VCard[x] - 48)
      else
        y := ((VCard[x] - 48) * 2);
      if (y >= 10) then
        y := ((y - 10) + 1);
      XCard := (XCard + y)
    end;
  x := (10 - (XCard mod 10));
  if (x = 10) then
    x := 0;
  if (x = (VCard[Length(Card)] - 48)) then
    Vc := Ord(Cstr[1]) - Ord('2')
  else
    Vc := 0
end;

begin
  ShowMessage(CardType[Vc('4479750100222862')]);
end.

2005. szeptember 22., csütörtök

Getting the number of records from a fixed-length ASCII file


Problem/Question/Abstract:

I work a lot with fixed-length ASCII files, and I need to know how many total lines there are in a file. Sure, I can open up the file in a text editor, but really large files take forever to load. Is there a better way?

Answer:

As Mr. Miyagi said to Daniel-san in Karate Kid, "Funny you should ask..." Yes, there is a better way. What I'm going to show you may not be the best way, but it's reasonably fast, and exceptionally easy to use. It starts out with this premise. If you know the total number of bytes in the file and know the length of each record, then if you divide the total bytes by the record length, you should get the number of records in the file. Sounds reasonable, right? And it's exactly the way we do it.

For this example, I used a TFileStream object to open up my text file. I like using this particular object because it has come convenient methods and properties that I can use to get the information that I need; in particular, the Size property and the Read and Seek methods. How do I use them? Let's go through some plain English to give you an idea:

Open up a file stream on a text file
Get its total byte size
Now, serially move through the file, byte-by-byte reading each byte into a single-character buffer until you reach a return character (#13).
As you pass each byte, increment a counter variable that will serve as both a file reference point and later, the length of the record.
When you get to the return character, break out of the loop, add 2 to the reference counter (to account for the #13#10 CR/LF pair).
Finally return the result as the file size divided by the record length.

Here's the code that accomplishes the English above:

{======================================================================
This function will give you the exact record count of a file. It uses
a TFileStream and goes through it byte by byte until it encounters
a #13. When it does, it adds 2 to the recLen to account for the #13#10
CR/LF pair, then divides the byte size of the file by the record true
record length.

Note that this will only work on text files.
======================================================================}

function GetTextFileRecords(FileName: string): Integer;
var
  ts: TFileStream;
  fSize,
    recLen: Integer;
  buf: Char;
begin
  buf := #0;
  recLen := 0;
  //Open up a File Stream
  ts := TFileStream.Create(FileName, fmOpenRead);
  with ts do
  begin
    //Get the File Size
    fSize := Size;
    try
      //Move through the file a byte at a time
      while (buf <> #13) do
      begin
        Seek(recLen, soFromBeginning);
        Read(buf, 1);
        Inc(recLen);
      end
    finally
      Free;
    end;
  end;
  recLen := recLen + 2; //Need to account for CR/LF pair.
  Result := Round(fSize / recLen);
end;

As I mentioned above, this may not be the "best" way to do this, but it is a way to approach this problem. A faster way to do this would have been to open up the file as a regular file, then read a bunch of bytes into a large buffer, let's say an Array of Char 4K in size. Perusing through an array is much faster than moving through a file, but the disadvantage there is that you run the risk of having the buffer too small. I've seen some fixed-length ASCII files with line sizes up to 8K.

In any case, the method I presented above may not be the most efficient, but it's safe, and it works. Besides, what's a few milliseconds worth to you? Have at it!

Wait a minute! 10:00PM

Okay, I couldn't resist. I realized that I could've done better than my example above. Here's the method I described immediately above:

function GetTextFileRecords(FileName: string): Integer;
const
  BlockSize = 8192;
var
  F: file;
  fSize,
    amtXfer: Integer;
  buf: array[0..BlockSize] of Char;
begin
  AssignFile(F, FileName); //Open up the text file as an untyped file
  Reset(F, 1);
  fSize := FileSize(F); //Get the file size
  BlockRead(F, buf, BlockSize, amtXfer); //read in up to an 8K block
  CloseFile(F); //close the file, you're done
  Result := Round(fSize / (Pos(#13, StrPas(buf)) + 1));
end;

There are several things different about this function as opposed to the function above. First of all, it involves a lot less code. This is due to not have to perform class constructor; I open up an untyped file, read a big block, get its size, then immediately close it. Notice too that I don't use a loop to find a #13. Instead, I use the StrPas function to convert the array of char into a string that's passed to the Pos function that will give me the position of the return character; thus the record length. Adding one to this value will account for the #10 portion of the CR/LF pair.

Because I don't have to deal with constructing an object, this method is a lot faster than method above, and amazingly it's not very complicated. Where this type of operation can get tricky is with the BlockRead function. In order to use BlockRead successfully, you need to specify a record size. That can be a bit confusing, so just remember this: for byte- by-byte serial reads through a file, always use a record size of 1. Also, notice that I also included a variable called amtXfer. BlockRead fills this with the actual number of bytes read. If you don't supply this, you'll raise an exception when BlockRead executes. That's not too much of a problem because all you need to do is create an exception handling block - but why bother? Just supply the variable, and you don't have to worry about the exception.

Okay, now it's time to close this out... Is this the best way to get the record length of a fixed length text file? Admittedly, it's one of the faster ways save using Assembler. But I'm wondering what a purely WinAPI call set would look like.... If you have any ideas, please make sure to let me know!

Here I Go Again! 11:05 PM

I guess my curiosity got the best of me tonight, because I just wasn't satisfied doing just the BlockRead method. I knew there had to be another way to do it with WinAPI calls. So I did just that. Look at the code below:

function GetTextFileRecordsWinAPI(FileName: string): Integer;
const
  BlockSize = 8192;
var
  F: THandle;
  amtXFer,
    fSize: DWORD;
  buf: array[0..BlockSize] of Char;
begin
  //Open up file
  F := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ, nil,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_NO_BUFFERING, 0);
  fSize := GetFileSize(F, nil); //Get the file's size
  ReadFile(F, buf, BlockSize, amtXfer, nil); //Read a block from the file
  CloseHandle(F);
  Result := Round(fSize / (Pos(#13, StrPas(buf)) + 1));
end;

This method is almost exactly the same as the one immediately above, but instead uses WinAPI calls to accomplish the same task.

Now which method is better? I DON'T KNOW! Actually, for simplicity's sake, I prefer the elegance of the second method - there's just a lot less coding involved. With the WinAPI method, while it may require one less line of code, the CreateFile function is not the easiest thing to work with - I spent a bit of time Alt-Tabbing between the code editor and Windows help to get the syntax and constants right. Granted, it's easier now that I've done it, but it's not a method that I prefer.

So I'll leave it up to you to decide which method you like better.

2005. szeptember 21., szerda

Set margins in a TMemo


Problem/Question/Abstract:

How to set margins in a TMemo

Answer:

EM_SETRECT message is sent to Memo to fix the size of the canvas of the component.

procedure TForm1.Button1Click(Sender: TObject);
var
  R: TRect;
  LeftMargin: integer;
  RightMargin: integer;
begin
  LeftMargin := 20;
  RightMargin := 10;

  R := Memo1.ClientRect;
  R.Left := R.Left + LeftMargin;
  R.Top := R.Top + 2;
  R.Bottom := R.Bottom - 2;
  R.Right := R.Right - RightMargin;
  SendMessage(Memo1.Handle, EM_SETRECT, 0, Longint(@R));
end;

2005. szeptember 20., kedd

How to disable the ability of a MDI child form to move


Problem/Question/Abstract:

I try to disable the ability to move MDI children around. Problem is that if I have tiled for example 2 MDI's, I cannot change focus to another one.

Answer:

procedure TForm1.WMSyscommand(var msg: TWMSyscommand);
begin
  if (msg.cmdtype and $FFF0) <> SC_MOVE then
    inherited;
end;

2005. szeptember 19., hétfő

How to store two strings in the item of a TComboBox


Problem/Question/Abstract:

I want to store a second string in a combobox item. How can I do this without defining an object wrapper?

Answer:

procedure AddString(const S1, S2: string; Items: TStrings);
var
  Obj: TObject;
begin
  Obj := nil;
  string(Obj) := S2;
  Items.AddObject(S1, Obj);
end;

procedure DeleteItem(const I: Integer; Items: TStrings);
var
  Obj: TObject;
begin
  Obj := Items.Objects[I];
  Items.Objects[I] := nil;
  string(Obj) := '';
end;


Just be sure to go over every item and release the string.

2005. szeptember 18., vasárnap

How to control focus in a MDI application


Problem/Question/Abstract:

Status: MDI-application. MDI window and one child window. MDI window owns a TPanel component, which owns any component which can get focus (TEdit for example). Child window owns a TDBGrid component.

Problem: After running this simple test application, the focus is set on child window's first focusable component - TDBGrid. After switching focus to TEdit component owned by MDI window, there is no more possibility to switch focus back to TDBGrid component owned by child window. TDBGrid component is immune to any mouse events. Why? It looks like a child window is thinking about still having focus.

Answer:

This is one of the many shortcomings of the Windows MDI framework, it has never been designed to cope with controls outside the MDI children that can take the focus. You can trick it by sending a WM_MDIACTIVATE message to the active MDI child, here demonstrated by an OnClick handler for a combobox on the toolbar:

procedure TMainForm.ComboBox1Click(Sender: TObject);
begin
  { ... other actions }
  if Assigned(ActiveMDIChild) then
    with ActiveMDIChild do
      sendmessage(handle, WM_MDIACTIVATE, 0, handle);
end;

2005. szeptember 17., szombat

Determining if a string matches a pattern with wildcards ('?' and '*')


Problem/Question/Abstract:

Is there a LIKE function in Delphi that compares a string with a pattern?

Answer:

Solve 1:

Sometimes we need to know if a string matches a pattern, which is a string with wildcards (for example '?' and '*'). Here we implement a function that returns True if the string matches the pattern and False if not.

function Like(AString, Pattern: string): boolean;
var
  i, n, n1, n2: integer;
  p1, p2: pchar;
label
  match, nomatch;
begin
  AString := UpperCase(AString);
  Pattern := UpperCase(Pattern);
  n1 := Length(AString);
  n2 := Length(Pattern);
  if n1 < n2 then
    n := n1
  else
    n := n2;
  p1 := pchar(AString);
  p2 := pchar(Pattern);
  for i := 1 to n do
  begin
    if p2^ = '*' then
      goto match;
    if (p2^ <> '?') and (p2^ <> p1^) then
      goto nomatch;
    inc(p1);
    inc(p2);
  end;
  if n1 > n2 then
  begin
    nomatch:
    Result := False;
    exit;
  end
  else if n1 < n2 then
  begin
    for i := n1 + 1 to n2 do
    begin
      if not (p2^ in ['*', '?']) then
        goto nomatch;
      inc(p2);
    end;
  end;
  match:
  Result := True;
end;

Sample call

if Like('Walter', 'WA?T*') then
  ShowMessage('It worked!');

If you want to see another example, we use this function to determine if a file name matches a specification in the article "Determining if a file name matches a specification" (keyword: MatchesSpec).


Solve 2:

There is a built in Delphi function called MatchesMask(). It takes * , ? and sets as parameters.

{...}
if MatchesMask('Hello World', '[H-K]?????[W-Y]*') then
  
{...}
if MatchesMask(FileName, '*.exe') then
  
{...}

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

2005. szeptember 16., péntek

How to create a TDrawGrid where all cells act as buttons


Problem/Question/Abstract:

Is there anybody who knows how to subclass the existing TDrawGrid so that all the cells act as buttons? I would like the OnDrawCell to return the inner rectangle of the button look and set the colors of the bevel so that they look like a button.

Answer:

unit ButtonDrawGrid;

interface

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

type
  TPBButtonDrawGrid = class(TDrawGrid)
  private
    FCellDown: TGridCoord;
  protected
    { Protected declarations }
    procedure DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    function SelectCell(ACol, ARow: Longint): Boolean; override;
  public
    constructor Create(aOwner: TComponent); override;
  end;

procedure Register;

implementation

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

{ TButtonDrawGrid }

constructor TPBButtonDrawGrid.Create(aOwner: TComponent);
begin
  inherited;
  FCellDown.X := -1;
  FCellDown.Y := -1;
end;

procedure TPBButtonDrawGrid.DrawCell(ACol, ARow: Integer; ARect: TRect;
  AState: TGridDrawState);
var
  r: TRect;
  style: DWORD;
begin
  r := ARect;
  if not (gdFixed in aState) then
  begin
    Canvas.Brush.Color := clBtnFace;
    Canvas.Font.Color := clBtnText;
    style := DFCS_BUTTONPUSH or DFCS_ADJUSTRECT;
    if (FCellDown.X = aCol) and (FCellDown.Y = aRow) then
      style := style or DFCS_PUSHED;
    DrawFrameControl(Canvas.Handle, r, DFC_BUTTON, style);
  end;
  inherited DrawCell(ACol, aRow, r, aState);
end;

procedure TPBButtonDrawGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  cell: TGridCoord;
begin
  if (Button = mbLeft) and ((Shift - [ssLeft]) = []) then
  begin
    MousetoCell(X, Y, cell.X, cell.Y);
    if (cell.X >= FixedCols) and (cell.Y >= FixedRows) then
    begin
      FCellDown := cell;
      InvalidateCell(cell.X, cell.Y);
    end;
  end;
  inherited;
end;

procedure TPBButtonDrawGrid.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  cell: TGridCoord;
begin
  if Shift = [ssLeft] then
  begin
    MousetoCell(X, Y, cell.X, cell.Y);
    if not CompareMem(@cell, @FCellDown, Sizeof(cell)) then
    begin
      if (FCellDown.X >= 0) and (FCellDown.Y >= 0) then
        InvalidateCell(FCellDown.X, FCellDown.Y);
      FCellDown := cell;
      InvalidateCell(cell.X, cell.Y);
    end;
  end;
  inherited;
end;

procedure TPBButtonDrawGrid.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if (Button = mbLeft) and (Shift = []) then
  begin
    InvalidateCell(FCellDown.X, FCellDown.Y);
    FCellDown.X := -1;
    FCellDown.Y := -1;
  end;
  inherited;
end;

function TPBButtonDrawGrid.SelectCell(ACol, ARow: Integer): Boolean;
begin
  result := false;
end;

end.

2005. szeptember 15., csütörtök

How to create a custom TShape with a caption


Problem/Question/Abstract:

I'd like to read text from a Unicode text file, but don't know how to do this. It looks like ReadLn only works with single-byte character sets.

Answer:

Here is how you can add a caption:

unit SampleShape;

interface

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

type
  TSampleShape = class(TShape)
  private
    { Private declarations }
  protected
    { Protected declarations }
    procedure Paint; override;
    procedure CMFontChanged(var Msg: TMessage); message CM_FONTCHANGED;
    procedure CMTextChanged(var Msg: TMessage); message CM_TEXTCHANGED;
  public
    { Public declarations }
  published
    { Published declarations }
    property Caption;
    property Font;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TSampleShape]);
end;

procedure TSampleShape.CMFontChanged(var Msg: TMessage);
begin
  inherited;
  Invalidate;
end;

procedure TSampleShape.CMTextChanged(var Msg: TMessage);
begin
  inherited;
  Invalidate;
end;

procedure TSampleShape.Paint;
var
  R: TRect;
begin
  inherited;
  Canvas.Font.Assign(Font);
  R := ClientRect;
  DrawText(Canvas.Handle, PChar(Caption), -1, R, DT_VCENTER or
    DT_CENTER or DT_SINGLELINE);
end;

end.

2005. szeptember 14., szerda

Creating an equivalent to the missing TListView.OnColumnDblClick


Problem/Question/Abstract:

How to subclass your header-control (using a TListView), to receive a OnColumnDblClick- equivalent notification?

Answer:

This requires a bit of work. MS did not see fit to send a notification to the TListView when the user double-clicks on the header. But the header control class does have the CS_DBLCLKS style, so it does get WM_LBUTTONDBLCLK messages, it just does not do anything with them.
To get at these messages requires API-style subclassing of the header control. How? See below.


uses..., Commctrl;
...
const
  UM_LISTVIEW_COLUMN_DBLCLICK = WM_USER + 1982;
  ....
    { the HeaderProc function should look something like this: }

function
  HeaderProc(wnd: HWND; msg: Cardinal; wparam: WPARAM; lparam: LPARAM): Longint;
    stdcall;
var
  hti: THDHitTestInfo;
begin
  Result := CallWindowProc(Pointer(GetWindowLong(wnd, GWL_USERDATA)),
    wnd, msg, wparam, lparam);
  if msg = WM_LBUTTONDBLCLK then
  begin
    FillChar(hti, sizeof(hti), 0);
    hti.Point := SmallPointToPoint(TSmallPoint(lparam));
    if SendMessage(wnd, HDM_HITTEST, 0, Longint(@hti)) >= 0 then
      if hti.Flags = HHT_ONHEADER then
        PostMessage(MainForm.Handle, UM_LISTVIEW_COLUMN_DBLCLICK, hti.Item, 0);
    { Change MainForm to whatever you need }
  end;
end;

procedure TMainForm.FormCreate(Sender: TObject);
var
  wnd: HWND;
  oldProc: Integer;
begin
  {beginning of workaround for missing TListView.OnColumnDblClick}
  wnd := GetWindow(aListView.handle, GW_CHILD); { <-- your TListView's name here }
  if wnd <> 0 then
  begin
    if (GetClassLong(wnd, GCL_STYLE) and CS_DBLCLKS) <> 0 then
    begin
      oldproc := GetWIndowLong(wnd, GWL_WNDPROC);
      if GetWindowLong(wnd, GWL_USERDATA) <> 0 then
        raise
          Exception.Create('Cannot sublcass ListView header, USERDATA already in use');
      SetWIndowLong(wnd, GWL_USERDATA, oldproc);
      SetWindowLong(wnd, GWL_WNDPROC, integer(@HeaderProc));
    end;
  end
  else
    ShowMessage('ListView component in vsReport state is missing !!!');
  {...}
  {Do some more wonderful things}
end;

and then don't forget to declare a custom message handler for UM_LISTVIEW_COLUMN_DBLCLICK (this will be your OnColumnDblClick equivalent).

2005. szeptember 13., kedd

How to create an 'Easter Egg' in an application


Problem/Question/Abstract:

How to create an 'Easter Egg' in an application

Answer:

1. Give the form a field of type String:


Match: string;


2. Declare a constant that represents the character sequence that needs to be typed in order
     for the Easter Egg to appear. For example:

const
  Target = ' abc ' #1;


(In this example, you have to type "a"  "b"  "c" and finally CTRL - A)


3. Set the forms KeyPreview property to True.


4. In the dialog's OnCreate event handler, do this:


procedure TMyAboutBox.FormCreate(Sender: TObject);
begin
  Match := ''
end;


5. In the dialog's OnKeyPress event handler, do this:


procedure TMyAboutBox.FormKeyPress(Sender: TObject; var Key: Char);
begin
  Match := Match + Key;
  if Pos(Match, Target) <> 1 then
    Match := ''
  else if Match = Target then
    ShowMessage('Congratulations')
end;

2005. szeptember 12., hétfő

Display hints on the title bar of a TForm


Problem/Question/Abstract:

How to display hints on the title bar of a TForm

Answer:

To accomplish this you need to create a handler for the OnHint event for TApplication. Whenever a hint is going to fire, Delphi calls the TApplication.OnHint event for processing, if nothing is defined then the default processing occurs (i.e. the yellow tool-tip window).

To override the event, you need to define a TNotifyEvent method in your form and assign it to Application.OnHint. Following is sample code that demonstrates this. To use, create a new project and drop three buttons on the form. Then set the Form's ShowHint property to True. Finally enter the following code and run the application, when you move the mouse over the buttons, their hint will appear as the form's caption.

unit Sample1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    procedure DoHint(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

procedure TForm1.DoHint(Sender: TObject);
begin

  {A hint can contain to pieces, a short and long hint separated by a pipe '|' (e.g. "Open File|Displays a file browser to select file to open". The short hint is "Open File" and the long hint is "Displays a file browser to select file to open".)

  To display the short portion, use the global method
  GetShortHint(const Hint: string): string;

  To display the long portion, use the global method
  GetLongHint(const Hint: string): string;}

  Caption := GetLongHint(Application.Hint);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnHint := DoHint;
  {Assigns the form's current caption as the form's hint}
  Hint := Caption;
  {Assign Hints to the buttons}
  Button1.Hint := 'Button One|This is the hint for button 1';
  Button2.Hint := 'Button Two|This is the hint for button 2';
  Button3.Hint := 'Button Three|This is the hint for button 3';
end;

end.

2005. szeptember 11., vasárnap

Determining if a file name matches a specification


Problem/Question/Abstract:

How can I know if a file name matches a specification with wildcards?

Answer:

Sometimes we need to know if a file name matches a file specification (a name with wildcards: '?' and '*'). Here we implement a function that returns True if the given file name matches a specification and False if not.

function MatchesSpec(const FileName,
  Specification: string): boolean;
var
  SName, SExt, FName, FExt: string;
begin
  FName := ExtractFileName(FileName);
  SName := ExtractFileName(Specification);
  FExt := ExtractFileExt(FName);
  SExt := ExtractFileExt(SName);
  SetLength(FName, Length(FName) - Length(FExt));
  SetLength(SName, Length(SName) - Length(SExt));
  if SName = '' then
    SName := '*';
  if SExt = '' then
    SExt := '.*';
  if FExt = '' then
    FExt := '.';
  Result := Like(FName, SName) and Like(FExt, SExt);
end;

NOTE: The Like function has been featured in my article  
"Determining if a string matches a pattern with wildcards ('?' and '*')"

Sample call

if MatchesSpec('Document1.doc', 'DOC*.DO?') then
  ShowMessage('It worked!');

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

2005. szeptember 10., szombat

How to create a TListBox with coloured entries


Problem/Question/Abstract:

I add protocol messages into a listbox, simple lines of text like "success" and "failed". Now I want to have a different background color for every item. For example the "failed" ones in red and the "successed" in green. How to achieve this?

Answer:

Put a TListBox on a form, call it ListBox1, set its style to "lbOwnerDrawFixed" and implement the following event:

procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  Flags: Longint;
begin
  with ListBox1 do
  begin
    { If the item is not selected, then...}
    if not (odSelected in State) then
      with Canvas.Brush do
      begin
        { Choose the appropriate color}
        case Index of
          0: Color := clBlue;
          1: Color := clRed;
          2: Color := clGreen;
        end;
      end;
    { Draw the colored rectangle.}
    ListBox1.Canvas.FillRect(Rect);
    if Index < Items.Count then
    begin
      Flags := DrawTextBiDiModeFlags(DT_SINGLELINE or DT_VCENTER or DT_NOPREFIX);
      if not UseRightToLeftAlignment then
        Inc(Rect.Left, 2)
      else
        Dec(Rect.Right, 2);
      DrawText(Canvas.Handle, PChar(Items[Index]), Length(Items[Index]), Rect, Flags);
    end;
  end;
end;

2005. szeptember 9., péntek

Create a transparent form which is still moveable


Problem/Question/Abstract:

How to create a transparent form which is still moveable

Answer:

You can achieve a transparency effect by creating a window region that includes only the controls on the form but not the background. Making a window transparent and still moveable:

procedure TForm1.Button2Click(Sender: TObject);
var
  frmRegion, tempRegion: HRGN;
  i: Integer;
  Arect: TRect;
begin
  frmRegion := 0;
  for I := 0 to ControlCount - 1 do
  begin
    { create a region for the control }
    aRect := Controls[i].BoundsRect;
    { coordinates have to be window-relative, not client area relative }
    OffsetRect(aRect, clientorigin.x - left, clientorigin.y - top);
    tempRegion := CreateRectRgnIndirect(aRect);
    { merge the region with the "summary" region we are building }
    if frmRegion = 0 then
      frmRegion := tempRegion
    else
    begin
      CombineRgn(frmRegion, frmRegion, tempRegion, RGN_OR);
      DeleteObject(tempRegion);
    end;
  end;
  { create a region for the caption and menu bar and add it to the summary }
  tempregion := CreateRectRgn(0, 0, Width, GetSystemMetrics(SM_CYCAPTION) +
    GetSystemMetrics(SM_CYSIZEFRAME) +
    GetSystemMetrics(SM_CYMENU) * Ord(Menu < > nil));
  CombineRgn(frmRegion, frmRegion, tempRegion, RGN_OR);
  DeleteObject(tempRegion);
  SetWindowRgn(handle, frmRegion, true);
end;

2005. szeptember 8., csütörtök

Four different ways to load and play sound files


Problem/Question/Abstract:

Four different ways to load and play sound files

Answer:

There are four ways of loading and playing sound in your program:

Use the sndPlaySound() function to directly play a wave file
Read the wave file into memory, then use the sndPlaySound() to play the wave file
Use sndPlaySound to directly play a wave file thats embedded in a resource file attached to your application.
Read a wave file thats embedded in a resource file attached to your application into memory, then use the sndPlaySound() to play the wave file


Sample Code:

unit PlaySnd1;

interface

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

type
  TForm1 = class(TForm)
    PlaySndFromFile: TButton;
    PlaySndFromMemory: TButton;
    PlaySndbyLoadRes: TButton;
    PlaySndFromRes: TButton;
    procedure PlaySndFromFileClick(Sender: TObject);
    procedure PlaySndFromMemoryClick(Sender: TObject);
    procedure PlaySndFromResClick(Sender: TObject);
    procedure PlaySndbyLoadResClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}
{$R snddata.res} // Resource file containing the *.wav file

uses
  MMSystem;

{1. Use the sndPlaySound() function to directly play a wave file}

procedure TForm1.PlaySndFromFileClick(Sender: TObject);
begin
  sndPlaySound('hello.wav', SND_FILENAME or SND_SYNC);
end;

{2. Read the wave file into memory, then use the sndPlaySound() to play the wave file}

procedure TForm1.PlaySndFromMemoryClick(Sender: TObject);
var
  f: file;
  p: pointer;
  fs: integer;
begin
  AssignFile(f, 'hello.wav');
  Reset(f, 1);
  fs := FileSize(f);
  GetMem(p, fs);
  BlockRead(f, p^, fs);
  CloseFile(f);
  sndPlaySound(p, SND_MEMORY or SND_SYNC);
  FreeMem(p, fs);
end;

{3. Use sndPlaySound to directly play a wave file thats embedded in a resource file attached
to your application}

procedure TForm1.PlaySndFromResClick(Sender: TObject);
begin
  PlaySound('HELLO', hInstance, SND_RESOURCE or SND_SYNC);
end;

{4. Read a wave file thats embedded in a resource file attached to your application into memory,
then use the sndPlaySound() to play the wave file}

procedure TForm1.PlaySndbyLoadResClick(Sender: TObject);
var
  h: THandle;
  p: pointer;
begin
  h := FindResource(hInstance, 'HELLO', 'WAVE');
  h := LoadResource(hInstance, h);
  p := LockResource(h);
  sndPlaySound(p, SND_MEMORY or SND_SYNC);
  UnLockResource(h);
  FreeResource(h);
end;

end.

2005. szeptember 7., szerda

Interbase and standard database components


Problem/Question/Abstract:

How do I use an Interbase database with standard database controls?

Answer:

I have struggled for hours to get to grips with this. Manuals available aren't very clear on this.

What you need:

Set BDE alias for Database (BDE Administrator)
Add table (tblDepartm) ,Datasource,Database and UpdateSQL component to a dataform.
Set the Database field of the database to the name of the database component (Not to the Alias of the BDE, this is cross linked through the database component)
Add Data aware component to main form. Set there source to Datasource above.
Add Post button on the main form. (Set the button to post the table i.e. table1.post)
On the UpdateSQL component right click to open the SQLupdate editor. Generate the code for the various functions (Select, update, delete);
Set the tables UpdateObject to the UpdateSQL component.
Set CachedUpdates to true on the table.

How does it work. Each table has an UpdateSQL component associated with it. This handles updates to a live record set. Updating a live recordset of a Interbase database wouldn't be possible without a UpdateSQL component. The code in the UpdateSQL component handles the changes to the underlying table of the table component. When updating a table (pressing the post button) the Onupdaterecord event (below) is called which then tells the UpdateSQL component which update kind to use (insert,delete,update). Once this is applied to the UpdateSQL component the changes are made to the local cached dataset. Remember these updates will not be applied until the Database component's Applyupdates method is called. (Below)

Type the following into the Onupdaterecord event of the Table:

procedure TDataform.tblDepartmUpdateRecord(DataSet: TDataSet;
  UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
begin
  {This is a confusing concept, but the Apply below tells the
  update component which SQL commands to use , insert , alter or select and
  when you applyupdates on the database for this dataset the correct
  sql statement is utilised and the dataset is updated. See on closequery
  method of Department form}
  try
    uptDepartm.Apply(Updatekind);
    UpdateAction := uaApplied;
  except
    UpdateAction := uaFail;
  end;

end;

This is the code to close the main form. It checks if any updates are pending on the database and then applies the updates. Only now is the underlying table updated on the server. Keep in mind that you have to close and open the tables to reflect the changes on the client side because a refresh is not allowed on a DBMS like Interbase.

procedure TfrmDepart.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
var
  Res: integer;
begin
  with Dataform do
    if tblDepartm.UpdatesPending then
    begin
      Res := messagedlg('Save changes ?', mtInformation, mbYesNoCancel, 0);
      if Res = mrYes then
      begin
        dbMTS.Applyupdates([tblDepartm]);
        {Somehow the first time you applyupdates on a dataset it is very
        slow, but thereafter it is lightning fast. Must be that the BDE
        only caches the info once you call the applyupdates method for the
        first time!}
        tblDepartm.close;
        tblDepartm.open;
      end;
      Canclose := Res <> mrCancel;
    end;
end;

2005. szeptember 6., kedd

How to change contrast in a colour image


Problem/Question/Abstract:

How to change contrast in a color image

Answer:

Changing contrast in a greyscale image is fairly easy. You can use histogram equilization or histogram stretching. Contrast enhancing a color image is a bit tricker since there are three color planes. In some cases you can histostretch each color plane (like HistoStretchGrays above), but usually this will result in an undesirable color shift.

Here's some code for changing contrast in a 256 colour image:

function ContrastLUT(Amount: Integer): array[Byte] of Byte;
var
  i, z: Integer;
begin
  for i := 0 to 126 do
  begin
    z := i - ((Abs(128 - i) * Amount) div 256);
    if z > 255 then
      z := 255
    else if z < 0 then
      z := 0;
    Result[i] := z;
  end;
  for i := 127 to 255 do
  begin
    z := i + ((Abs(128 - i) * Amount) div 256);
    if z > 255 then
      z := 255
    else if z < 0 then
      z := 0;
    Result[i] := z;
  end;
end;

Apply the lookup table to your bitmap bytes to adjust contrast.

2005. szeptember 5., hétfő

How do I stop my TEdit control beeping?


Problem/Question/Abstract:

If a user hits the enter key in a TEdit box, he is 'rewarded' with an annoying beep.

Answer:

To avoid this, set KeyPreview := True for the form and capture the enter key in the OnKeyPress form event.
If the sender is the editbox, you now can process the return and change it to something else like null before the editbox sees it.

2005. szeptember 4., vasárnap

How to create a flat TCheckBox


Problem/Question/Abstract:

I want to inherit the TCheckbox class in order to create a new TCheckBox class that is the flat version for TCheckbox. What style to override in order to make it a flat checkbox instead of a 3D checkbox?

Answer:

{ ... }
TExCheckBox = class(TCheckBox)
private
  { Private declarations }
  FFlat: Boolean;
  FMultiLine: Boolean;
  FPushLike: Boolean;
  procedure SetFlat(const Value: Boolean);
  procedure SetMultiLine(const Value: Boolean);
  procedure SetPushLike(const Value: Boolean);
protected
  { Protected declarations }
  procedure CreateParams(var Params: TCreateParams); override;
public
  { Public declarations }
  constructor Create(AOwner: TComponent); override;
published
  { Published declarations }
  property Flat: Boolean read FFlat write SetFlat default true;
  property MultiLine: Boolean read FMultiLine write SetMultiLine default false;
  property PushLike: Boolean read FPushLike write SetPushLike default false;
end;

{ TExCheckBox }

constructor TExCheckBox.Create(AOwner: TComponent);
begin
  FFlat := true;
  FMultiLine := false;
  FPushLike := false;
  inherited Create(AOwner);
end;

procedure TExCheckBox.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  if FFlat then
    Params.Style := Params.Style or BS_FLAT
  else
    Params.Style := Params.Style and not BS_FLAT;
  if FMultiLine then
    Params.Style := Params.Style or BS_MULTILINE
  else
    Params.Style := Params.Style and not BS_MULTILINE;
  if FPushLike then
    Params.Style := Params.Style or BS_PUSHLIKE
  else
    Params.Style := Params.Style and not BS_PUSHLIKE;
end;

procedure TExCheckBox.SetFlat(const Value: Boolean);
begin
  if Value <> FFlat then
  begin
    FFlat := Value;
    RecreateWnd;
  end;
end;

procedure TExCheckBox.SetMultiLine(const Value: Boolean);
begin
  if Value <> FMultiLine then
  begin
    FMultiLine := Value;
    RecreateWnd;
  end;
end;

procedure TExCheckBox.SetPushLike(const Value: Boolean);
begin
  if Value <> FPushLike then
  begin
    FPushLike := Value;
    RecreateWnd;
  end;
end;

2005. szeptember 2., péntek

Plug-in Internet Protocols (without DLL's)


Problem/Question/Abstract:

Show how to make a plugin protocol which executes your program and can pass variables to your application (like mailto:, http:, telnet:, outlook:,...)

Answer:

After searching through the internet for a way to integrate my application into Internet Explorer in the form of a protocol I found 2 different ways that were documented and included delphi code:

myprotocol://
http://mynamespace//

However I was looking for a way which would look like this:

myprotocol:

Eventually I gave up, until I noticed that on http://messenger.yahoo.com/messenger/imv they used this to execute a chat window with the desired theme. So I decided to look in the registry (as with all previous work I had discovered that the relevant data, usually including CLSID's would be linked together in the registry) and I discovered something
incredibly simple yet effective. Rather than using a DLL and CLSID's they had simply added some keys and values to the HKEY_CLASSES_ROOT exactly the same way as you would if you were associating a file-type. However there were 2 abnormal values:

HKEY_CLASSES_ROOTymsgr (Default) was equal to "URL: YMessenger Protocol"
There was a blank string added as HKEY_CLASSES_ROOTymsgr "URL Protocol"

After changing the default value I found that it made no difference, so all you need to do is to add a blank string named "URL Protocol".

This type of protocol can take parameters, which are parsed as follows:
Lets say that our program is named c:\program.exe and our protocol is program: if you use program:minimize, this is parsed asif you entered the following at the commandline:

c:>program.exe program:minimize

therefore ParamStr(1) is equal to program:minimize

Now, if you are wondering what this has to do with myprotocol:// type protocols, then I think you dont quite understand what was written above. Despite the fact that our protocol program: does not end with //, does not mean that we can not use it in the same way, after all, program: does take parameters, therefore you can actually use myprotocol:// and simply ignore that prefix.

Heres some code to add your program as a protocol:

procedure AddProtocol(Details, Protocol, Command: string);
var
  Reg: TRegistry;
begin
  Reg := TRegistry.Create;
  Reg.RootKey := HKEY_CLASSES_ROOT;
  Reg.LazyWrite := false;
  Reg.OpenKey(Protocol, true);
  Reg.WriteString('', Details);
  Reg.WriteString('URL Protocol', '');
  Reg.OpenKey('shell\open\command', true);
  Reg.WriteString('', command);
  Reg.CloseKey;
  Reg.free;
end;

example

AddProtocol('URL: DKB Protocol', 'dkb',
  '"D:\Projects\Programs\DKB\Compiled\dkb.exe" %1');

2005. szeptember 1., csütörtök

How to attach a file inside a DLL or executable


Problem/Question/Abstract:

I don't know if it is possible to attach a file to a DLL or exe. Example: You create a function (useOtherdll (bool)) in Test.dll with a parameter that tells you to use a DLL named Needed.dll. If the function parameter is 'true' then the DLL Needed.dll must be in the current directory, if the function parameter is 'false' then the DLL Needed.dll must not to be in the current directory. So if it is possible to attach in test.dll my other DLL Needed.dll, and then I can copy it if it is necessary or not.

Answer:

You can use streams to copy any data to the end of any other data - ie., copy a DLL to the end of a DLL. Example:

procedure TForm1.Button1Click(Sender: TObject);
var
  f: integer;
  fStream: TFileStream;
  mStream: TMemoryStream;
  theFiles: TStringList;
begin
  theFiles := TStringList.Create;
  try
    theFiles.Add('Needed.dll');
    theFiles.Add('TEST.dll');
    if theFiles.Count > 0 then
    begin
      mStream := TMemoryStream.Create;
      try
        for f := 0 to theFiles.Count - 1 do
        begin
          fStream := TFileStream.Create(theFiles[f], fmOpenRead);
          try
            mStream.CopyFrom(fStream, fStream.Size);
          finally
            fStream.Free;
          end;
        end;
        mStream.Seek(0, soFromBeginning);
        mStream.SaveToFile('NEW.dll');
      finally
        mStream.Free;
      end;
    end;
  finally
    theFiles.Free;
  end;
end;

You would need to mark the start of the second DLL somewhere. Then when needed, load the combined DLL into a stream. Seek to second DLL block, and copy it in a stream. Save that block steam back to the disk as the second DLL name.