2006. november 30., csütörtök

Create a TStringGrid with one column of runtime created buttons in it


Problem/Question/Abstract:

I want the following: A StringGrid with 1 column of buttons in it. The number of rows in the grid is not known at design time, so the buttons are created at runtime.

Answer:

TSpeedButton will work and you won't have to worry about the TabStop. The problem with using the Rect that comes in as a param, it doesn't hit all the cells in the column. So what you end up with is buttons displaying in the wrong cells. If it doesn't matter, then you're ok. But if it does, then you'll need to update the entire column for all the visible cells. Here's what I came up with:

{ ... }
var
  HelpButtons: array of TSpeedButton;

procedure Form1.CreateTheButtons;
var
  i: Integer;
begin
  SetLength(HelpButtons, ParamGrid.RowCount - 1);
  for i := 0 to ParamGrid.RowCount - 2 do
  begin
    HelpButtons[i] := TSpeedButton.Create(Self);
    HelpButtons[i].Visible := False;
    HelpButtons[i].Parent := ParamGrid;
    HelpButtons[i].Caption := IntToStr(i) + ' ?';
    HelpButtons[i].Width := 34;
    HelpButtons[i].Height := 18;
    HelpButtons[i].Tag := i;
    HelpButtons[i].OnClick := ParamGridButtonClick;
  end;
  {Force the buttons to show}
  ParamGrid.Refresh;
end;

procedure TForm1.ParamGridDrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);

  procedure UpdateTheColumn;
  var
    i: Integer;
    R: TRect;
  begin
    for i := ParamGrid.TopRow to (ParamGrid.VisibleRowCount + ParamGrid.TopRow) do
    begin
      if i >= ParamGrid.RowCount then
        Break;
      R := ParamGrid.CellRect(2, i);
      HelpButtons[i - 1].Top := R.Top;
      HelpButtons[i - 1].Left := R.Left;
      if not HelpButtons[i - 1].Visible then
        HelpButtons[i - 1].Visible := True;
    end;
  end;

begin
  if Length(HelpButtons) = 0 then
    Exit;
  if not FRefresh then
    Exit;
  if ((ACol = 2) and (ARow > 0)) then
  begin
    UpdateTheColumn;
  end;
end;

procedure TForm1.ParamGridButtonClick(Sender: TObject);
begin
  ShowMessage('Click ' + Sender.ClassName + ' ' + IntToStr(TControl(Sender).Tag));
end;

2006. november 29., szerda

Incremental Searches with a TListbox


Problem/Question/Abstract:

How can I create a form that has a list box that I can perform an incremental search on?

Answer:

There are a couple of ways to do this. One's hard and slow, the other easy and fast (we're going to take the easy and fast option).

For those of you who aren't familiar with incremental searching with list boxes, the concept is simple: A user types part of a string into an edit box, then the list box automatically selects one of its items that most closely matches the value typed by the user. For example of this, open up any topic search dialog in a Windows Help file. If you type into the edit box, the list will scroll to the value that most closely matches what you type.

Why is creating a capability like this essential? Because it's tedious to scroll through a list that has lots of items. Imagine if a list contained hundreds of unsorted items. To get to the value you're looking for would take a long time if you only had the capability of scrolling through the list using the vertical scroll bar. But if you knew at least part of the value you're trying to find, entering it into an edit box and getting the item you want immediately is a much more attractive solution.

Let's delve into what you have to do make this work. First, here's the unit code for a sample form I produced:

unit uinclist;

interface

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

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure Edit1Change(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
{This is a test string to load into the list box at runtime}
const
  ListStrings = 'United States'#13'Guatemala'#13'Mexico'#13 +
    'El Salvador'#13'Costa Rica'#13'Yucatan'#13 +
    'China'#13'Japan'#13'Thailand'#13'Switzerland'#13 +
    'Germany'#13'Lichtenstein'#13'Jamaica'#13'Greece' +
    'Turkey'#13'Ireland'#13'United Kingdom'#13'Scotland' +
    'Canada'#13'Uruguay'#13'Paraguay'#13'Cuba'#13 +
    'Spain'#13'Italy'#13'France'#13'Portugal'#13'New Zealand'#13 +
    'Austria'#13'Australia'#13'Philippines'#13'Korea'#13 +
    'Malaysia'#13'Tibet'#13'Nepal'#13'India'#13'Sri Lanka'#13 +
    'Pakistan'#13 + 'Saudi Arabia'#13'United Arab Emerates'#13'Iran'#13 +
    'Ukraine'#13'Belarus'#13 +
    'Chechen'#13'Yugoslavia'#13'Czechoslovakia'#13'Slovina'#13'Kazakhstan'#13 +
    'Egypt'#13'Morocco'#13'Macedonia'#13'Cyprus'#13'Finland'#13 +
    'Norway'#13'Sweden'#13'Denmark'#13'Netherlands'#13'Lithuania'#13;
begin
  ListBox1.Items.SetText(ListStrings);
end;

procedure TForm1.Edit1Change(Sender: TObject);
var
  S: array[0..255] of Char;
begin
  StrPCopy(S, Edit1.Text);
  with ListBox1 do
    ItemIndex := Perform(LB_SELECTSTRING, 0, LongInt(@S));
end;

end.

Form1 has two controls: a TEdit and a TListBox. Notice that during FormCreate, I loaded up the value of the list box with the huge string of countries. This was only for testing purposes. How you load up your list is up to you. Now, the trick to making the incremental search is in the OnChange event of Edit1. I've used the Windows message LB_SELECTSTRING to perform the string selection for me. Let's talk about the message.

LB_SELECTSTRING is one of the members of the WinAPI list box message family (all preceeded by LB_) that manipulates all aspects of a list box object in Windows. The message takes two parameters: wParam, the index from which the search should start; and lParam, the address of the null-terminated string to search on. Since WinAPI calls require null-terminated strings, use either a PChar or an Array of Char to pass string values. It's more advantageous to use a an Array of Char if you know a string value won't exceed a certain length. You don't have to manually allocate and de-allocate memory with an Array of Char, as opposed to a PChar that requires you to use GetMem or New and FreeMem to allocate and de-allocate memory.

In any case, to convert a Pascal string to a null-terminated string, just use StrPCopy to copy the contents of the Pascal string into the null-terminated string. Once that's done, all we have to do is pass the address of the null- terminated string into the wParam parameter of LB_SELECTSTRING, and that's done by using the @ symbol.

When we use Perform to execute the LB_SELECTSTRING message, the message will return the item index of the matching list item. Then all that's left to do is assign the ItemIndex property of the list box to the return value of the message. The net result is that the list box will scroll to and select the list element that was found.

There are several list box messages you can perform in Delphi. If you bring up the help system and do a topic search, enter LB_ in the edit box, and peruse the list of messages.

Delphi Expert Eddie Shipman adds the following useful information:

This procedure can be applied to TComboBox by changing to this code:

procedure TForm1.ComboBox1Change(Sender: TObject);
var
  S: array[0..255] of Char;
begin
  StrPCopy(S, TComboBox(Sender).Text);
  with ComboBox1 do
    ItemIndex := Perform(CB_SELECTSTRING, 0, LongInt(@S));
end;

2006. november 28., kedd

FoxPro limits


Problem/Question/Abstract:

FoxPro limits

Answer:

Table and Index Files
Max. # of records per table
1 billion*
Max. # of chars per record
65,000
Max. # of fields per record
255
Max. # of open DBFs
225
Max. # of chars per field
254
Max. # of chars per index key (IDX)
100
Max. # of chars per index key (CDX)
240
Max. # of open index files per table
unlimited**
Max. # of open index files in all work areas
unlimited**

* The actual file size (in bytes) cannot exceed 2 gigabytes for single-user or exclusively opened multi-user tables. Shared tables with no indexes or .IDX indexes cannot exceed 1 gigabyte. Shared tables with structural .CDX indexes cannot exceed 2 gigabytes.
** Limited by memory. In FoxPro for MS-DOS and FoxPro for Windows, also limited by available MS-DOS file handles. Each .CDX file uses only 1 file handle. The number of MS-DOS file handles is determined by the CONFIG.SYS FILES parameter.
Field Characteristics
Max. size of character fields
254
Max. size of numeric fields
20
Max. # of chars in field names
10
Digits of precision in numeric computations
16

2006. november 27., hétfő

How to create tables in Word


Problem/Question/Abstract:

Is is possible to create a table in Word via OLE Automation and to specify the value of each cell?

Answer:

Yes. If Doc is a TWordDocument, for example:

{ ... }
var
  Tbl: Table;
  R: Range;
  Direction: OleVariant;
  { ... }
Direction := wdCollapseEnd;
R := Doc.Range;
R.Collapse(Direction);
Tbl := Doc.Tables.Add(R, 2, 4, EmptyParam, EmptyParam);
Tbl.Cell(1, 1).Range.Text := 'Row 1, Col 1';
Tbl.Cell(1, 2).Range.Text := 'Row 1, Col 2';

But doing things with individual table cells in Word is extremely slow. If you can, it's better to enter the data as (for example) comma-separated values and convert it into a table only as the last step. Here's an example:

{ ... }
const
  Line1 = 'January,February,March';
  Line2 = '31,28,31';
  Line3 = '31,59,90';
var
  R: Range;
  Direction, Separator, Format: OleVariant;
{ ... }
R := Word.Selection.Range;
Direction := wdCollapseEnd;
R.Collapse(Direction);
R.InsertAfter(Line1);
R.InsertParagraphAfter;
R.InsertAfter(Line2);
R.InsertParagraphAfter;
R.InsertAfter(Line3);
R.InsertParagraphAfter;
Separator := ',';
Format := wdTableFormatGrid1;
R.ConvertToTable(Separator, EmptyParam, EmptyParam, EmptyParam, Format, EmptyParam,
  EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam,
  EmptyParam, EmptyParam);
{ ... }

2006. november 26., vasárnap

How to calculate the minimum distance between two polygons


Problem/Question/Abstract:

Do you know how to calculate the minimum distance between two polygons if they don't overlap?

Answer:

Well, this is rather complex. I start with the basics (don't know if you already know). I use UPPER letters for vectors and lower letters for numbers. Well, and sorry that I repeat some things, I wrote the text and realized I forgot something you have to know for a later step, so I went back and added it, I didn't write this text line by line. If something isn't clear to you, I recommend to make some small drawings, I had to do many, too.

A plane is defined by (X - P) * N = 0 where P is a vector to any point in your plane and N is the normal vector of your plane. Sometimes another definition is used, which is easier to gain if you have the corners of a polygon: X = P + a * A + b * B (a, b are any real numbers). If you know 3 Points X1, X2, X3 of the plane (3 corners of the polygon), you can get A and B by A = X2 - X1 (subtract each component of the vector from the same component of the other vector) and B = X3 - X1 (and you can use P = X1).

Unfortunately this definition is not good to calculate distances, so you have to get N out of A and B. A * N must be 0 and B * N must be 0 (which means a1 * n1 + a2 * n2 + a3 * n3 = 0 and b1 * n1 + b2 * n2 + b3 * n3 = 0). Sorry I cannot remember how to do this, but you have 2 equations with three unknown variables, so you can choose one of them as you want (just be careful with 0 and not 0), the only difference is that the resulting N differs in its length.

A line is defined by X = P + v * V where P is any one point of your line and V is the line's direction (like A and B of the plane). Again if you know two points X1 and X2 of your line you get V by V = X2 - X1(and you can use P = X1).

The length of a vector V = (v1, v2, v3) is length = sqrt(sqr(v1) + sqr(v2) + sqr(v3)) (just 3-dimensional Pythagoras).

You add two vectors A = (a1, a2, a3) and B = (b1, b2, b3) like that: A + B = (a1 + b1, a2 + b2, a3 + b3) (which is a vector again).

You multiply two vectors A = (a1, a2, a3) and B = (b1, b2, b3) like that: A * B = (a1 * b1 + a2 *b2 + a3 * b3) (which is a NUMBER).

Use following formula to get the distance between any one point X and a plane: dist = 1 / n * (X - P) *N where X is a vector to the point you want to examine and n is the length of N (you don't need 1/n if N has alread length of 1). If X is (x1, x2, x3) this is

dist = 1 / n * ((x1 - p1) * n1 + (x2 - p2) * n2 + (x3 - p3) * n3)
n = sqrt(sqr(n1) + sqr(n2) + sqr(n3))

Now the distance between two polygons isn't that simple, because there are many different cases (and a polygon is more than just a simple plane, even if it's size is smaller).

What you also need is to calculate the distance between two lines. At first you need a plane, that is parallel to both lines and includes one of the lines:

P(plane) = P(line1)
A(plane) = V(line1)
B(plane) = V(line2)

where A and B are two vectors in the plane (N * A = 0 and N * B = 0), V is a vector in your line (for polygons, you can take V = X2 - X1 where X2 and X1 are two corners). Now calculate the distance between this plane and any one point of line2 using the formula above (any point because ALL points have the same distance to a plane that's parallel to the line - nice trick, isn't it?

The last thing we need is not only the minimum distance between two lines, but the points of the lines, that have minimum distance. You can do this (for the point of line1 M1 with minimum distance to line2) by calculating a plane again with

P(secondplane) = P(line2)
A(secondplane) = V(line2)
B(secondplane) = N(plane) <-- the plane we calculated above

The second plane includes line2 and the point of line1 with the minimum distance to line2. To get this point of minimum distance, set P(line1) + v * V(line1) = P(secondplane) + a * A(secondplane) + b * B(secondplane). Solve this, you should get the v and when you set this v into X = P + v * V of line1 you have the point X (=M1) of minimum distance.

The bad news: To get M2, you have to repeat this for line2. Another way would be to take the distance between the lines (I call it d) and do following: M2 = M1 + d * 1 / n * N(plane) (or M2 = M1 - d * 1 / n * N(plane), depends on the direction of N). The distance between two points X1 and X2 equals the length of the vector X2 - X1.

Okay, these were the basics. Now the different cases, you have to cope with:



1.) The planes of both polygons are parallel (N1 = x * N2):

Transform both polygons the following way: X' = X - P for each corner of the polygon (where P is any point in your plane). The new polygons should now be in the same plane. Test whether both polygons overlap (is not as simple as it sounds, to be honest I don't know how to do that).



1a) They overlap:

The minimum distance is the distance of the two planes (take any one point of one plane and use the formula above to get the distance to the other plane).



1b) They do not overlap:

Use 2) to calculate the minimum distance



2) The planes are not parallel (or case 1b):

Calculate the minimum distance of one line of one polygon and one line of the other polygon. Calculate the points of minimum distance from the lines. The edges of the polygons do not have infinite length (the lines do have), so check whether the points of minimum distance are within the polygons (I'd better say: within the edges of the polygons).



2a) Both points are within the polygons:

Store the minimum distance from the lines.



2b) One point or both points are not within the polygon:

Take the corner(s) of the polygon(s) within the line(s) you checked next to the point(s) of minimum distance. Calculate the distance between these points and store it. Now repeat this for each pair of lines (if you have 2 triangles you get 9 combinations (3 times 3). When you are ready compare all the minimum distances and take the smallest one.



Okay, this is quite much to do (realtime? difficult. perhaps if you don't have many polygons) and there are several problems (a vector (x1, x2, x3) and a vector (x1, x2, 0) may have to be treated different, for example when you try to get N out of A and B). If you really need the minimum distance, try it, but perhaps you find an easier way, that is not that exact (take the distance between the center of each polygon would be least exact, but very much easier).

I want to add, that I don't know of other solutions, perhaps there are better ones, and that I don't know if everything I told you is right, I haven't tested it, everything is just theoretically.

2006. november 25., szombat

How to load a bitmap from a resource file without losing its palette


Problem/Question/Abstract:

How to load a bitmap from a resource file without losing its palette

Answer:

procedure TForm1.Button1Click(Sender: TObject);
var
  Bmp: TBitmap;
  HResInfo: THandle;
  BMF: TBitmapFileHeader;
  MemHandle: THandle;
  Stream: TMemoryStream;
  ResPtr: PByte;
  ResSize: Longint;
begin
  BMF.bfType := $4D42;
  HResInfo := FindResource(HInstance, 'BITMAP_2', RT_Bitmap);
  ResSize := SizeofResource(HInstance, HResInfo);
  MemHandle := LoadResource(HInstance, HResInfo);
  try
    ResPtr := LockResource(MemHandle);
    Stream := TMemoryStream.Create;
    try
      Stream.SetSize(ResSize + SizeOf(BMF));
      Stream.Write(BMF, SizeOf(BMF));
      Stream.Write(ResPtr^, ResSize);
      Stream.Seek(0, 0);
      Bmp := TBitmap.Create;
      try
        Bmp.LoadFromStream(Stream);
        Canvas.Draw(0, 0, Bmp);
      finally
        Bmp.Free;
      end;
    finally
      Stream.Free;
    end;
  finally
    FreeResource(MemHandle);
  end;
end;

2006. november 24., péntek

Drag a row from a TDBGrid to a TMemo


Problem/Question/Abstract:

I have a TDBGrid with several rows of data in it. Is it possible to be able to drag one of the rows off of the grid and drop it on another control?

Answer:

procedure TForm1.DBGrid1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  if DragDetect(DBGrid1.Handle, Point(x, y)) then
    DBGrid1.BeginDrag(False);
end;

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

procedure TForm1.Memo1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  i: Integer;
begin
  Memo1.Clear;
  for i := 0 to DBGrid1.Columns.Count - 1 do
    Memo1.Lines.Add(DBGrid1.Columns[i].Field.AsString);
  {or use DataSet}
end;

2006. november 23., csütörtök

How to set a system-wide font for dialogs


Problem/Question/Abstract:

I have a custom dialog. So far I have set the font to Verdana. What I would like to do is to be able to set the dialog font to the one that's currently being used.

Answer:

This code sets the font of dialogs system-wide to Verdana:

procedure TForm1.Button1Click(Sender: TObject);
var
  ncm: TNonClientMetrics;
begin
  ncm.cbSize := SizeOf(TNonClientMetrics);
  {get old non client metrics}
  SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SizeOf(TNonClientMetrics), @ncm, 0);
  {lfCaptionFont - regular captions
  lfSmCaptionFont - small captions
  lfMenuFont - menus
  lfStatusFont - status bars
  lfMessageFont - message boxes}
  ncm.lfMessageFont.lfFaceName := 'Tahoma';
  {set new non client metrics}
  SystemParametersInfo(SPI_SETNONCLIENTMETRICS, SizeOf(TNonClientMetrics), @ncm, 0);
end;

2006. november 22., szerda

How to capture the Desktop and shade it


Problem/Question/Abstract:

How to capture the Desktop and shade it

Answer:

This unit takes the Desktop and makes it look like it does when you select Shutdown from the Start menu. You need a TForm with BorderStyle set to bsNone and a TImage component set to alClient.

procedure TForm1.FormShow(Sender: TObject);

  procedure Veil;
  var
    BrushBmp: TBitmap;
    X, Y: Integer;
  begin
    BrushBmp := TBitmap.Create;
    with BrushBmp do
    begin
      Width := 8;
      Height := 8;
      for X := 0 to 7 do
        for Y := 0 to 7 do
          if Odd(X + Y) then
            Canvas.Pixels[X, Y] := clWhite
          else
            Canvas.Pixels[X, Y] := clBlack;
    end;
    Image1.Canvas.Brush.Bitmap := BrushBmp;
    {The PatBlt function paints the given rectangle using the brush that is
                currently selected into the specified device context.
                The brush color and the surface color(s) are combined by using the
    given raster operation.}
    PatBlt(Image1.Canvas.Handle, 0, 0, Image1.Width, Image1.Height, $000A0329);
    BrushBmp.Free;
  end;

var
  ScreenDC: HDC;
  tmpRect: TRect;
  tmpBitmap: TBitmap;
begin
  {Set the form bounds}
  SetBounds(0, 0, Screen.Width, Screen.Height);
  {get our screen device context}
  ScreenDC := GetDC(0);
  {create our bitmap}
  tmpBitmap := TBitmap.Create;
  {get the screen area}
  tmpRect := Rect(0, 0, Screen.Width, Screen.Height);
  {set the bitmap to the screen area}
  tmpBitmap.Width := tmpRect.Right - tmpRect.Left;
  tmpBitmap.Height := tmpRect.Bottom - tmpRect.Top;
  try
    {transfer the screen pixels to the bitmap}
    BitBlt(tmpBitmap.Canvas.Handle, tmpRect.Left, tmpRect.Top, tmpBitmap.Width,
      tmpBitmap.Height, ScreenDC, tmpRect.Left, tmpRect.Top, SRCCOPY);
    {assign the bitmap image to our TImage}
    Image1.Picture.Bitmap.Assign(tmpBitmap);
  finally
    {free our bitmap}
    tmpBitmap.Free;
    {release our screen device context}
    ReleaseDC(0, ScreenDC);
  end;
  Veil;
end;

end.

2006. november 21., kedd

Disable the close button on a floating dock form


Problem/Question/Abstract:

I want to know how to prevent an undocked control from destroying when I clicked on the X shaped close button on the right corner.

Answer:

You have to create your own docksite form:

type
  TNoCloseDockform = class(TCustomDockForm)
  private
    procedure WMSysCommand(var msg: TWMSyscommand); message WM_SYSCOMMAND;
  end;

  { TNoCloseDockform }

procedure TNoCloseDockform.WMSysCommand(var msg: TWMSyscommand);
begin
  if (msg.CmdType and $FFF0) = SC_CLOSE then
    Msg.result := 0
  else
    inherited;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  panel1.FloatingDockSiteClass := TNoCloseDockform;
end;

2006. november 20., hétfő

Obtain the actual IP address


Problem/Question/Abstract:

How to obtain the actual IP address

Answer:

Solve 1:

uses
  Winsock;

function GetLocalIPs: string;
type
  PPInAddr = ^PInAddr;
var
  wsaData: TWSAData;
  HostInfo: PHostEnt;
  HostName: array[0..255] of Char;
  Addr: PPInAddr;
begin
  Result := '';
  if WSAStartup($0102, wsaData) <> 0 then
    exit;
  try
    if gethostname(HostName, SizeOf(HostName)) <> 0 then
      exit;
    HostInfo := gethostbyname(HostName);
    if HostInfo = nil then
      exit;
    Addr := Pointer(HostInfo^.h_addr_list);
    if (Addr = nil) or (Addr^ = nil) then
      exit;
    Result := StrPas(inet_ntoa(Addr^^));
    inc(Addr);
    while Addr^ <> nil do
    begin
      Result := Result + ^M^J  + StrPas(inet_ntoa(Addr^^));
      inc(Addr);
    end;
  finally
    WSACleanup;
  end;
end;


Solve 2:

uses
  winsock;

function localIP: string;
type
  TaPInAddr = array[0..10] of PInAddr;
  PaPInAddr = ^TaPInAddr;
var
  phe: PHostEnt;
  pptr: PaPInAddr;
  Buffer: array[0..63] of char;
  I: Integer;
begin
  Result := '';
  GetHostName(Buffer, SizeOf(Buffer));
  phe := GetHostByName(buffer);
  if phe = nil then
    Exit;
  pptr := PaPInAddr(Phe^.h_addr_list);
  I := 0;
  while pptr^[I] <> nil do
  begin
    result := StrPas(inet_ntoa(pptr^[I]^));
    Inc(I);
  end;
end;


Solve 3:

You can use the following code to retrieve your local addresses:

uses
  winsock;

procedure GetAllLocalIPs(var Address: string);
type
  TaPInAddr = array[0..255] of PInAddr;
  PaPInAddr = ^TaPInAddr;
var
  pptr: PaPInAddr;
  I: Integer;
  WSAData: TWSAData;
  HostEnt: PHostEnt;
  Name: string;
begin
  Address := '';
  WSAStartup(MakeWord(1, 1), WSAData);
  SetLength(Name, 255);
  GetHostName(PChar(Name), 255);
  SetLength(Name, StrLen(PChar(Name)));
  HostEnt := GetHostByName(PChar(Name));
  with HostEnt^ do
  begin
    pptr := PaPInAddr(HostEnt^.h_addr_list);
    I := 0;
    while pptr^[I] <> nil do
    begin
      Address := Address + StrPas(inet_ntoa(pptr^[I]^)) + ';  ';
      Inc(I);
    end;
  end;
  WSACleanup;
end;

2006. november 19., vasárnap

How to send files across the internet using FTP


Problem/Question/Abstract:

If I wish to send files to an FTP server how difficult is it to do?

Answer:

After HTTP, FTP is possibly the most used protocols. It allows files to be transferred to and from FTP servers. The only disadvantage with using FTP is that the username and password are sent unencrypted in plain text. Even Internet Explorer can handle FTP.

The FTP protocol is reasonably easy to implement if you know how to use Winsock, but it has already been done by many people so this is probably one wheel you do NOT need to reinvent. The following list includes source code and they are free. ICS (Francois Piette&#8217;s superb library at (http://overbyte.delphicenter.com/frame_index.html), Winshoes or Indy as it is now known (and soon to be included in Delphi 6 I believe) at http://www.nevrona.com/Indy/ and MonsterFtp which is on www.torry.net on the Internet part of the VCL section under FTP.

Of these I tried Monster FTP but found a bug using it within a firewall, but Winshoes version 7 (8 is now being released as Indy) worked fine and the code shown below shows just how simple it is to upload files using the FTP. I haven&#8217;t tried ICS or any other kits so apologies if I overlooked any.

For any FTP account you need the following:
Username
Password
Server URL (ftp:// &#8230;) or IP Address
And optionally, a folder to change to, after the connection is established.

In the code below, ftpObject is a Winshoes TSimpleFTPObject.FtpUpload is a record or class containing Server (Ip Address or Name), Username, Password, Timeout (in milliseconds) and optionally Directory (to change into).  The file transferred is passed in as FilenametoSend.Just add your own error Procedure to deal with errors.

procedure Error(const ErrorString: string);

Depending on the type of file transferred you may wish to transfer files as binary or as Ascii. The only difference is that Ascii transferred files have Carriage Return/Line Feeds added or stripped (according to direction of flow) if between Unix systems and Windows.

Note this needs to be slotted into a procedure or Method.


try
  FtpObject.Hostname := FtpUpload.Server;
  FtpObject.Username := FtpUpload.Username;
  FtpObject.Password := FtpUpload.Password;
  Ftpobject.ConnectTimeout := Ftpupload.Timeout * 1000;

  if not FTPObject.Connect then
  begin
    Error('failed to connect to server');
    exit;
  end;

except
  on E: Exception do
  begin
    Error(Format('Failed to connect to FTP server %s', [FTPUpload.Server]));
    EXIT;
  end;
end;

{ Change Working Directory }
try
  if FtpUpload.Directory <> '' then
    FtpObject.ChangeRemoteDir(FtpUpload.Directory);
except
  on E: Exception do
  begin
    Error(Format('Failed to switch to FTP folder %s', [FtpUpload.Directory]));
    EXIT;
  end;
end;

//FTPObject.Mode(MODE_BYTE);
FTPObject.Transfertype := ttBinary;
LocalFile := CommonExportFolder + FTPUpload.FileNameToSend;
{ Includes date/time in remote file name to keep name unique on a resend }
RemoteFile := Prefix + FormatDateTime('yyyymmddhhnnss', now) + NameList[i];
try
  FTpObject.PutQualifiedFile(LocalFile, RemoteFile);
except
  Error('Failed Copying File ' + Localfile + ' To ' + Remotefile);
end;


Component Download: http://www.nevrona.com/Indy/

To download files there is an equivalent .getQualifiedFile method.

2006. november 18., szombat

How to modify the color of a TProgressBar


Problem/Question/Abstract:

How to modify the color of a TProgressBar

Answer:

If you have 4.71 or greater CommCtrl32.dll version, you can use this to set the bar color:



SendMessage(ProgressBar1.Handle, PBM_SETBARCOLOR, 0, clLime);


or


SendMessage(ProgressBar1.Handle, PBM_SETBKCOLOR, 0, clGreen);


The messages are defined in COMMCTRL.pas.

2006. november 17., péntek

How to close a non-responding application


Problem/Question/Abstract:

How to close a non-responding application

Answer:

{ ... }
const
  PROCESS_MURDER = $0001;
var
  pid: Cardinal;
  ProcessHandle: THandle;
begin
  { ... }
  GetWindowThreadProcessId(WindowHandleOfHangingApp, @pid);
  ProcessHandle := OpenProcess(PROCESS_MURDER, FALSE, pid);
  TerminateProcess(ProcessHandle, 4);
  { ... }

2006. november 16., csütörtök

TDesktopCanvas - write on the desktop


Problem/Question/Abstract:

TDesktopCanvas - write on the desktop

Answer:

This canvas allows you to access the desktop:


type
  TDesktopCanvas = class(TCanvas)
  private
    DC: hDC;
    function GetWidth: Integer;
    function GetHeight: Integer;
  public
    constructor Create;
    destructor Destroy; override;
  published
    property Width: Integer read GetWidth;
    property Height: Integer read GetHeight;
  end;

  { TDesktopCanvas object }

function TDesktopCanvas.GetWidth: Integer;
begin
  Result := GetDeviceCaps(Handle, HORZRES);
end;

function TDesktopCanvas.GetHeight: Integer;
begin
  Result := GetDeviceCaps(Handle, VERTRES);
end;

constructor TDesktopCanvas.Create;
begin
  inherited Create;
  DC := GetDC(0);
  Handle := DC;
end;

destructor TDesktopCanvas.Destroy;
begin
  Handle := 0;
  ReleaseDC(0, DC);
  inherited Destroy;
end;

2006. november 15., szerda

Fill a listbox with file names


Problem/Question/Abstract:

Easy method to fill a listbox with file names

Answer:

Today I have read, that you can fill a listbox items with file names by one message only! Try it - it's cool:

var
  s: string;
begin
  s := 'c:\windows\*.bmp'#0;
  ListBox1.Perform(LB_DIR, DDL_READWRITE, LongInt(@s[1]));
end;

Note: this code only seems to work in Windows 2000... I used this code in 2k fine, but when I tested my proggy in WinME this bit of code did nothing... Few other ppl reported the same thing :\

2006. november 14., kedd

How to set a TEdit or TMemo to overwrite instead of insert


Problem/Question/Abstract:

How to set a TEdit or TMemo to overwrite instead of insert

Answer:

Solve 1:

You have to fake it because the control does not natively support overtype mode. Provide overtype capability for edits and memos:

procedure TScratchMain.Memo1KeyPress(Sender: TObject; var Key: Char);
begin
  if (Sender is TCustomEdit) and Odd(GetKeyState(VK_INSERT)) then
    with TCustomEdit(Sender) do
      if SelLength = 0 then
        case Key of
          ' '..#126, #128..#255:
            begin
              SelLength := 1;
              if (SelLength > 0) and (SelText[1] = #13) then
                SelLength := 2;
            end;
        end;
end;

With this handler the control will start out in insert mode since the state of VK_INSERT is not toggled by default. Pressing it once will toggle the key and put the control in overtype mode. If you want it to start out in overtype, use "not Odd(...)" in the If statement.

Solve 2:

I managed to simulate it by doing this (you need to declare the FOverwrite: boolean somewhere in the form):

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
type
  TSmallPoint = packed record
    case integer of
      0: (x, y: Smallint);
      1: (long: integer);
  end;
var
  CaretPos: TPoint;
  sCaretPos: TSmallPoint;
begin
  if (FOverwrite) and (Edit1.SelLength = 0) then
  begin
    GetCaretPos(CaretPos);
    sCaretPos.x := CaretPos.x;
    sCaretPos.y := CaretPos.y;
    Edit1.SelStart := SendMessage(Edit1.Handle, EM_CHARFROMPOS, 0, sCaretPos.long);
    Edit1.SelLength := 1;
    Edit1.SelText := Key;
    Key := #0;
  end;
end;

procedure TForm1.Edit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  case Key of
    VK_INSERT: FOverwrite := not FOverwrite;
  end;
end;

2006. november 13., hétfő

How to start a search upon an [Enter] key press in a TEdit


Problem/Question/Abstract:

I have a series of 6 edit boxes that users type info in that are then passed to params in my SQL TQuery. The search query is started by clicking on a button. However, users have asked that if they type in one of the edit boxes and then press 'Enter' that the system searches. I can use Key Press event to trigger it and then if key = #13 to make sure its the enter key but then i want it to trigger the procedure that does the search, usually triggered by the tool button. Any ideas?

Answer:

Solve 1:

The best solution is to use actions, which I'll describe below. But if you don't want to use actions, do this:

Move your search procedure into a separate procedure, and then call that from both the toolbutton OnClick and edit OnKeyPress events, like this:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    ToolBar1: TToolBar;
    ToolButton1: TToolButton;
    procedure ToolButton1Click(Sender: TObject);
    procedure Edit1KeyPress(Sender: TObject; var Key: Char);
  private
    {Private Declarations}
    procedure PerformSearch;
  public
    {Public declarations}
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.ToolButton1Click(Sender: TObject);
begin
  PerformSearch;
end;

procedure TForm1.PerformSearch;
begin
  { Do search here }
  ShowMessage('Search performed');
end;

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if Key = #13 then
  begin
    PerformSearch;
    Key := #0;
  end;
end;

end.

To use actions, place a TActionList component onto your form, then create an action called something like "SearchAction". Then assign SearchAction to the ToolButton's Action property. Finally, call the action's Execute method from the edit, like this:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    ToolBar1: TToolBar;
    ToolButton1: TToolButton;
    ActionList1: TActionList;
    SearchAction: TAction;
    procedure Edit1KeyPress(Sender: TObject; var Key: Char);
    procedure SearchActionExecute(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if Key = #13 then
  begin
    SearchAction.Execute;
    Key := #0;
  end;
end;

procedure TForm1.SearchActionExecute(Sender: TObject);
begin
  { Do search here }
  ShowMessage('Search performed');
end;

end.

Solve 2:

I'll go one further than Rick. All event handlers should only delegate (unless they're one line long in which case they are delegating). In other words if you have

procedure TForm1btnSearch.Click(Sender: TObject);
begin
  {...many lines of code that actually implement the search}
end;

Change this to:

procedure TForm1btnSearch.Click(Sender: TObject);
begin
  FindInformation;
end;

procedure TForm1.FindInformation;
begin
  {...many lines of code that actually implement the search.}
end;

There are, of course, exceptions to this rule, however, for the greater part, you will not do wrong to treat an event handler as a proxy rather than placing the code directly in it. For one thing, it makes it easier to move the domain code into a separate object, so you could end up with:

procedure TForm1btnSearch.Click(Sender: TObject);
begin
  MyInformationFinder.Execute;
end;

2006. november 12., vasárnap

Cancel that wrong drag operation


Problem/Question/Abstract:

Cancel that wrong drag operation

Answer:

Have you ever started moving a component while designing your form and realized you selected the wrong component?

No doubt, you can think of other instances when you would like to cancel a design-time drag operation you've already begun. Here's a tip that let's you do just that.

After you've begun the drag but before you release the mouse button, press the Esc key. The control will snap back to its original position!

2006. november 11., szombat

Manipulate shapes and inline shapes in Word


Problem/Question/Abstract:

I'm trying to insert a picture in a document and sent it to the back, with the text over the picture using automation.

Answer:

If Doc is a Word document:

{ ... }
var
  Pic: Word2000.Shape;
  Left, Top: OleVariant;
  { ... }

{To add a pic and make it appear behind text}
Left := 100;
Top := 100;
Pic := Doc.Shapes.AddPicture('C:\Small.bmp', EmptyParam, EmptyParam, Left, Top,
  EmptyParam, EmptyParam, EmptyParam);
Pic.WrapFormat.Type_ := wdWrapNone;
Pic.ZOrder(msoSendBehindText);
{To get a watermark effect}
Pic.PictureFormat.Brightness := 0.75;
Pic.PictureFormat.Contrast := 0.20;
{To make any white in a picture transparent}
Pic.PictureFormat.TransparencyColor := clWhite;
Pic.PictureFormat.TransparentBackground := msoTrue;
Pic.Fill.Visible := msoFalse;
{ ... }

2006. november 10., péntek

How to simulate combobox behaviour with a TEdit


Problem/Question/Abstract:

I'm trying to make a component that acts in certain cases like a combobox, i.e. when the user presses a button, a list box is shown and the user can select an item. The problem I have is that I need the list to hide itself whenever the user clicks the mouse outside the list (including clicks in non-windowed controls).

Answer:

One way you can do this is by listening for CM_CANCELMODE messages in the parent of your drop-down list (presumably an edit control or something similar). You will probably then have to work out whether the message originated from a click on the drop-down list or elsewhere. In the code below, FPopup points to the list component:

procedure TMyPopupEdit.CMCancelMode(var Message: TCMCancelMode);
var
  P: TPoint;
  R: TRect;
begin
  {Get the top-left coordinate of the Sender and see if it is within the popup
  control. If not, close the popup without changing the text in the edit box.}
  P.X := Message.Sender.Left;
  P.Y := Message.Sender.Top;
  R := Rect(0, 0, FPopup.Width, FPopup.Height);
  if not PtInRect(R, P) then
    if FPopupVisible then
      PopupCloseUp(FPopup, False);
end;

2006. november 9., csütörtök

How to implement the RPos


Problem/Question/Abstract:

Sometime Pos is not enough, because you need to find the position of the first character of a sub string in a string from the end of that string. There's the solution.

Answer:

function RPos(Substr: string; S: string): Integer;
var
  i: Integer;
begin
  Result := 0;
  if ((Length(S) > 0) and (Length(Substr) > 0)) then
    if (Length(S) >= Length(Substr)) then
      for i:= (Length(S) - Length(Substr)) downto 1 do
        if (Copy(S, i, Length(Substr)) = Substr) then
        begin
          Result := i;
          Exit;
        end;
end;

2006. november 8., szerda

How to set all tables linked to a TDatabase back to active


Problem/Question/Abstract:

Is there a way to set all tables linked to a particular TDatabase component back to active automatically?

Answer:

Not totally automatic but with just two lines of code. If you want certain tables that share DB1 to keep closed (shadow tables etc, only opened for special routines) use the Tag of the DataSets to determine which ones to open.

for i := 0 to pred(DB1.DataSetCount) do
  {if DataSets.Tag = 1 then}
  DataSets[i].Open;

2006. november 7., kedd

How to form a DDE link with a Netscape browser


Problem/Question/Abstract:

How to form a DDE link with a Netscape browser

Answer:

unit Netscp1;

interface

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

type
  TForm1 = class(TForm)
    DdeClientConv1: TDdeClientConv;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    LinkStatus: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    URLName: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  LinkOpened: Integer;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
  if LinkOpened = 0 then
  begin
    DdeClientConv1.SetLink(' Netscape ', ' WWW_OpenURL ');
    if DdeClientConv1.OpenLink then
    begin
      LinkStatus.Text := ' Netscape Link has been opened ';
      LinkOpened := 1;
    end
    else
      LinkStatus.Text := ' Unable to make Netscape Link ';
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  LinkOpened := 0;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  DdeClientConv1.CloseLink;
  LinkOpened := 0;
  LinkStatus.Text := ' Netscape Link has been closed ';
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  ItemList: string;
begin
  if LinkOpened <> 0 then
  begin
    ItemList := URLName.Text + ', , 0xFFFFFFFF, 0x3, , ,';
    DdeClientConv1.RequestData(ItemList);
  end;
end;

end.

2006. november 6., hétfő

How to use Randomize so that the same value is not chosen more than once (2)


Problem/Question/Abstract:

Would you mind to make me a random procedure to change the background of my program in an interval of 15 seconds?

Answer:

The best would be to store the names in an array:

const
  CaImgs: array[0..9] of string = ('image1.jpg', 'image2.jpg', ...);

This way, on start-up, you can check that the images are there. Then, if you merely want a random image from the array, you do:

myFileName = CaImgs[random(10)];

This means that you have one chance out of ten of repeating the same image - no visible change. If you want to show always different images, but in random order, then you need a shuffle function (see above). To shuffle your array of filenames (despite being declared a constant, it's actually a var), you do this:

procedure shuffleImages;
var
  a: array[0..high(CaImgs)] of integer;
  j: integer;
  s: string;
begin
  for j := low(a) to high(a) do
    a[j] := j;
  shuffle(a, 0);
  for j := low(a) to high(a) do
  begin
    s := CaImgs[j];
    CaImgs[j] := CaImgs[a[j]];
    CaImgs[a[j]] := s;
  end;
end;

You do this once at application start. This way, the 10 images will show in random order (but the order will repeat throughout the current run).

In both cases (random of shuffle), you should call Randomize just once, at the start of the application.

2006. november 5., vasárnap

How to tell if a TPanel is moved outside the visible part of a TForm


Problem/Question/Abstract:

I use SC_DRAGMOVE so I can drag a TPanel around a form. Now, how do I tell the form when the panel is outside the form and the form should add some scrollbars?

Answer:

unit Unit1;

interface

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

type
  TPanel = class(ExtCtrls.TPanel)
  private
    procedure WMExitSizeMove(var message: TMessage); message WM_EXITSIZEMOVE;
  end;

  TForm1 = class(TForm)
    StatusBar: TStatusBar;
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    Label1: TLabel;
    ComboBox1: TComboBox;
    CheckBox1: TCheckBox;
    Panel1: TPanel;
    procedure Panel1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
const
  SC_DRAGMOVE = $F012;
begin
  Mouse.Capture := 0;
  sendmessage(panel1.handle, WM_SYSCOMMAND, SC_DRAGMOVE, 0);
end;

{ TPanel }

procedure TPanel.WMExitSizeMove(var message: TMessage);
begin
  Left := Left + 1;
  Left := Left - 1;
end;

end.

2006. november 4., szombat

One way to copy whole contents of an array into another array


Problem/Question/Abstract:

You have an array and you want to copy the value you have in it to another array.

Answer:

I just use a button to se if it compile and if I get an Error when I click it. There ar other ways, like copy an array in a for loop, but then you have to know how big it is. If you handle different arrays in an application and need to copy them into one, this is the way. As you can se, it works with just one index of the array too.

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    Array1: array[0..40] of Integer;
    Array2: array[0..100] of Integer;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Move(Array1, Array2, SizeOf(Array1)); {Moves/Copy the Array1 to Array2.}
end;

end.

2006. november 3., péntek

Trapping Messages Sent to an Application


Problem/Question/Abstract:

I wrote code for the OnMessage event handler of Application object to trap all Windows messages sent to my application, but it doesn't seem to fire on all messages. Is there a way to trap all messages sent to my application?

Answer:

There sure is. And the answer to this "problem" is amazingly simple. But before I go into trapping messages at the application level, I should probably discuss some mechanics.

TApplication's "Hidden" Window
It's not a commonly known fact that the default Application object creates a hidden window when your application is started. But you can seen evidence of this by creating a new application saving it, then running it (make sure you don't rename anything - just keep the main form as "Form1" and the project as "Project1). When you run the application, you'll notice that the caption bar for your main form says, "Form1" while the icon displayed on the task bar says "Project1." That icon represents the application's hidden window, and it affects your program in many ways, especially when you're trying to handle messages sent to your application.
Delphi surfaces the OnMessage event for the Application object. The OnMessage event handler is "supposed" to allow you trap every message sent to your application. But there's a problem with this: OnMessage will only fire when there's something in the Application object's message queue. These messages are typically window management messages such as WM_PAINT or messages sent to the application from Windows through PostMessage, Broadcast or SystemMessage . However, messages sent directly to a window using SendMessage bypass the Application object's message queue, so OnMessage doesn't fire for those types of situations.
Some of you more familiar with handling windows messages might think that a solution to the problem above might be to override the WndProc method for the Application object. Unfortunately, that's not possible because TApplication's WndProc method is not only private, it's also declared as a static method which means it's not overrideable. So it's not only invisible, you can't create a TApplication subclass to override WndProc (not that you'd want either). But that doesn't mean that you can't get to the WndProc method using alternative means.

"Hooking" All Messages
Even though WndProc is all but closed to direct subclassing, TApplication does include a method called HookMainWindow that allows you to insert your own message handler at the top of WndProc to intercept messages sent to your application before they're handled by the Application object. This is convenient for all developers, and solves the problem of trapping any message sent to your application.
HookMainWindow is declared under TApplication as follows:

procedure HookMainWindow(Hook: TWindowHook);

Notice that HookMainWindow takes one parameter, Hook of type TWindowHook. TWindowHook is a method pointer type that's defined like so:

type
  TWindowHook = function(var Message: TMessage): Boolean of object;

Since TWindowHook is a method pointer, you can define your own method as the hook function as long as it follows the nomenclature defined for TWindowHook. Notice that the return value of the function is of type Boolean. This is the equivalent of the "Handled" parameter of OnMessage. If your function handles a particular message, you'd return true. This will be passed back to the Application's WndProc and message processing for that message will be terminated. Otherwise, you'd return False. Here's an example method:

function TForm1.AppHookFunc(var Message: TMessage): Boolean;
begin
  Result := False; //I just do this by default
  if Message.Msg = WM_ < SomethingOrOther > then
  begin
    ...DoSomething...
      Result := True;
  end;
end;

Okay, now that we've set up everything, we need to make the application hook the messages. This can be done in the main form's OnCreate method:

function TForm1.FormCreate(Sender: TObject);
begin
  HookMainWindow(AppHookFunc);
end;

I should mention that you need to clear the hook using, you guessed it, UnHookMainWindow, after you're done using it, and this can be done in the OnDestroy for the main form:

function TForm1.FormDestroy(Sender: TObject);
begin
  UnHookMainWindow(AppHookFunc);
end;

Okay, disgustingly simple. But I feel the best things in life are those that give maximum satisfaction for the least amount of cost (please don't read ANYTHING into that <G>). So, now you've got the tools to create your own message "hooker" (sorry, had to do that at least once). Until next time...

2006. november 2., csütörtök

How to convert decimal numbers to fractions


Problem/Question/Abstract:

I'm looking for a function that I can pass in a decimal and return a fraction.

Answer:

Solve 1:

The "Denominators" parameter is an array of potential denominators that would be acceptable. For example, to get a fractional inch dimension with a power of 2 denominator, you'd pass [2, 4, 8, 16, 32] for that parameter, and the function will figure out which potential denominator will work best.

function ConvertFloatToFraction(const Value: Double;
  const Denominators: array of Integer): string;
var
  Index: Integer;
  TempDelta: Double;
  MinDelta: Double;
  TempNumerator: Integer;
  FracValue: Double;
  Numerator: Integer;
  Denominator: Integer;
  IntValue: Integer;
begin
  IntValue := Trunc(Value);
  FracValue := Abs(Frac(Value));
  MinDelta := 0;
  Numerator := 0;
  Denominator := 0;
  for Index := 0 to High(Denominators) do
  begin
    TempNumerator := Round(FracValue * Denominators[Index]);
    TempDelta := Abs(FracValue - (TempNumerator / Denominators[Index]));
    if ((Index = 0) or (TempDelta < MinDelta)) then
    begin
      MinDelta := TempDelta;
      Numerator := TempNumerator;
      Denominator := Denominators[Index];
    end;
  end;
  if (Numerator = Denominator) then
  begin
    IntValue := IntValue + Sign(IntValue);
    Numerator := 0;
  end;
  Result := '';
  if ((IntValue <> 0) or (Numerator = 0)) then
    Result := IntToStr(IntValue);
  if ((IntValue <> 0) and (Numerator <> 0)) then
    Result := Result + ' ';
  if (Numerator <> 0) then
    Result := Result + IntToStr(Numerator) + '/' + IntToStr(Denominator);
end;


Solve 2:

This function takes the number to convert, the fraction scale you want returned such as 8 for eighths or 10 for tenths, etc. and a boolean to tell it to round up or down the nearest fraction. It returns a string with the integer portion, a space and then the fraction portion. It will also reduce the fraction to the smallest common denominator. You can use the ErrorFactor variable to adjust the percentage of when to consider a number close enough to the next level to be close enough. I use 4 percent of the fractional scale value.

function ToFraction(num: double; scale: integer; RoundUp: boolean): string;

{Function to find greatest common denominator}
  function GCD(A, B: integer): integer;
  begin
    if (B mod A) = 0 then
      result := A
    else if (B mod A) = 1 then
      result := 1
    else
      result := GCD((B mod A), A);
  end;

var
  x, y: integer;
  ScaleFrac,
    NumFrac,
    ErrorFactor: double;
begin
  ScaleFrac := 1 / scale;
  NumFrac := Frac(Num);
  ErrorFactor := ScaleFrac * 0.04; {error factor of 4 percent}
  x := 0;
  while (((x + 1) * ScaleFrac) < (NumFrac + ErrorFactor)) do
    inc(x);
  if RoundUp then
    if (((((x + 1) * ScaleFrac) - NumFrac) / 2) > (ScaleFrac / 2)) then
      inc(x);
  if (x = 0) then {no fraction, just the integer portion}
  begin
    result := IntToStr(Trunc(Num))
  end
  else
  begin {reduce the fraction as much as possible}
    y := GCD(x, scale);
    while (y <> 1) do
    begin
      x := x div y;
      scale := scale div y;
      y := GCD(x, scale);
    end;
    result := IntToStr(Trunc(Num)) + ' ' + IntToStr(x) + '/' + IntToStr(scale);
  end;
end;

2006. november 1., szerda

Create 32bit string resources with 16bit Resource Workshop


Problem/Question/Abstract:

Create 32bit string resources with 16bit Resource Workshop

Answer:

There is an easy way to do this (besides buying Resource Workshop version 4 :-)

create your strings as 16bit resource with Resource Workshop
store them as a *.RC text file (instead of *.RES)
compile the text file with BRCW32 (command line tool: BRC32) into a 32bit *.RES file