2005. október 31., hétfő

Load and save a TStringGrid from/ to a stream


Problem/Question/Abstract:

I have a form with 4 StringGrids which I fill at run time with data. It takes some time to enter the data and so I thought it would save me some time if I could save and load the form with its data - something I've never done in Delphi before. I've sorted the menu and the dialogue boxes but what method do I have to write to save the form, its StringGrids and the data therein - simply?

Answer:

You will have noticed that the StringGrid is a control that does not allow you to enter strings into it at design-time. The reason is that the streaming system cannot handle array properties like Cells, so the standard component streaming is no use for your task. But you can write your own routines to save a StringGrids content, of course. Something like this for example:

procedure SaveGridToStream(aStream: TStream; aGrid: TStringGrid);
var
  i, k: Integer;
  iBuf: Integer;
  S: string;

  procedure WrInt(anInt: Integer);
  begin
    aStream.WriteBuffer(anInt, Sizeof(anInt));
  end;

begin
  with aGrid do
  begin
    WrInt(ColCount);
    WrInt(rowCount);
    for i := 0 to rowCount - 1 do
      for k := 0 to colCount - 1 do
      begin
        S := Cells[k, i];
        WrInt(Length(S));
        if Length(S) > 0 then
          aStream.WriteBuffer(S[1], Length(S));
      end;
  end;
end;

procedure LoadGridFromStream(aStream: TStream; aGrid: TStringGrid);
var
  i, k: Integer;
  iBuf: Integer;
  S: string;

  function RdInt: Integer;
  begin
    aStream.ReadBuffer(Result, Sizeof(Result));
  end;

begin
  with aGrid do
  begin
    ColCount := RdInt;
    RowCount := RdInt;
    for i := 0 to rowCount - 1 do
      for k := 0 to colCount - 1 do
      begin
        iBuf := RdInt;
        if iBuf > 0 then
        begin
          SetLength(S, iBuf);
          aStream.ReadBuffer(S[1], iBuf);
          Cells[k, i] := S;
        end;
      end;
  end;
end;

2005. október 30., vasárnap

Creating visual objects at run-time


Problem/Question/Abstract:

Sometimes it's necessary to create a visual object, like a button or a label, at run time. Here I'll show how to do it with a simple example.

Answer:

To explain how to create a visual object at run-time, there's some simple notions that is necessary to understood. I'll try to explain it in a simple and fast way (so don't bother with any "less accurate" information). Simplifying things, objects have two things: properties and methods. A visual object is not different, it has properties, methods and can act on event handling.

A visual object's life cycle is just like any other object life cycle, it must be created, will "live" and at the end will die. Follwoing this three basic life cycle steps:

When creating a visual object, it's necessary to say who will be the parent, who will have it has a child. Example: when deploying a TPanel on a TForm, the TForm is the TPanels' parent, when deploying a TLabel on the TPanel, the TPanel is the TLabels' parent.
To give the object some "life", it's necessary to code the answer to an event. Example: the OnKeyUp event on a TForm tells the TForm what to do when the user releases a key.
When the object is no longer needed, it should be removed. Example: when you have a TLabel that you no long need, you delete it.

So, let's take a simple case to exemplefy this. Let's create a TButton and give it some life.

Start a new project, name your main form as "Form1" and specify these private variables:

MyButton: TButton;
IsAlive: Boolean;

Deploy a TButton on Form1, caption it as "Manage Button" and on the OnClick event write the following code:

// create the button
MyButton := TButton.Create(Form1);
with MyButton do
begin
  // setting the buttons' parent
  Parent := Form1;
  // setting the alignment and size
  Top := 50;
  Left := 10;
  Width := 100;
  // showing it
  Caption := "&It Works!";
  Visible := True;
end;

This creates the button when the "Manage Button" is pushed. Run the application and click the "Manage Button".

This is not really usefull util the button created ar run-time actually do anything, so let's give it life. Let's build a procedure able to respond to a OnClick event. On your Form1 private declaration write:

procedure MyButtonOnClickEvent(Sender: TObject);

and write the following code for it:

procedure TForm1.MyButtonOnClickEvent(Sender: TObject);
begin
  ShowMessage("Well, it really works!");
end;

How do we know this will work? Well, if you check the TButton help, you will see that the OnClick event has the same signature as the MyButtonOnClickEvent that was just coded. Whenever you need to build an event, check the documentation on that object's event and make a procedure with the same signature.

Now let's make the MyButtonOnClickEvent the OnClick event of the button, just add

OnClick := MyButtonOnClickEvent;

to the code, it should look like this now:

// create the button
MyButton := TButton.Create(Form1);
with MyButton do
begin
  // setting the buttons' parent
  Parent := Form1;
  // setting the alignment and size
  Top := 10;
  Left := 10;
  Width := 100;
  // making it respond to the OnClick event
  onClick := MyButtonOnClickEvent;
  // showing it
  Caption := "&It Works!";
  Visible := True;
end;

Now run it, click the "Manage Button" and then click the "It Works!" button! ;)

Now that the button has some "life", all it needs is to "die" to have a full life cycle. So, let's kill it by calling the standard destructor. Let's change the "Manage Button" code to create and destroy the MyButton button:

if IsAlive = False then
begin
  // create the button
  MyButton := TButton.Create(Form1);
  with MyButton do
  begin
    // setting the buttons' parent
    Parent := Form1;
    // setting the alignment and size
    Top := 10;
    Left := 10;
    Width := 100;
    // making it respond to the OnClick event
    onClick := MyButtonOnClickEvent;
    // showing it
    Caption := "&It Works!";
    Visible := True;
  end;
  IsAlive := True;
end
else
begin
  MyButton.Free; // kills the button
  IsAlive := False;
end;

The IsAlive flag will tell us if the button is alive or not. To be positive that this has no faults, the IsAlive variable should be initialized, let's fo it on Form1's OnShow event:

IsAlive := False;

Now run the application. Click the "Manage Button", click the "It Works!" button, click the "Manage Button" again.
Simple, isn't it ? :)

2005. október 29., szombat

Database "index out of date" error


Problem/Question/Abstract:

I was testing a program I wrote and I received an error message saying, "Index is out of date." I have no idea what this error means. I have looked everywhere in my Delphi books for some help on this problem, and can't seem to find any information about it. Can you help me?

Answer:

This is a BDE/Paradox error message. For newbies, BDE error messages are daunting, cryptic messages. Actually, even for seasoned veterans, they can sometimes be real "stumpers." Unfortunately, there's no real good reference available that I know of, so all I can offer with respect to this error message is my experience.

The "Index out of date" message can mean a couple of things:

One of the more common causes of this error is one in which you have a couple of copies of a table existing on your network or machine. For instance, when I develop applications, I have my application tables residing in my development system, then have copies of them on my network. When I need to update my tables, I usually do the updates in my development system, then copy them over to my deployment system on the network. I've run into this exact error when I've copied only the table (.DB) file and not its accompanying index file(s) (.PX, .X01, .Y01, etc) as well. You see, when you update a table by changing it in any way, its index files are also resynched to reflect the changes. So if you copy just the table to a new place on your system and don't include its family members, you'll index files that aren't in synch with your table. Okay that's one cause.
The next cause could be just this: One of your indexes is corrupt. This could be due to sector errors on your hard disk, or the rare, but possible, direct corruption of an index. This usually happens if your program abended while performing an update to a table with an index of some sort. In that case, the index doesn't get updated.

But in any case, the only way I know of to correct the problem is to do the following:

Open up your table in Database Desktop.
Restructure it.
Define/Rebuild all your indexes.
Save the file.

2005. október 28., péntek

How to filter on substrings within a string field's value


Problem/Question/Abstract:

How to filter on substrings within a string field's value

Answer:

In the TTable OnFilter event:

var
  sp, ss: ShortString
begin
  with DataSet do
  begin
    sp := FilterPosition.Text; {TEdit containing substring to be filtered}
    ss := AnsiUpperCase(FieldByName('MyField').asString);
    Accept := Pos(sp, ss) > 0;
  end;
end;

or with an SQL script:

SELECT * FROM MyTable
WHERE UPPER(MyField)LIKE "%UPPER(MySubstring)%";

2005. október 27., csütörtök

How to check if a MDI child has been created or destroyed


Problem/Question/Abstract:

I have to write a kind of task list of all MDI child windows which are opened in the application. For this reason I tried to use the WM_PARENTNOTIFY to get an event, if a MDI child is created or destroyed. But I don't receive a message when the child windows are created or destroyed.

Answer:

Try the following:


const
  WM_AddWin = WM_User + 300;
  WM_DelWin = WM_User + 301;

type
  TForm1
    { ... }
  private

  procedure OnAddWindow(var msg: TMessage); message WM_AddWin;
  procedure OnDelWindow(var msg: TMessage); message WM_DelWin;
    public
end;

procedure TForm1.OnAddWindow(var msg: TMessage);
begin
  List.Add(Strpas(Pointer(msg.lparam)));
end;

procedure TForm1.OnDelWindow(var msg: TMessage);
begin
  List.delete(List.indexof(Strpas(Pointer(msg.lparam))));
end;

procedure OnChildFormCreate(Sender: TObject);
begin
  PostMessage(TForm(Owner).handle, WM_AddWin, 0, Integer(PChar('ChildFormname')));
end;

procedure OnChildFormDestroy;
begin
  PostMessage(TForm(Owner).handle, WM_DelWin, 0, Integer(PChar('ChildFormname')));
end;


This will post a message to the owner of a child form to add it to the master list and delete it
when it dies.

2005. október 26., szerda

How to change file attributes


Problem/Question/Abstract:

How do I change the attributes of a file like write protection, hidden etc. ?

Answer:

var
  F_Attr: Word;

F_Attr := FileGetAttr('MyFile');

{Turn ReadOnly OFF}
FileSetAttr('MyFile', F_Attr and not faReadOnly);

2005. október 25., kedd

How to implement autocompletion in a TEdit


Problem/Question/Abstract:

How to implement autocompletion in a TEdit

Answer:

Solve 1:

Here is a procedure using the OnKeyDown that will autocomplete an edit box using a lookup source table. Change it to suit your needs but it should give you an idea of how to do the selections and stuff with an edit control. This will work with just about any type of edit control and I use it for combo boxes as well. You just need to change the typecasting.

procedure TForm1.EditKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
var
  s1: string;
  s2: string;
begin
  if TEdit(Sender).Text = '' then
    exit;
  s1 := TEdit(Sender).Text;
  s2 := s1;
  with mtDM.LookTable do {change here for your own lookup stuff...}
  begin
    if not Locate(LookField, TEdit(Sender).Text, [loPartialKey]) then
    begin
      Key := 0;
      if length(s2) = 1 then
      begin
        TEdit(Sender).Text := '';
        exit;
      end;
      System.delete(s2, length(s2), 1);
      TEdit(Sender).Text := s2;
      s1 := s2;
      Locate(LookField, TEdit(Sender).Text, [loPartialKey]);
    end;
    s1 := FieldByName(LookField).AsString;
    TEdit(Sender).Text := copy(s1, 1, length(s2)) + copy(s1, length(s2) + 1, length(s1));
    TEdit(Sender).SelStart := Length(s2);
    TEdit(Sender).SelLength := length(s1) - length(s2);
  end;
  inherited;
end;

Solve 2:

unit AutoEdit;

interface

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

type
  TAutoEdit = class(TEdit)
  private
    fList: TListBox;
    fItems: TStringList;
    fLabel: TLabel;
    fCaption: string;
    fBackColor: TColor;
    fCaptionColor: TColor;
    fAutoComplete: Boolean;
    fListCount: Integer;
    fOldText: string;
    procedure SetCaption(S: string);
    procedure SetCaptionColor(const Color: TColor);
    procedure SetBackColor(const Color: TColor);
    procedure SetAutoComplete(AutoCompleteOn: Boolean);
    procedure ShowList;
  protected
    procedure CreateWnd; override;
    procedure CreateParams(var params: TCreateParams); override;
    procedure SetParent(AParent: TWinControl); override;
    procedure SetName(const Value: TComponentName); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
    procedure ListMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure HideList;
    procedure DoExit; override;
    property Items: TStringList read fItems write fItems;
  published
    procedure KeyPress(var Key: Char); override;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    property Caption: string read fCaption write SetCaption;
    property CaptionColor: TColor read fCaptionColor write SetCaptionColor;
    property BackColor: TColor read fBackColor write SetBackColor;
    property AutoComplete: Boolean read fAutoComplete write SetAutoComplete;
    property ListCount: Integer read fListCount write fListCount default 5;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Freeware', [TAutoEdit]);
end;

{ TAutoEdit }

constructor TAutoEdit.Create(AOwner: TComponent);
begin
  inherited;
  fItems := TStringList.Create;
  fList := TListBox.Create(Self);
  fLabel := TLabel.Create(Self);
  fLabel.ParentColor := True;
  fLabel.AutoSize := False;
  fLabel.FocusControl := Self;
  fCaptionColor := fLabel.Font.Color;
  fBackColor := fLabel.Color;
  fList.Parent := Self;
  fList.IntegralHeight := True;
  fList.ParentCtl3D := False;
  fList.Ctl3D := False;
  fList.TabStop := False;
  fList.Visible := False;
  fListCount := 5;
end;

destructor TAutoEdit.Destroy;
begin
  {fList.Free;}
  fItems.Free;
  fLabel.Free;
  inherited;
end;

procedure TAutoEdit.SetParent(AParent: TWinControl);
var
  FirstSetting: Boolean;
begin
  if Parent = nil then
    FirstSetting := True
  else
    FirstSetting := False;
  inherited;
  if Parent <> nil then
  begin
    fList.Parent := Self.Parent;
    fLabel.Parent := Self.Parent;
    if FirstSetting then
    begin
      fLabel.ParentColor := True;
      SetBounds(Left, Top, Width, Height);
    end;
  end;
end;

procedure TAutoEdit.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
  inherited SetBounds(ALeft, ATop, AWidth, AHeight);
  if Parent <> nil then
  begin
    if (fCaption > '') and (fLabel.Parent <> nil) then
    begin
      fLabel.Top := ATop - (1 + fLabel.Canvas.TextHeight('lj'));
      fLabel.Height := AHeight + 4 + fLabel.Canvas.TextHeight('lj');
    end
    else
    begin
      fLabel.Top := ATop - 2;
      fLabel.Height := AHeight + 4;
    end;
    fLabel.Left := ALeft - 2;
    fLabel.Width := AWidth + 4;
    if csDesigning in ComponentState then
    begin
      fList.Parent := Self;
      HideList;
    end
    else if fList.Visible then
      ShowList;
  end;
end;

procedure TAutoEdit.SetName(const Value: TComponentName);
begin
  if Name > '' then
    if fCaption = Name then
      Caption := Value;
  inherited SetName(Value);
  if Text = Name then
  begin
    Text := '';
    Caption := Value;
  end;
end;

procedure TAutoEdit.CreateWnd;
begin
  inherited;
end;

procedure TAutoEdit.CreateParams(var params: TCreateParams);
begin
  inherited;
  fList.Color := Self.Color;
  fList.Font := Self.Font;
  fList.OnMouseUp := ListMouseUp;
  HideList;
end;

procedure TAutoEdit.SetCaption(S: string);
begin
  fCaption := S;
  fLabel.Caption := ' ' + S;
  SetBounds(Left, Top, Width, Height)
end;

procedure TAutoEdit.SetCaptionColor(const Color: TColor);
begin
  if fCaptionColor <> Color then
  begin
    fCaptionColor := Color;
    fLabel.Font.Color := Color;
    SetBounds(Left, Top, Width, Height)
  end;
end;

procedure TAutoEdit.SetBackColor(const Color: TColor);
begin
  if fBackColor <> Color then
  begin
    fBackColor := Color;
    fLabel.Color := Color;
    SetBounds(Left, Top, Width, Height)
  end;
end;

procedure TAutoEdit.SetAutoComplete(AutoCompleteOn: Boolean);
begin
  fAutoComplete := AutoCompleteOn;
end;

procedure TAutoEdit.ListMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  Text := fList.Items[fList.ItemIndex];
  SelStart := Length(Text);
  HideList;
  fList.Clear;
  PostMessage(Handle, WM_KEYDOWN, VK_TAB, 0);
  PostMessage(Handle, WM_KEYUP, VK_TAB, 0);
end;

procedure TAutoEdit.DoExit;
begin
  if not fList.Focused then
    HideList;
  inherited;
end;

procedure TAutoEdit.KeyPress(var Key: Char);
var
  K, T: string;
  I, S: Integer;
begin
  if ReadOnly then
  begin
    inherited;
    Exit;
  end;
  K := Key;
  if (Key = #27) and (fList.Visible) then
  begin
    Key := #0;
    Text := Copy(Text, 1, SelStart);
    SelStart := Length(Text);
    fList.Clear;
    HideList;
  end
  else if fAutoComplete then
    if ((K > #27) and (K < #129)) or (K = #8) then
    begin
      if (K = #8) then
        T := Copy(Text, 1, SelStart - 1)
      else
        T := Copy(Text, 1, SelStart) + K;
      K := Uppercase(T);
      fList.Clear;
      if fItems.Count > 0 then
        for I := 0 to fItems.Count - 1 do
        begin
          if (Pos(K, Uppercase(fItems[I])) = 1) then
            fList.Items.Add(fItems[I]);
          if fList.Items.Count > fListCount - 1 then
            Break;
        end;
      S := Length(T);
      if (fList.Items.Count > 0) and (Key <> #8) then
      begin
        Text := Copy(T, 1, S) + Copy(fList.Items[0], S + 1, Length(fList.Items[0]));
      end
      else
        Text := T;
      Key := #0;
      SelStart := S;
      SelLength := Length(Text) - S;
      fOldText := Copy(Text, 1, SelStart);
    end;
  if fList.Items.Count > 0 then
    ShowList
  else
    HideList;
  inherited;
end;

procedure TAutoEdit.KeyDown(var Key: Word; Shift: TShiftState);
var
  I, S: Integer;
begin
  if Key = VK_DELETE then
  begin
    fList.Clear;
    HideList;
  end
  else if fList.Visible then
    if (Key = VK_DOWN) or (Key = VK_UP) then
    begin
      S := SelStart;
      if Key = VK_DOWN then
        I := fList.ItemIndex + 1
      else
        I := fList.ItemIndex - 1;
      if I < -1 then
        I := fList.Items.Count - 1;
      if I > fList.Items.Count - 1 then
        I := -1;
      fList.ItemIndex := I;
      if I = -1 then
      begin
        Text := fOldText;
        SelStart := Length(Text);
        SelLength := 0;
      end
      else
      begin
        Text := fList.Items[fList.ItemIndex];
        SelStart := S;
        SelLength := Length(Text) - S;
      end;
      Key := 0;
    end;
  if (not fList.Visible) and ((Key = VK_LEFT) or (Key = VK_RIGHT)) then
    if SelLength = Length(Text) then
      if (Shift = []) and (Length(Text) > 0) then
      begin
        SelLength := 0;
        {if (Key = VK_LEFT) and (SelStart > 1) then
          SelStart := SelStart - 1
        else
          if (Key = VK_RIGHT) and (SelStart < Length(Text)) then
            SelStart := SelStart + 1;}
        Key := 0;
      end;
  inherited;
end;

procedure TAutoEdit.ShowList;
begin
  if Parent <> nil then
  begin
    fList.Top := Top + ClientHeight;
    fList.Left := Left;
    fList.Width := Width;
    fList.Height := fList.ItemHeight * (fList.Items.Count + 1);
    fList.BringToFront;
    fList.Show;
  end;
end;

procedure TAutoEdit.HideList;
var
  I: Integer;
begin
  if (Text > '') then
    for I := 0 to fList.Items.Count - 1 do
      if Uppercase(fList.Items[I]) = Uppercase(Text) then
      begin
        Text := fList.Items[I];
        Break;
      end;
  fList.Hide;
  fList.Top := Top;
  fList.Height := 0;
  fList.Left := Left;
  fList.Width := 0;
end;

initialization
  begin
    RegisterClass(TLabel);
  end;

end.

2005. október 24., hétfő

How to make a palette and a pf1bit bitmap not necessary in B/W


Problem/Question/Abstract:

How to make a palette and don't bother about Range Checking ON or OFF ({$R+/-}?
How to convert a bitmap to a pf1bit bitmap, but not necessary in B/W?

Answer:

// This method will give an error if Range Checking is on
// ======================================================

var
  LogPal: PLogPalette;
  Palette: HPalette;
  PalSize: LongInt;

begin
  { ... }
  PalSize := 2 * SizeOf(Word) + n_Colors * SizeOf(TPaletteEntry));
{2 * SizeOf(Word) to get space for palVersion and palNumEntries,  n_Colors is the number
of colors in the palette}
GetMem(LogPal, PalSize);
LogPal^.palVersion := $0300;
LogPal^.palNumEntries := n_Colors;
LogPal^.palPalEntry[0] := {Some colour};
LogPal^.palPalEntry[1] := {Some other colour};
{ etc. }
FreeMem(LogPal, PalSize);
{ ... }
end;

// This method will NOT give a Range Check Error!
// =============================================

var
  pal: TMaxLogPalette;
  hpal: HPalette;
  DummyImage: TImage;

begin
  pal.palVersion := $300; // Magic number
  pal.palNumEntries := 2; // Palette for 1bit images not black and white!

  // Set foreground color
  pal.palPalEntry[0].peRed := GetRValue(Color1);
  pal.palPalEntry[0].peGreen := GetGValue(Color1);
  pal.palPalEntry[0].peBlue := GetBValue(Color1);
  pal.palPalEntry[0].peFlags := 0;

  // Set backGroundColor
  pal.palPalEntry[1].peRed := GetRValue(Color2);
  pal.palPalEntry[1].peGreen := GetGValue(Color2);
  pal.palPalEntry[1].peBlue := GetBValue(Color2);
  pal.palPalEntry[1].peFlags := 0;

  // Create the palette
  hpal := CreatePalette(PLogPalette(@pal)^);

  // Create a new image
  DummyImage := TImage.Create(Self);
  DummyImage.Picture.Bitmap.Width := Image1.Picture.Bitmap.Width;
  DummyImage.Picture.Bitmap.Height := Image1.Picture.Bitmap.Height;
  DummyImage.Picture.Bitmap.PixelFormat := pf1bit;
  DummyImage.Picture.Bitmap.Palette := hpal; // Assign the palette
  DummyImage.Picture.Bitmap.Canvas.Draw(0, 0, Image1.Picture.Graphic); // Draw it
  {...}
  {...}
end;

// DummyImage now holds the pf1bit represenation of Image1.
// Yes! A pf1bit image has a palette and it doesn't have to be black and white either!

2005. október 23., vasárnap

How to handle bitmap layers


Problem/Question/Abstract:

I want to overlay bitmap2 over bitmap. The bitmap2 should be on top of bitmap1, i.e. the end result should be that bitmap2 should also show bits of bitmap1. What is the best way in Delphi to handle this?

Answer:

There are two methods. One works using a TImageList the other without. So the latter will work with other file types besides BMP's. Here's the code for both, it's pretty clear how both work. The key thing is you must specify a transparent color:

procedure DrawTrans(DestCanvas: TCanvas; X, Y: smallint; SrcBitmap: TBitmap; AColor: TColor);
var
  ANDBitmap, ORBitmap: TBitmap;
  CM: TCopyMode;
  Src: TRect;
begin
  ANDBitmap := nil;
  ORBitmap := nil;
  try
    ANDBitmap := TBitmap.Create;
    ORBitmap := TBitmap.Create;
    Src := Bounds(0, 0, SrcBitmap.Width, SrcBitmap.Height);
    with ORBitmap do
    begin
      Width := SrcBitmap.Width;
      Height := SrcBitmap.Height;
      Canvas.Brush.Color := clBlack;
      Canvas.CopyMode := cmSrcCopy;
      Canvas.BrushCopy(Src, SrcBitmap, Src, AColor);
    end;
    with ANDBitmap do
    begin
      Width := SrcBitmap.Width;
      Height := SrcBitmap.Height;
      Canvas.Brush.Color := clWhite;
      Canvas.CopyMode := cmSrcInvert;
      Canvas.BrushCopy(Src, SrcBitmap, Src, AColor);
    end;
    with DestCanvas do
    begin
      CM := CopyMode;
      CopyMode := cmSrcAnd;
      Draw(X, Y, ANDBitmap);
      CopyMode := cmSrcPaint;
      Draw(X, Y, ORBitmap);
      CopyMode := CM;
    end;
  finally
    ANDBitmap.Free;
    ORBitmap.Free;
  end;
end;

The other way:

abitmap := TBitmap.Create;
imagelist1.Clear;
abitmap.LoadFromFile('1.bmp');
imagelist1.AddMasked(abitmap, clNone);
abitmap.Empty;
abitmap.LoadFromFile('e1.bmp');
imagelist1.AddMasked(abitmap, clWhite);
abitmap.Empty;
abitmap.LoadFromFile('h1.bmp');
imagelist1.AddMasked(abitmap, clWhite);
imagelist1.Draw(image1.Canvas, 0, 0, 0);
imagelist1.Draw(image1.Canvas, 0, 0, 1);
imagelist1.Draw(image1.Canvas, 0, 0, 2);
abitmap.free;

2005. október 22., szombat

Retrieve all table names from an Interbase database


Problem/Question/Abstract:

How to retrieve all table names from an Interbase database

Answer:

unit InterbaseDbTables;

interface

uses
  IbDatabase, IbCustomDataSet, SysUtils;

type
  TTableType = (ttTable, ttView, ttSystemTable);

type
  TTableTypes = set of TTableType;

type
  TTableItem = record
    ItemName: string;
    ItemType: string;
  end;

type
  TTableItems = array of TTableItem;

function addFilter(string1, string2: string): string;
function IbDbTables(IbDatabase: TIbDatabase; types: TTableTypes): TTableItems;

implementation

function addFilter(string1, string2: string): string;
begin
  if string1 <> '' then
    Result := string1 + ' or ' + string2
  else
    Result := string2;
end;

function IbDbTables(IbDatabase: TIbDatabase; types: TTableTypes): TTableItems;
var
  IbDataSet: TIbDataSet;
  IbTransaction: TIbTransaction;
  i: integer;
  Filtro: string;
begin
  IbDataSet := TIbDataSet.Create(nil);
  IbTransaction := TIbTransaction.Create(nil);
  IbTransaction.DefaultDatabase := IbDatabase;
  IbDataSet.Transaction := IbTransaction;
  IbDataSet.SelectSQL.Text := 'SELECT RDB$RELATION_NAME, RDB$SYSTEM_FLAG,
    RDB$VIEW_SOURCE FROM RDB$RELATIONS';
    if (ttTable in types) then
    Filtro := addFilter(Filtro, '((RDB$VIEW_SOURCE IS NULL) and
      ((RDB$SYSTEM_FLAG = 0) or (RDB$SYSTEM_FLAG is NULL)))');
      if (ttView in types) then
      Filtro := addFilter(Filtro, '(RDB$VIEW_SOURCE IS NOT NULL)');
  if (ttSystemTable in types) then
    Filtro := addFilter(Filtro,
      '((RDB$SYSTEM_FLAG <> 0) and (RDB$SYSTEM_FLAG IS NOT NULL))');
  if Filtro <> '' then
    IbDataSet.SelectSQL.Text := IbDataSet.SelectSQL.Text + ' where ' + Filtro;
  IbDataSet.Open;
  IbDataSet.Last;
  SetLength(Result, IbDataSet.RecordCount);
  i := 0;
  with IbDataSet do
  begin
    First;
    while not Eof do
    begin
      with Result[i] do
      begin
        ItemName := Trim(FieldByName('RDB$RELATION_NAME').AsString);
        if (not FieldByName('RDB$VIEW_SOURCE').IsNull) then
          ItemType := 'VIEW'
        else if (FieldByName('RDB$SYSTEM_FLAG').AsInteger <> 0) and
          (not FieldByName('RDB$SYSTEM_FLAG').IsNull) then
          ItemType := 'SYSTEM'
        else
          ItemType := 'TABLE';
      end;
      Inc(i);
      Next;
    end;
  end;
  IbDataSet.Close;
  IbTransaction.CommitRetaining;
  IbDataSet.Free;
  IbTransaction.Free;
end;

end.


Example:
Create a new project and add a TIbDatabase (IbDatabase1), a TButton (Button1) and a TMemo (Memo1). Assign the DatabaseName property of the IbDatabase1 component and set "IbDatabase1.Connected := True".

procedure TForm1.Button1Click(Sender: TObject);
var
  output: TTableItems;
  i: integer;
begin
  output := IbDbTables(IbDatabase1, [ttTable, ttView]);
  { output := IbDbTables(IbDatabase1, [ttView]);
  output := IbDbTables(IbDatabase1, [ttSystemTable]); }
  for i := low(output) to high(output) do
  begin
    Memo1.Lines.Add(output[i].ItemName + '---' + output[i].ItemType);
  end;
  output := nil;
end;

2005. október 21., péntek

Edit *.pif files programmatically


Problem/Question/Abstract:

Does anybody know how to create and/ or modify a *.pif programmatically? Windows creates *.pif files for all DOS programs but does not provide any method to edit it except manually. Is that right?

Answer:

procedure CreateShortcut(const FileName: string; Location: ShortcutType);
{Procedure to create a shortcut on the desktop or startmenu}
var
  MyObject: IUnknown;
  MySLink: IShellLink;
  MyPFile: IPersistFile;
  Directory: string;
  LinkName: string;
  IconName: string;
  DirName: string;
  pifName: WideString;
  WFileName: WideString;
  QuickLaunchReg: TRegIniFile;
  aPidl: PItemIDList;
  Res: HResult;
  Buf: PByteArray;
  PPif: pif_record_ref_type absolute Buf;
  Flag: boolean;
  i, j: integer;
  n: longint;
  PHeading: section_heading_record_ref_type;
  PVMMSection: windows_vmm_section_ref_type;
  PW386Section: ^windows_386_section_type;
  f: file;
begin
  MyObject := CreateComObject(CLSID_ShellLink);
  MySLink := MyObject as IShellLink;
  MyPFile := MyObject as IPersistFile;
  MySLink.SetPath(PChar(FileName));
  LinkName := ChangeFileExt(FileName, '.lnk');
  LinkName := ExtractFileName(LinkName);
  case Location of
    _DESKTOP:
      Res := SHGetSpecialFolderLocation(Application.Handle, CSIDL_DESKTOPDIRECTORY,
        aPidl);
    _STARTMENU:
      Res := SHGetSpecialFolderLocation(Application.Handle, CSIDL_STARTMENU, aPidl);
    _SENDTO:
      Res := SHGetSpecialFolderLocation(Application.Handle, CSIDL_SENDTO, aPidl);
    _QUICKLAUNCH:
      Res := 0;
  end;
  if Res <> NOERROR then
  begin
    case Location of
      _DESKTOP:
        Directory := 'ShellFolders->Desktop';
      _STARTMENU:
        Directory := 'ShellFolders->Start Menu';
      _SENDTO:
        Directory := 'ShellFolders->SendTo';
      _QUICKLAUNCH:
        Directory := 'MapGroups->Quick Launch';
    end;
    ShowMessage(Directory + ': Failed');
  end
  else
  begin
    {Get the actual path from the PItemIDList}
    SetLength(Directory, MAX_PATH);
    SHGetPathFromIDList(aPidl, PChar(Directory));
    SetLength(Directory, StrLen(PChar(Directory)));
    WFileName := Directory + '\' + LinkName;
    if (Location = _DESKTOP) and (LinkName = 'PAULITA.lnk') then
    begin
      pifName := ExtractFilePath(FileName);
      Res := MyPFile.Load(PWChar(pifName + 'SYS\PauLita.pif'), 0);
      if Res = E_OUTOFMEMORY then
        ShowMessage('.PIF LOAD: Out of Memory')
      else if Res = E_FAIL then
        ShowMessage('.PIF LOAD: Failed');
      IconName := pifName + 'SYS\PAULITA.ICO';
      Res := MySLink.SetIconLocation(PChar(IconName), 0);
      if Res <> NOERROR then
        ShowMessage('SetIconLocation: Failed');
    end;
    MySLink.SetPath(PChar(FileName));
    DirName := ExtractFilePath(FileName);
    DirName := Copy(DirName, 1, Length(DirName) - 1);
    MySLink.SetWorkingDirectory(PChar(DirName));
    Res := MyPFile.Save(PWChar(WFileName), FALSE);
    if Res <> S_OK then
      ShowMessage('Save ' + WFileName + ' Failed');
    if (Location = _DESKTOP) and (LinkName = 'PAULITA.lnk') then
    begin
      Buf := nil;
      Assign(f, Directory + '\PAULITA.PIF');
      try
        Reset(f, 1);
        n := FileSize(f);
        GetMem(Buf, n);
        BlockRead(f, Buf^, n);
        PW386Section := nil;
        Flag := FALSE;
        i := $187;
        while i + SizeOf(section_heading_record_type) <= n do
        begin
          PHeading := @Buf^[i];
          {ShowMessage(PHeading^.Name); }
          {Look for WINDOWS 386 3.0 group}
          if StrPas(@PHeading^.Name) = 'WINDOWS 386 3.0' then
          begin
            PW386Section := @Buf^[i + SizeOf(section_heading_record_type)];
          end;
          {Look for WINDOWS VMM 4.0 group}
          if StrPas(@PHeading^.Name) = 'WINDOWS VMM 4.0' then
          begin
            Flag := TRUE;
            Break;
          end;
          i := i + SizeOf(section_heading_record_type) + PHeading^.Len;
        end;
        if not Flag then
        begin
          ShowMessage('WINDOWS VMM 4.0 not Found in' + Directory + '\PAULITA.PIF');
        end
        else
        begin
          Flag := FALSE;
          if (PPif^.Flags1 and CLOSE_ON_EXIT) = $0000 then
          begin
            PPif^.Flags1 := PPif^.Flags1 or CLOSE_ON_EXIT;
            Flag := TRUE;
          end;
          j := Pos('PAULITA.EXE', PPif^.FileName);
          if j > 0 then
          begin
            StrPCopy(PPif^.FileName, Copy(StrPas(@PPif^.FileName), 1, j - 1) +
              'LITA.BAT'#0);
            Flag := TRUE;
          end;
          if PW386Section <> nil then
          begin
            if (PW386Section^.Flags1 and $00000008) = $0000 then
            begin
              {Used}
              PW386Section^.Flags1 := PW386Section^.Flags1or $00000008;
                {Full screen mode}
              Flag := TRUE;
            end;
            if (PW386Section^.MaxEMS <> $FFFF) or (PW386Section^.ReqEMS <> $0000) or
              (PW386Section^.MaxXMS <> $FFFF) or (PW386Section^.ReqXMS <> $0000) then
            begin
              PW386Section^.MaxEMS := $FFFF;
              PW386Section^.ReqEMS := $0000;
              PW386Section^.MaxXMS := $FFFF;
              PW386Section^.ReqXMS := $0000;
              Flag := TRUE;
            end;
          end;
          PVMMSection := @Buf^[i + SizeOf(section_heading_record_type)];
          if (PVMMSection^.Flags2 and FULL_SCREEN_MODE) = $0000 then
          begin
            {Not used}
            PVMMSection^.Flags2 := PVMMSection^.Flags2 or FULL_SCREEN_MODE;
            Flag := TRUE;
          end;
          if Flag then
          begin
            Seek(f, 0);
            BlockWrite(f, Buf^, n);
          end;
        end;
      finally
        Close(f);
        if Buf <> nil then
          FreeMem(Buf, n);
      end;
    end;
  end;
end;

2005. október 20., csütörtök

How to iterate through the fields of a TTable


Problem/Question/Abstract:

How to iterate through the fields of a TTable

Answer:

There are a number of reasons why a program might need to query the structure of a table used in the application. One reason is a prelude to creating TField components at run-time that represent the fields in the table. The information gleaned from the structure of the table form the basis of the TField components to be created.

The example below demonstrates how to iterate through the fields available in a TTable or TQuery. The example extracts information about the available fields and displays the information in a TListBox, but the same methodology can be used to provide information necessary for the dynamic building of TField descendants. The example uses a TTable as the data set, but a TQuery can be used in the same manner as both TTable and TQuery components incorporate the FieldDefs property the same way.

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
  F: TFieldDef;
  D: string;
begin
  Table1.Active := True;
  ListBox1.Items.Clear;
  with Table1 do
  begin
    for i := 0 to FieldDefs.Count - 1 do
    begin
      F := FieldDefs.Items[i];
      case F.DataType of
        ftUnknown: D := 'Unknown';
        ftString: D := 'String';
        ftSmallint: D := 'SmallInt';
        ftInteger: D := 'Integer';
        ftWord: D := 'Word';
        ftBoolean: D := 'Boolean';
        ftFloat: D := 'Float';
        ftCurrency: D := 'Currency';
        ftBCD: D := 'BCD';
        ftDate: D := 'Date';
        ftTime: D := 'Time';
        ftDateTime: D := 'DateTime';
        ftBytes: D := 'Bytes';
        ftVarBytes: D := '';
        ftBlob: D := 'BLOB';
        ftMemo: D := 'Memo';
        ftGraphic: D := 'Graphic';
      else
        D := '';
      end;
      ListBox1.Items.Add(F.Name + ', ' + D);
    end;
  end;
  Table1.Active := False;
end;

2005. október 19., szerda

How to clip a TBitmap inside an irregular shape


Problem/Question/Abstract:

If I have an irregular shape, say a rectangle with the top corners rounded. How can I fill the inside of that shape with a bitmap or limit pen drawing to inside the area of the shape?

Answer:

Below is a small sample of how the problem can be resolved, using a Path converting the Path to a Region and setting the ClippingRgn:

procedure DrawHeader
var
  Bmp: TBitmap;
  clEnd, clStart: TColor;
  R: TRect;
  Rd: Integer;
  Rgn: HRGN;
begin
  Rd := spnTest.IntValue;
  GetActiveColors(clStart, clEnd);
  Bmp := TBitmap.Create;
  try
    R.Left := btnClipGradient.Left + btnClipGradient.Width + 20;
    R.Top := btnClipGradient.Top;
    R.Right := R.Left + 100;
    R.Bottom := R.Top + 100;
    with Bmp do
    begin
      Height := 20;
      Width := 100;
      DrawGradient(Canvas, Rect(0, 0, 100, 20), 50, goHorizontal, clStart, clEnd);
    end;
    with Canvas do
    begin
      BeginPath(Handle);
      Pen.Color := clYellow;
      Pen.Style := psSolid;
      {Bottom line}
      MoveTo(R.Left, R.Bottom);
      LineTo(R.Right, R.Bottom);
      {Right Line}
      LineTo(R.Right, R.Top + (Rd div 2));
      {Top Right}
      ArcTo(Handle, R.Right - Rd + 1, R.Top, R.Right + 1, R.Top + Rd, R.Right + 1,
        R.Top + (Rd div 2), R.Right - (Rd div 2) + 1, R.Top);
      {Top Line}
      LineTo(R.Right - (Rd div 2), R.Top);
      {Top Left}
      ArcTo(Handle, R.Left, R.Top, R.Left + Rd, R.Top + Rd, R.Left + (Rd div 2),
        R.Top, R.Left, R.Top + (Rd div 2));
      {Left line}
      LineTo(R.Left, R.Top + (Rd div 2));
      EndPath(Handle);
      Rgn := PathToRegion(Handle);
      SelectClipRgn(Handle, Rgn);
      Draw(R.Left + 1, R.Top + 1, Bmp);
      SelectClipRgn(Handle, HRGN(nil));
      Pen.Color := clYellow;
      Pen.Style := psSolid;
      {Bottom line}
      MoveTo(R.Left, R.Bottom);
      LineTo(R.Right, R.Bottom);
      {Right Line}
      LineTo(R.Right, R.Top + (Rd div 2));
      {Top Right}
      ArcTo(Handle, R.Right - Rd + 1, R.Top, R.Right + 1, R.Top + Rd, R.Right + 1,
        R.Top + (Rd div 2), R.Right - (Rd div 2) + 1, R.Top);
      {Top Line}
      LineTo(R.Right - (Rd div 2) + 1, R.Top);
      {Top Left}
      ArcTo(Handle, R.Left, R.Top, R.Left + Rd, R.Top + Rd, R.Left + (Rd div 2),
        R.Top, R.Left, R.Top + (Rd div 2));
      {Left line}
      LineTo(R.Left, R.Bottom);
      DeleteObject(Rgn);
    end;
  finally
    Bmp.Free;
  end;
end;

2005. október 18., kedd

Create a transparent form using regions


Problem/Question/Abstract:

When I override the CreateParams method of the TForm class and put an "or" clause to include a WS_EX_TRANSPARENT effect, all work fine. My Form appears with a transparent effect (or better, it does not appear. Only controls appear). But if I change the icons positions on the Desktop screen, my form does not get the changes and redraws itself. Well, I call redraw manually, but the form cannot get the desktop screen rectangle to correct the background of the form. How do I solve this problem? Is there a way to get a selected rectangle of the desktop screen to be copied to the canvas on the form?

Answer:

Here's a transparent form method that uses regions. It's so transparent, you can click right through to the underlying windows:

unit Unit1;

{The transparent form effect is done with regions. First create a region that encompasses the entire form. Then, find the client area of the form (Client vs. non-Client) and combine with the full region with RGN_DIFF to make the borders and title bar visible.  Then create a region for each of the controls and combine them with the original (FullRgn) region.}

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Panel1: TPanel;
    Button2: TButton;
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    { Private declarations }
    procedure DoVisible;
    procedure DoInvisible;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  FullRgn, ClientRgn, CtlRgn: THandle;

implementation

{$R *.DFM}

procedure TForm1.DoInvisible;
var
  AControl: TControl;
  A, Margin, X, Y, CtlX, CtlY: Integer;
begin
  Margin := (Width - ClientWidth) div 2;
  {First, get form region}
  FullRgn := CreateRectRgn(0, 0, Width, Height);
  {Find client area region}
  X := Margin;
  Y := Height - ClientHeight - Margin;
  ClientRgn := CreateRectRgn(X, Y, X + ClientWidth, Y + ClientHeight);
  {'Mask' out all but non-client areas}
  CombineRgn(FullRgn, FullRgn, ClientRgn, RGN_DIFF);
  {Now, walk through all the controls on the form and 'OR' them into the existing Full region}
  for A := 0 to ControlCount - 1 do
  begin
    AControl := Controls[A];
    if (AControl is TWinControl) or (AControl is TGraphicControl) then
      with AControl do
      begin
        if Visible then
        begin
          CtlX := X + Left;
          CtlY := Y + Top;
          CtlRgn := CreateRectRgn(CtlX, CtlY, CtlX + Width, CtlY + Height);
          CombineRgn(FullRgn, FullRgn, CtlRgn, RGN_OR);
        end;
      end;
  end;
  {When the region is all ready, put it into effect:}
  SetWindowRgn(Handle, FullRgn, TRUE);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  {Clean up the regions we created}
  DeleteObject(ClientRgn);
  DeleteObject(FullRgn);
  DeleteObject(CtlRgn);
end;

procedure TForm1.DoVisible;
begin
  {To restore complete visibility:}
  FullRgn := CreateRectRgn(0, 0, Width, Height);
  CombineRgn(FullRgn, FullRgn, FullRgn, RGN_COPY);
  SetWindowRgn(Handle, FullRgn, TRUE);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  {We start out as a transparent form ...}
  DoInvisible;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  {This button just toggles between transparent and not transparent}
  if Button1.Caption = 'Show Form' then
  begin
    DoVisible;
    Button1.Caption := 'Hide Form';
  end
  else
  begin
    DoInvisible;
    Button1.Caption := 'Show Form';
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Application.Terminate;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  {Need to address the transparency if the form gets resized. Also, note that Form1 scroll bars are set to VISIBLE/FALSE. I did that to save a little coding here....}
  if Button1.Caption = 'Show Form' then
    DoInvisible
  else
    DoVisible;
end;

end.