2010. december 31., péntek

How to play sound from a resource file


Problem/Question/Abstract:

I am attempting to have a wave file played when a button is clicked. Rather than install the wave file and use the PlaySound() API call, I'd like to put it into a resource file so that it plays with only the EXE present.

Answer:

{ ... }
var
  FindHandle, ResHandle: THandle;
  ResPtr: Pointer;
begin
  FindHandle := FindResource(HInstance, 'Name of your resource', 'WAVE');
  if FindHandle <> 0 then
  begin
    ResHandle := LoadResource(HInstance, FindHandle);
    if ResHandle <> 0 then
    begin
      ResPtr := LockResource(ResHandle);
      if ResPtr <> nil then
        SndPlaySound(PChar(ResPtr), snd_ASync or snd_Memory);
      UnlockResource(ResHandle);
    end;
    FreeResource(FindHandle);
  end;
end;

2010. december 30., csütörtök

How to paint on a TControlCanvas in a TMemo


Problem/Question/Abstract:

How to paint on a TControlCanvas in a TMemo

Answer:

Solve 1:

Create a new component derived from TMemo and override its drawing. Something like this:


type
  TMyMemo = class(TMemo)
  protected
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  end;

procedure TMyMemo.WMPaint(var Message: TWMPaint);
var
  MCanvas: TControlCanvas;
  DrawBounds: TRect;
begin
  inherited;
  MCanvas := TControlCanvas.Create;
  DrawBounds := ClientRect;
  try
    MCanvas.Control := Self;
    with MCanvas do
    begin
      Brush.Color := clBtnFace;
      FrameRect(DrawBounds);
      InflateRect(DrawBounds, -1, -1);
      FrameRect(DrawBounds);
      FillRect(DrawBounds);
      MoveTo(33, 0);
      Brush.Color := clWhite;
      LineTo(33, ClientHeight);
      PaintImages;
    end;
  finally
    MCanvas.Free;
  end;
end;


The PaintImages procedure draws images on the TMemo's canvas.


procedure TMyMemo.PaintImages;
var
  MCanvas: TControlCanvas;
  DrawBounds: TRect;
  i, j: Integer;
  OriginalRegion: HRGN;
  ControlDC: HDC;
begin
  MCanvas := TControlCanvas.Create;
  DrawBounds := ClientRect;
  try
    MCanvas.Control := Self;
    ControlDC := GetDC(Handle);
    MCanvas.Draw(0, 1, Application.Icon);
  finally
    MCanvas.Free;
  end;
end;


Solve 2:

Basically you will need to intercept WM_ERASEBKGND and WM_PAINT messages. Let's say you have a TImage control the same size as your TMemo holding a bitmap that you want to use as your background. Let's assume you have this hooked in a TImage field called FImage available in your memo component code. The following should give you a good start:

In your class definition for TMyMemo:


procedure WMEraseBkGnd(var Message: TWMEraseBkGnd); message WM_ERASEBKGND;
procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
    {...}

procedure TMyMemo.WMEraseBkGnd(var Message: TWMEraseBkGnd);
begin
  {assuming we get a good DC in Message - you should check this of course}
  BitBlt(Message.dc, 0, 0, Width, Height, FImage.Canvas.Handle, 0, 0, SRCCOPY);
  Message.Result := -1;
end;

procedure TMyMemo.WMPaint(var Message: TWMPaint);
var
  bm: TBitmap;
  dc: HDC;
  hDummy: HWND;
  i: integer;
  tm: TEXTMETRIC;
  Y: integer;
begin
  bm := TBitmap.Create;
  try
    bm.Width := Width;
    bm.Height := Height;
    Perform(WM_ERASEBKGND, bm.Canvas.Handle, 0); {always in this simple example}
    bm.Canvas.Font.Assign(Font);
    GetTextMetrics(bm.Canvas.Handle, tm);
    SetBkMode(bm.Canvas.Handle, TRANSPARENT);
    Y := 0;
    for i := 0 to Lines.Count - 1 do
    begin
      bm.Canvas.TextOut(0, Y, Lines[i]);
      Inc(Y, tm.tmHeight);
    end;
    dc := GetDeviceContext(hDummy);
    BitBlt(dc, 0, 0, Width, Height, bm.Canvas.Handle, 0, 0, SRCCOPY);
    ReleaseDC(hDummy, dc);
  finally
    bm.Free;
  end;
  Message.Result := 0;
end;


Note that this is only good for displaying transparently. Editing is another story. What I do is call the inherited behavior when I'm editing (so no transparency while typing). Obviously this example has no error checking. Also, the Message parameter for WM_PAINT may contain a device context to use in lieu of GetDeviceContext. The text always draws at X = 0 so it ignores the border style & width. Finally, you should check for clipping to improve performance (I did this last).

2010. december 29., szerda

Incremental search in a DBGrid


Problem/Question/Abstract:

When you fill a DBGrid with Data from a Query you can search for each column of the Grid, with a TEdit.

Answer:

Here is a sample project:

// Makes incremental search in a DBGrid with a TEdit

unit U_Main;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Db, DBTables, StdCtrls, Grids, DBGrids, ExtCtrls, DBCtrls;

type
  TFm_Main = class(TForm)
    Panel1: TPanel;
    Panel2: TPanel;
    qry_Data: TQuery;
    Ds_Data: TDataSource;
    dbg_Data: TDBGrid;
    Label1: TLabel;
    Ed_Search: TEdit;
    Database1: TDatabase;
    qry_DataNUM_FACTURA: TStringField;
    qry_DataF_FACTURA: TDateTimeField;
    qry_DataM_DEVENGADO: TFloatField;
    DBNavigator1: TDBNavigator;
    procedure dbg_DataTitleClick(Column: TColumn);
    procedure FormCreate(Sender: TObject);
    procedure Ed_SearchChange(Sender: TObject);

  private
    FQueryStatement: string;

    //Since for Alphanumeric Field you don�t need to validate nothing
    //just keep a method pointer to the default Event Handler
    FALphaNumericKeyPress: TKeyPressEvent;
  public
    property QueryStatement: string read FQueryStatement;

    //Since we are going to search in various Fields wich DataType
    //can be of diferent types, we must validate the user input on
    //the OnkeyPress of the TEdit, but instead of building a super
    //generic routine, lets make things simple. Build a separate
    //method for each DataType you are interested in validate.

    //I will only validate for Fields of type ftFloat, but you easily
    //customize the code for your own needs..

    //Method Pointer for Fields of DataType ftFloat
    procedure FloatOnKeyPress(Sender: TObject; var Key: Char);
  end;

var
  Fm_Main: TFm_Main;

implementation

{$R *.DFM}

procedure TFm_Main.dbg_DataTitleClick(Column: TColumn);
var
  vi_Counter: Integer;
  vs_Field: string;
begin
  with dbg_Data do
  begin
    //First, deselect all the Grid�s Columns
    for vi_Counter := 0 to Columns.Count - 1 do
      Columns[vi_Counter].Color := clWindow;

    //Next "Select" the column the user has Clicked on
    Column.Color := clTeal;

    //Get the FieldName of the Selected Column
    vs_Field := Column.FieldName;

    //Order the Grid�s Data by the Selected column
    with qry_Data do
    begin
      DisableControls;
      Close;
      SQL.Clear;
      SQL.Text := QueryStatement + 'ORDER BY ' + vs_Field;
      Open;
      EnableControls;
    end;

    //Get the DataType of the selected Field and change the Edit�s event
    //OnKeyPress to the proper method Pointer
    case Column.Field.DataType of
      ftFloat: Ed_Search.OnKeyPress := FloatOnKeyPress;
    else
      Ed_Search.OnKeyPress := FALphaNumericKeyPress;
    end;
  end;
end; //End of TFm_Main.dbg_DataTitleClick

procedure TFm_Main.FloatOnKeyPress(Sender: TObject; var Key: Char);
begin
  if not (Key in ['0'..'9', #13, #8, #10, #46]) then
    Key := #0;
end; //End of TFm_Main.FloatOnKeyPress

procedure TFm_Main.FormCreate(Sender: TObject);
begin
  //Keep a pointer for the default event Handler
  FALphaNumericKeyPress := Ed_Search.OnKeyPress;

  //Set the original Query SQL Statement
  FQueryStatement := 'SELECT FIELD1, FIELD2, FIELD3 '
    'FROM ANYTABLE ';

  //Select the first Grid�s Column
  dbg_DataTitleClick(dbg_Data.Columns[0]);
end; //End of TFm_Main.FormCreate

procedure TFm_Main.Ed_SearchChange(Sender: TObject);
var
  vi_counter: Integer;
  vs_Field: string;
begin
  with dbg_Data do
  begin
    //First determine wich is the Selected Column
    for vi_Counter := 0 to Columns.Count - 1 do
      if Columns[vi_Counter].Color = clTeal then
      begin
        vs_Field := Columns[vi_Counter].FieldName;
        Break;
      end;

    //Locate the Value in the Query
    with qry_Data do
      case Columns[vi_Counter].Field.DataType of
        ftFloat: Locate(vs_Field, StrToFloat(Ed_Search.Text),
            [loCaseInsensitive, loPartialKey]);
      else
        Locate(vs_Field, Ed_Search.Text, [loCaseInsensitive,
          loPartialKey]);
      end;
  end;
end; //End of TFm_Main.Ed_SearchChange

end.

So, you can customize the code to manage another DataTypes of TFields.

2010. december 28., kedd

An example of drag and drop between DBGrids


Problem/Question/Abstract:

This sample component and sample project demonstrates an easy way of enabling drag and drop of an arbitrary field in one data aware grid onto an arbitrary field in another data aware grid.

Answer:

An example of drag and drop between DBGrids - by Borland Developer Support Staff

Technical Information Database

TI1562D.txt - An example of drag and drop between DBGrids

Category   :General Programming
Platform   :All Windows
Product    :All32Bit,  

Description:
Title: An example of drag and drop between DBGrids

This sample component and sample project demonstrates an easy way of enabling drag and drop of an arbitrary field in one data aware grid onto an arbitrary field in another data aware grid.

Launch Delphi x.xx (the code will work in 1 and 2 as well with some minor changes).

Do a File|New|Unit. Take the MyDBGrid unit (below) and paste it in the newly created unit. Do a File|Save As. Save the unit as MyDBGrid.pas.

Do a Component|Install Component. Switch to the Info New Package tab. Put MyDBGrid.pas in the Unit file name box. Call the package MyPackage.dpk. Hit Yes when Delphi tells you that the package will be built and installed. Hit OK when Delphi tells you that VCLxx.DPL is needed. The package will now be rebuilt and installed. You will now find the TMyDBGrid component on your Samples tab on your component palette. Close the package editor and save the package.

Do a File|New Application. Right click on the form (Form1) and select View As Text. Take the GridU1 form source (below) and paste it in Form1. Right click on the form and select View As Form. This may take a few moments since it's opening up the tables for you. Take the GridU1 unit (below) and paste it in the unit (Unit1).

Do a File|Save Project As. Save the unit as GridU1.pas. Save the project as GridProj.dpr.

Now, run the project and enjoy the dragging and dropping of fields inbetween or with the two grids.


The MyDBGrid unit

unit MyDBGrid;

interface

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

type
  TMyDBGrid = class(TDBGrid)
  private
    { Private declarations }
    FOnMouseDown: TMouseEvent;
  protected
    { Protected declarations }
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
  published
    { Published declarations }
    property Row;
    property OnMouseDown read FOnMouseDown write FOnMouseDown;
  end;

procedure Register;

implementation

procedure TMyDBGrid.MouseDown(Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Assigned(FOnMouseDown) then
    FOnMouseDown(Self, Button, Shift, X, Y);
  inherited MouseDown(Button, Shift, X, Y);
end;

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

end.

The GridU1 unit

unit GridU1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, Db, DBTables, Grids, DBGrids, MyDBGrid, StdCtrls;

type
  TForm1 = class(TForm)
    MyDBGrid1: TMyDBGrid;
    Table1: TTable;
    DataSource1: TDataSource;
    Table2: TTable;
    DataSource2: TDataSource;
    MyDBGrid2: TMyDBGrid;
    procedure MyDBGrid1MouseDown(Sender: TObject;
      Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure MyDBGrid1DragOver(Sender, Source: TObject;
      X, Y: Integer; State: TDragState; var Accept: Boolean);
    procedure MyDBGrid1DragDrop(Sender, Source: TObject;
      X, Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

var
  SGC: TGridCoord;

procedure TForm1.MyDBGrid1MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  DG: TMyDBGrid;
begin
  DG := Sender as TMyDBGrid;
  SGC := DG.MouseCoord(X, Y);
  if (SGC.X > 0) and (SGC.Y > 0) then
    (Sender as TMyDBGrid).BeginDrag(False);
end;

procedure TForm1.MyDBGrid1DragOver(Sender, Source: TObject;
  X, Y: Integer; State: TDragState; var Accept: Boolean);
var
  GC: TGridCoord;
begin
  GC := (Sender as TMyDBGrid).MouseCoord(X, Y);
  Accept := Source is TMyDBGrid and (GC.X > 0) and (GC.Y > 0);
end;

procedure TForm1.MyDBGrid1DragDrop(Sender, Source: TObject;
  X, Y: Integer);
var
  DG: TMyDBGrid;
  GC: TGridCoord;
  CurRow: Integer;
begin
  DG := Sender as TMyDBGrid;
  GC := DG.MouseCoord(X, Y);
  with DG.DataSource.DataSet do
  begin
    with (Source as TMyDBGrid).DataSource.DataSet do
      Caption := 'You dragged "' + Fields[SGC.X - 1].AsString + '"';
    DisableControls;
    CurRow := DG.Row;
    MoveBy(GC.Y - CurRow);
    Caption := Caption + ' to "' + Fields[GC.X - 1].AsString + '"';
    MoveBy(CurRow - GC.Y);
    EnableControls;
  end;
end;

end.

The GridU1 form

object Form1: TForm1
  Left = 200
    Top = 108
    Width = 544
    Height = 437
    Caption = 'Form1'
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = []
    PixelsPerInch = 96
    TextHeight = 13
    object MyDBGrid1: TMyDBGrid
    Left = 8
      Top = 8
      Width = 521
      Height = 193
      DataSource = DataSource1
      Row = 1
      TabOrder = 0
      TitleFont.Charset = DEFAULT_CHARSET
      TitleFont.Color = clWindowText
      TitleFont.Height = -11
      TitleFont.Name = 'MS Sans Serif'
      TitleFont.Style = []
      OnDragDrop = MyDBGrid1DragDrop
      OnDragOver = MyDBGrid1DragOver
      OnMouseDown = MyDBGrid1MouseDown
  end
  object MyDBGrid2: TMyDBGrid
    Left = 7
      Top = 208
      Width = 521
      Height = 193
      DataSource = DataSource2
      Row = 1
      TabOrder = 1
      TitleFont.Charset = DEFAULT_CHARSET
      TitleFont.Color = clWindowText
      TitleFont.Height = -11
      TitleFont.Name = 'MS Sans Serif'
      TitleFont.Style = []
      OnDragDrop = MyDBGrid1DragDrop
      OnDragOver = MyDBGrid1DragOver
      OnMouseDown = MyDBGrid1MouseDown
  end
  object Table1: TTable
    Active = True
      DatabaseName = 'DBDEMOS'
      TableName = 'ORDERS'
      Left = 104
      Top = 48
  end
  object DataSource1: TDataSource
    DataSet = Table1
      Left = 136
      Top = 48
  end
  object Table2: TTable
    Active = True
      DatabaseName = 'DBDEMOS'
      TableName = 'CUSTOMER'
      Left = 104
      Top = 240
  end
  object DataSource2: TDataSource
    DataSet = Table2
      Left = 136
      Top = 240
  end
end

2010. december 27., hétfő

Change the TreeView item height

Problem/Question/Abstract:

How to change the TreeView item height?

Answer:

uses CommCtrl;

{ .... }

procedure SetTreeViewItemHeight(aTreeView: TTreeView; aItemHeight: Word);
begin
aTreeView.Perform(TVM_SETITEMHEIGHT, aItemHeight, 0);
end;

// Example:

procedure TForm1.Button1Click(Sender: TObject);
begin
SetTreeViewItemHeight(TreeView1, 30);
end;


2010. december 26., vasárnap

How to fix the MDI close button and window menu glitches


Problem/Question/Abstract:

I am trying to write a MDI application. I use a main form with a MainMenu. Every child form merges its main menu in the main form's main menu. If one of the child forms gets maximized, the close button (x button in the upper right corner) is grayed out but still works. If I merge the child form's main menu manually, the close button behaves in the same way.

Answer:

Solve 1:

I have tried the following patch to Menus.pas and it works wonders for me. The button no longer disappears or disable and the window menu functions after changes are made to it. I would like to know how well this works for them. Neither of these two fixes are 'hacks' into that they don't cause extra flashing or refreshing. They just fix the 'problematic' code in Menus.pas. The below snippits of code are based on D5.

procedure TMenuItem.RebuildHandle;
const
  cFAF = $04;
var
  I: Integer;
  LRepopulate: Boolean;
begin
  if csDestroying in ComponentState then
    Exit;
  if csReading in ComponentState then
    FStreamedRebuild := True
  else
  begin
    if FMergedWith <> nil then
      FMergedWith.RebuildHandle
    else
    begin
      I := GetMenuItemCount(Handle);
      LRepopulate := I = 0;
      while I > 0 do
      begin
        if (WordRec(LongRec(GetMenuState(Handle, I - 1, MF_BYPOSITION)).Lo).Lo and
          cFAF) = 0 then
        begin
          RemoveMenu(Handle, I - 1, MF_BYPOSITION);
          LRepopulate := True;
        end;
        Dec(I);
      end;
      if LRepopulate then
      begin
        if (FParent = nil) and (FMenu is TMainMenu) and (GetMenuItemCount(Handle) = 0)
          then
        begin
          DestroyMenu(FHandle);
          FHandle := 0;
        end
        else
          PopulateMenu;
        MenuChanged(False);
      end;
    end;
  end;
end;

function TMenu.DispatchPopup(AHandle: HMENU): Boolean;

  function IsMDIWindowMenu(AItem: TMenuItem): Boolean;
  begin
    Result := Assigned(Application.MainForm) and (Application.MainForm.FormStyle =
      fsMDIForm)
      and (Application.MainForm.WindowMenu = AItem);
  end;

var
  Item: TMenuItem;
  LRebuild: Boolean;
begin
  Result := False;
  Item := FindItem(AHandle, fkHandle);
  if Item <> nil then
  begin
    if not (csDesigning in Item.ComponentState) then
      Item.InitiateActions;
    Item.Click;
    LRebuild := Item.InternalRethinkHotkeys(False);
    LRebuild := Item.InternalRethinkLines(False) or LRebuild;
    if LRebuild then
      Item.RebuildHandle;
    if IsMDIWindowMenu(Item) then
      if SendMessage(Application.MainForm.ClientHandle, WM_MDIREFRESHMENU, 0, 0) <> 0
        then
        DrawMenuBar(Application.MainForm.Handle);
    Result := True;
  end
  else if not (csDesigning in ComponentState) and (Self is TPopupMenu) then
    Items.InitiateActions;
end;

You cannot recompile the standard packages, your license does not allow it and there are some units missing anyway. Copy the menus unit to your project directory, modify the copy, and compile it as part of your project. You can copy the produced DCU back into the LIB directory for other projects to use. This will work as long as you don't build with packages and don't change anything in the units interface.


Solve 2:

This piece of code fixes a bug present in all versions of Delphi, that occurs when switching between maximized MDI child windows, causing the close icon to be grayed in Delphi 3 & 4 or the system menu and max/min/close icons to vanish in Delphi 5. Tested in Delphi Client/Server 3, 4 & 5.

{$IFDEF VER100}
{$DEFINE DELPHI3&4}
{$ENDIF}

{$IFDEF VER120}
{$DEFINE DELPHI3&4}
{$ENDIF}

type
  TMDIChild = class(TForm)
    { ... }
  private
    procedure WMMDIActivate(var Msg: TWMMDIActivate); message WM_MDIACTIVATE;
    { ... }
  end;

procedure TMDIChild.WMMDIActivate;
var
  Style: Longint;
begin
  if (Msg.ActiveWnd = Handle) and (biSystemMenu in BorderIcons) then
  begin
    Style := GetWindowLong(Handle, GWL_STYLE);
    if (Style and WS_MAXIMIZE <> 0) and (Style and WS_SYSMENU = 0) then

{$IFDEF DELPHI3&4}
      SetWindowLong(Handle, GWL_STYLE, Style or WS_SYSMENU);
{$ELSE}
      SendMessage(Handle, WM_SIZE, SIZE_RESTORED, 0);
{$ENDIF}
  end;
  inherited;
end;

2010. december 25., szombat

How to compare the items in a TStringList with the items in the child nodes of a selected node in a TTreeView


Problem/Question/Abstract:

I would like to compare the items in a TStringList with the child nodes of the selected node in a TTreeView and instead of deleting the matching nodes, change the image of the node to one from a TImageList component.

Answer:

Solve 1:

Something like:

var
  T: TTreeNode;
begin
  {Point at the first child of the selected node}
  T := TreeView.Selected.GetFirstChild;
  {Loop over all children of this node}
  while Assigned(T) do
  begin
    {Compare T.Text against contents of a listbox, or whatever...}
    {T set to nil if Selected has no more children}
    T := TreeView.Selected.GetNextChild(T);
  end;
end;

Note this only works with direct children of the Selected node; if you may have to deal with deeper levels in the tree then it gets (marginally) more complex.


Solve 2:

Try one of these:

{ ... }
for i := 0 to TreeView1.Selected.Count - 1 do
  if ListBox1.Items.IndexOf(TreeView1.Selected.Item[i].Text) >= 0 then
    TreeView1.Selected.Item[i].ImageIndex := 4;
{ ... }

var
  child: TTreeNode;
  { ... }
  child := TreeView1.Selected.GetFirstChild;
  while Assigned(child) do
  begin
    if ListBox1.Items.IndexOf(child.Text) >= 0 then
      child.ImageIndex := 4;
    child := child.GetNextSibling
  end;

2010. december 24., péntek

How to enum font sizes like TFontDialog does


Problem/Question/Abstract:

I would like to get all font sizes for the given font like TFontDialog does. Minimum and maximum font size would be nice, too.

Answer:

Note that the list of font sizes for Truetype fonts is just an arbitrary selection of often-used sizes, you can scale these fonts to nearly any size.

Example for the use of EnumFontFamilies. Project requires two listboxes on the form, nothing else.

unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
  listbox1.items.assign(screen.fonts);
end;

function EnumProc(var elf: TEnumLogFont; var ntm: TNewTextmetric;
  fonttype: Integer; listbox: TListbox): Integer; stdcall;
var
  S: string;
begin
  if fonttype = TRUETYPE_FONTTYPE then
  begin
    listbox.Items.Add(Format('Name: %s', [elf.elfFullName]));
    listbox.Items.Add(Format('Style: %s', [elf.elfStyle]));
  end
  else
    listbox.Items.Add(Format('Name: %s', [elf.elfLogfont.lfFacename]));
  listbox.Items.Add(Format('Size: %d', [elf.elfLogFont.lfHeight]));
  listbox.Items.Add(Format('Weight: %d', [elf.elfLogFont.lfWeight]));
  if elf.elfLogFont.lfItalic <> 0 then
    listbox.Items.Add('This font is italic');
  case fonttype of
    DEVICE_FONTTYPE: S := 'device font';
    RASTER_FONTTYPE: S := 'raster font';
    TRUETYPE_FONTTYPE: S := 'truetype font'
  else
    S := 'unknown font type';
  end;
  listbox.Items.Add(Format('This is a %s', [S]));
  Result := 1;
end;

procedure TForm1.ListBox1Click(Sender: TObject);
begin
  listbox2.clear;
  with listbox1 do
    if ItemIndex >= 0 then
      EnumFontFamilies(Self.Canvas.Handle, PChar(Items[ItemIndex]),
        @EnumProc, Longint(listbox2));
end;

end.

2010. december 23., csütörtök

Hide and show the title bar of a TForm


Problem/Question/Abstract:

How to hide and show the title bar of a TForm

Answer:

Here is how to hide the titlebar:

procedure TYourFormName.HideTitlebar;
var
  Save: LongInt;
begin
  if BorderStyle = bsNone then
    Exit;
  Save := GetWindowLong(Handle, GWL_STYLE);
  if (Save and WS_CAPTION) = WS_CAPTION then
  begin
    case BorderStyle of
      bsSingle, bsSizeable:
        SetWindowLong(Handle, GWL_STYLE, Save and (not (WS_CAPTION)) or WS_BORDER);
      bsDialog:
        SetWindowLong(Handle, GWL_STYLE, Save and (not (WS_CAPTION)) or DS_MODALFRAME
          or WS_DLGFRAME);
    end;
    Height := Height - GetSystemMetrics(SM_CYCAPTION);
    Refresh;
  end;
end;

And here is how we show it again:

procedure TYourFormName.ShowTitlebar;
var
  Save: LongInt;
begin
  if BorderStyle = bsNone then
    Exit;
  Save := GetWindowLong(Handle, GWL_STYLE);
  if (Save and WS_CAPTION) <> WS_CAPTION then
  begin
    case BorderStyle of
      bsSingle, bsSizeable:
        SetWindowLong(Handle, GWL_STYLE, Save or WS_CAPTION or WS_BORDER);
      bsDialog:
        SetWindowLong(Handle, GWL_STYLE, Save or WS_CAPTION or DS_MODALFRAME or
          WS_DLGFRAME);
    end;
    Height := Height + GetSystemMetrics(SM_CYCAPTION);
    Refresh;
  end;
end;

2010. december 22., szerda

How to compare two images pixel by pixel


Problem/Question/Abstract:

How can I compare an image pixel by pixel. The images have the same size. But that is accepted that the images have a fault tolerance up 10%.

Answer:

Solve 1:

{ ... }
for x := 0 to image1.width - 1 do
  for y := 0 to image1.height - 1 do
    if image1.picture.bitmap.canvas.pixels[x, y] <>
      image2.picture.bitmap.canvas.pixels[x, y] then
      inc(different);

if different > (image1.width * image1.height / 10) then
  picturedifferent;


Solve 2:

A faster approach:

{ ... }
var
  b1, b2: TBitmap;
  c1, c2: PByte;
  x, y, i, different: integer;
begin
  b1 := Image1.Picture.Bitmap;
  b2 := Image2.Picture.Bitmap;
  assert(b1.PixelFormat = b2.PixelFormat); {they have to be equal}
  different := 0;
  for y := 0 to b1.Height - 1 do
  begin
    c1 := b1.Scanline[y];
    c2 := b2.Scanline[y];
    for x := 0 to b1.Width - 1 do
      for i := 0 to BytesPerPixel - 1 do {1, to 4, dep. on pixelformat}
      begin
        inc(different, integer(c1^ <> c2^));
        inc(c1);
        inc(c2);
      end;
  end;
end;

Using an Int for "different" means your pictures can be at most 715827882 pixels large, or 26754 x 26754, which should be enough for most uses. Depending on how you want to count "equal" you could enforce 32bits per pixel and use PLongWord instead, ditching the BytesPerPixel loop.

2010. december 21., kedd

Create a Combobox winthin in a Stringgrid


Problem/Question/Abstract:

How to dynamically create a Combobox within a Cell of a StringGrid

Answer:

You need a descendent of TStringgrid that properly reflects WM_COMMAND to embedded controls. The standard grid does not do it since it is not intended to play parent to other controls.

Additionaly simply declare a Set- and GetMethod to access the items of die combobox

unit BWControlStringGrid;

interface

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

type
  TBWControlStringGrid = class(TStringGrid)
  private
    fComboBox: TCombobox;
    procedure WMCommand(var msg: TWMCommand); message WM_COMMAND;
    procedure DblClick; override;
    procedure Click; override;
    procedure RelocateComboBox;
    procedure HideCombobox;
  protected
    procedure KeyPress(var Key: Char); override;
  public
    constructor Create(AOWner: TComponent); override;
    destructor Destroy; override;
  published
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('hEaDRoOm', [TBWControlStringGrid]);
end;

procedure TBWControlStringGrid.WMCommand(var msg: TWMCommand);
begin
  if EditorMode and (msg.Ctl = fComboBox.Handle) then
    inherited
  else if msg.Ctl <> 0 then
    msg.result :=
      SendMessage(msg.ctl, CN_COMMAND,
      TMessage(msg).wparam,
      TMessage(msg).lparam);
end;

procedure TBWControlStringGrid.KeyPress(var Key: Char);
begin
  if Key = #13 then
    RelocateComboBox
  else
    HideCombobox;

end;

procedure TBWControlStringGrid.DblClick;
begin
  inherited;
  RelocateComboBox;
end;

procedure TBWControlStringGrid.Click;
begin
  inherited;
  HideCombobox;
end;

procedure TBWControlStringGrid.RelocateComboBox;
begin
  fcombobox.boundsrect := CellRect(Selection.Left, Selection.Top);
  fcomboBox.Visible := TRUE;
  fcombobox.setfocus;
end;

procedure TBWControlStringGrid.HideCombobox;
begin
  fcomboBox.Visible := false;
end;

constructor TBWControlStringGrid.Create(AOWner: TComponent);
begin
  inherited Create(Aowner);
  fComboBox := TComboBox.Create(self);
  fComboBox.Parent := self;
  fComboBox.Visible := FALSE;
  Options := Options - [goRangeSelect];
end;

destructor TBWControlStringGrid.Destroy;
begin
  fComboBox.Destroy;
  inherited destroy;
end;

end.

This is great, but is just the skeleton, of course..

There needs to be some mechansim for getting the combo's text/selection into the cell, also for relaying the cells contents into the combo in the first place.

This can be done in the Hide and Relocate methods.

The whole thing can get unwieldy if you add a lot of get/set methods for updating the combos dropdownlist, etc, so making the Combo a Public property, rather than just a private field might help with that - the onus is then on the programmer to deal with the combo directly - it is unlikely, for instance, that the dropdownlist would be the same for each column.

Or two new events could be triggered - OnHide and OnRelocate
eg:

TComboVisibleChangeEvent = procedure(Sender: TObject; Row, Col: Longint; Combo:
  TComboBox; AllowVisibleChange: boolean) of object

fOnHide: TComboVisibleChangeEvent;
fOnRelocate: TComboVisibleChangeEvent;

etc.

This way the Combo would be make public when needed. When the Relocate fires, the dropdownlist could be repopulated, etc

Just ideas for whoever wants them!

2010. december 20., hétfő

Remove the border of a TPageControl


Problem/Question/Abstract:

How to remove the border of a TPageControl

Answer:

{TPageControlEx component
Copyright (c) 1998 Sigbjoern Revheim, Sigbjoern@mad.scientist.com
This component removes the border of the pagecontrol only if there are one ore more tabs.}

unit PageControlEx;

interface

uses
  Windows, Messages, Classes, CommCtrl, ComCtrls, Controls;

type
  TPageControlEx = class(TPageControl)
  private
    FThickFrame: Boolean;
    procedure SetThickFrame(const Value: Boolean);
  protected
    procedure WndProc(var Msg: TMessage); override;
    procedure CreateParams(var Params: TCreateParams); override;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property ThickFrame: Boolean read FThickFrame write SetThickFrame default true;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Extra', [TPageControlEx]);
end;

constructor TPageControlEx.Create(AOwner: TComponent);
begin
  inherited;
  FThickFrame := True;
  {DoubleBuffered := True;}
  ParentBackground := False;
end;

procedure TPageControlEx.CreateParams(var Params: TCreateParams);
begin
  inherited;
  {BorderWidth := 0;}
  {Params.Style := Params.Style or WS_POPUP;}
  ParentBackground := False;
end;

procedure TPageControlEx.SetThickFrame(const Value: Boolean);
begin
  if FThickFrame <> Value then
  begin
    FThickFrame := Value;
    RecreateWnd;
  end;
end;

procedure TPageControlEx.WndProc(var Msg: TMessage);
begin
  inherited WndProc(Msg);
  if not FThickFrame and (Msg.Msg = TCM_ADJUSTRECT) then
    with PRect(Msg.LParam)^ do
    begin
      Left := 0;
      Right := ClientWidth;
      Top := Top - 8;
      Bottom := ClientHeight;
    end;
end;

end.

2010. december 19., vasárnap

Least squares line fitting in Delphi


Problem/Question/Abstract:

Least squares line fitting in Delphi

Answer:

Example that finds least squares fit for y = Mx + c

procedure LeastSquares(X, Y: array of Extended; var M: Extended; var C: Extended);
var
  SumX, SumY, SumX2, SumXY: Extended;
  n, i: Integer;
begin
  if High(X) <> High(Y) then
    raise
      Exception.Create('LeastSquares() Error - Input X & Y arrays must be
                         of the same length');
  n := High(X) + 1;
  SumX := 0.0;
  SumY := 0.0;
  SumX2 := 0.0;
  SumXY := 0.0;
  for i := 0 to n - 1 do
  begin
    SumX := SumX + X[i];
    SumY := SumY + Y[i];
    SumX2 := SumX2 + (X[i] * X[i]);
    SumXY := SumXY + (X[i] * Y[i]);
  end;
  if (n * SumX2) = (SumX * SumX) then
    raise Exception.Create('LeastSquares() Error - X Values cannot all be the same');
  M := ((SumY * SumX2) - (SumX * SumXY)) / ((n * SumX2) - (SumX * SumX));
  C := ((n * SumXY) - (SumX * SumY)) / ((n * SumX2) - (SumX * SumX));
end;

2010. december 18., szombat

A Simple Property Editor (2)


Problem/Question/Abstract:

Sometimes, when you create components, you want to make it easier for the developers to use them. This is the time to develop a property editor.

Answer:

INTRODUCTION

In this article I will give you a short introduction to property editor development. This property editor developed here will simlpy allow you to edit string and TCaption properties in a better way, allowing you to add line breaks to strings.

There are two reasons for this property editor. First it is great to add line breaks into the labels caption, second it is fairly simple, therefore a good start for developing a property editor.

STEPS IN CREATING A PROPERTY EDITOR

First a short list of considerations when creating a property editor.

How should the property editor support the developer?
Which components/properties/data types should the editor support?
When do you have enough time to write it? :)

How do we support the developer?

Well, as I have written before, we will give the developer a simple way of adding line breaks to. The form we will create with the Delphi form designer, jsut as we do always. We add a public procedure to it, that will take the old value, load it into a memo field, show the form and return the either new value or the old if the user has not confirmed the changes.

Which components/properties/data are supported?

We will support all components and properties of the types string and TCaption.

DESIGING THE FORM

Start Delphi and close all open files. Create a new form and name it frmStringEditor. Add a memo field to the form and name it mmoStringProperty. Now we need to buttons, one for "OK" and one for "Cancel." Thats all for the design part. Make it fit "nicely." Add event handlers to the to button click procedures!

We will add one public procedure that will accomplish the form show and decide, whether the property is changed or not.

function Edit(var Data: string): Boolean;

The remaining code comes a little later.

CREATING THE PROPERTY EDITOR CLASS

All Property Editors have to be a descendend of the TPropertyEditor class. In our case we will descend from the TStringProperty class, that itself descends from the one previously named.

There are two function we need to override. GetAttributes to tell Delphi that we provide a dialog to manipulate the property. Edit is the function called when the developer calls for the property editor dialog.

TOurStringProperty = class(TStringProperty)
public
  function GetAttributes: TPropertyAttributes; override;
  procedure Edit; override;
end;

The remaining code comes a little later, too.

REGISTERING THE PROPERTY EDITOR

We will install the property editor just like we install components, therefore we have to provide the Register property. In the body we will add a call to the RegisterPropertyEditor function. This function takes four parameters.

Information about the property type handled by the editor
The component/control class this editor is for (nil for all)
The property this editor is for ('' for all)
The property editor class itself

AND NOW THE WHOLE CODE

I have placed this all into one unit developed on Delphi 5 and tested with Delphi 6 Evaluation version. You will need at least the Professional Editions to get it working. Earlier versions of Delphi should work just fine. Cannot test on them, sorry.

This unit assumes that you saved your form under the name of uStringEditor.

unit uStringEditor;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, TypInfo
{$IFNDEF VER140}
  , DsgnIntf
{$ELSE}
  , DesignEditors, DesignIntf
{$ENDIF}
  ;

type
  TfrmStringEditor = class(TForm)
    mmoStringProperty: TMemo;
    btnOK: TButton;
    btnCancel: TButton;
    procedure btnOKClick(Sender: TObject);
    procedure btnCancelClick(Sender: TObject);
  private
  public
    function Edit(var Data: string): Boolean;
  end;

  TOurStringProperty = class(TStringProperty)
  public
    function GetAttributes: TPropertyAttributes; override;
    procedure Edit; override;
  end;

procedure Register;

implementation

{$R *.DFM}

procedure Register;
begin
  RegisterPropertyEditor(TypeInfo(TCaption), nil, '', TOurStringProperty);
  RegisterPropertyEditor(TypeInfo(string), nil, '', TOurStringProperty);
end;

function EditConnectionString(
  Component: TComponent; PropInfo: PPropInfo
  ): Boolean;
var
  Str: string;
begin
  Result := False;
  with TfrmStringEditor.Create(Application) do
  try
    Caption := Format('%s.%s string editor', [Component.Name, PropInfo^.Name]);
    Str := GetStrProp(Component, PropInfo);
    if Edit(Str) then
    begin
      SetStrProp(Component, PropInfo, Str);
      Result := True;
    end;
  finally
    Free;
  end;
end;

{ TOurStringProperty }

procedure TOurStringProperty.Edit;
begin
  if EditConnectionString(GetComponent(0) as TComponent, GetPropInfo)
    then
    Modified;
end;

function TOurStringProperty.GetAttributes: TPropertyAttributes;
begin
  Result := [paDialog];
end;

{ TfrmStringEditor }

procedure TfrmStringEditor.btnCancelClick(Sender: TObject);
begin
  ModalResult := mrCancel;
end;

procedure TfrmStringEditor.btnOKClick(Sender: TObject);
begin
  ModalResult := mrOk;
end;

function TfrmStringEditor.Edit(var Data: string): Boolean;
begin
  mmoStringProperty.Text := Data;
  if ShowModal = mrOK then
  begin
    Result := Data <> mmoStringProperty.Text;
    Data := mmoStringProperty.Text;
  end
  else
  begin
    Result := False;
  end;
end;

end.

INSTALLING IT

Go to the menu Component | Install Component..., select the your file from the disk and press "OK." After compiling and saving the package you are finished. You may have to restart Delphi for the changes to take place.

2010. december 17., péntek

How to get BIOS information?


Problem/Question/Abstract:

This code shows you some information about your BIOS:

Answer:

procedure TForm1.FormCreate(Sender: TObject);
begin
  try
    Label1.Caption := string(PChar(Ptr($FE061))); // BIOS Name
    Label2.Caption := string(PChar(Ptr($FE091))); // Copyright
    Label3.Caption := string(PChar(Ptr($FFFF5))); // BIOS Date
    Label4.Caption := string(PChar(Ptr($FEC71))); // Serial Number
  except
    Label1.Caption := 'Unsupported';
    Label2.Caption := 'Unsupported';
    Label3.Caption := 'Unsupported';
    Label4.Caption := 'Unsupported';
  end;
end;

Hint!
This method doesn't work with NT based systems like Windows NT, 2000 and XP.

2010. december 16., csütörtök

View CPU debug info


Problem/Question/Abstract:

View CPU debug info

Answer:

Create this value in the registry:

HKEY_CURRENT_USER\Software\Borland\Delphi\2.0\Debugging\EnableCPU = "1"

Then start Delphi and you'll have a new menu item: View | CPU Window

2010. december 15., szerda

Find crosspoint of two lines


Problem/Question/Abstract:

How to calculate the crosspoint of two lines

Answer:

If you want to know if 2 Lines crossing each other, you can use this function below. The lines are given in with the beginning- and end-points. You will find the Coordinates of the Crosspoint in Cross, if the Function anwers True = we did found a crossing;

function GetCrossPointOfLines(pA1, pA2, pB1, pB2: TPoint; var Cross: TPoint): Boolean;
var
  h, i, j, k, l, m: Integer;
  o, p: Extended;
begin
  k := pB1.Y - pA1.Y;
  l := pA2.Y - pA1.Y;
  m := pB2.Y - pB1.Y;
  h := pA2.X - pA1.X;
  i := pB1.X - pA1.X;
  j := pB2.X - pB1.x;
  Result := false;
  if Abs(j * l - m * h) > 0 then
  begin
    p := (k * h - i * l) / (j * l - m * h);
    o := (k * j - i * m) / (j * l - m * h);
    if (o >= 0.0) and (o <= 1.0) and (p >= 0.0) and (p <= 1.0) then
    begin
      Cross.X := Round(pA1.X + o * h);
      Cross.Y := Round(pA1.Y + o * l);
      Result := True;
    end;
  end;
end;

2010. december 14., kedd

How to paint a translucent (not transparent) rectangle


Problem/Question/Abstract:

I want to have a background image and then draw a translucent, red rectangle over it. The effect would then be like looking through a piece of red glass. How to achieve that?

Answer:

There are a number of different techniques, which vary in the overall effect. A simple algorithm, that doesn't model specular reflection or refraction, is demonstrated by this code:


procedure DrawTransparentRectangle(Canvas: TCanvas; Rect: TRect;
  Color: TColor; Transparency: Integer);
var
  X: Integer;
  Y: Integer;
  C: TColor;
  R, G, B: Integer;
  RR, RG, RB: Integer;
begin
  RR := GetRValue(Color);
  RG := GetGValue(Color);
  RB := GetBValue(Color);
  for Y := Rect.Top to Rect.Bottom - 1 do
    for X := Rect.Left to Rect.Right - 1 do
    begin
      C := Canvas.Pixels[X, Y];
      R := Round(0.01 * (Transparency * GetRValue(C) + (100 - Transparency) * RR));
      G := Round(0.01 * (Transparency * GetGValue(C) + (100 - Transparency) * RG));
      B := Round(0.01 * (Transparency * GetBValue(C) + (100 - Transparency) * RB));
      Canvas.Pixels[X, Y] := RGB(R, G, B);
    end;
end;


This routine is meant to illustrate the principle; in reality, you'd use something other than the (very slow) Pixels property to access the individual pixels of the canvas. For example, if you were dealing with bitmaps, you could use the Scanline property.

The Transparency parameter ranges from 0 (completely opaque) to 100 (completely transparent). With this simple algorithm, transparency values greater than 50 work best. Note that this algorithm is non-physical. The results are not what you'd get with a real piece of colored glass.

2010. december 13., hétfő

Implement the equivalent of TForm.OnCreate for a TFrame


Problem/Question/Abstract:

I miss one thing in frames: The ability of performing some initialization code as I would do in a TForm.OnCreate. So what would be the equivalent for frames?

Answer:

You can override the constructor for example. An alternative is to override the SetParent method and do the initialization after calling the inherited method. This way the frame will have a parent and you can then do things that require a window handle without running into problems. I use a base frame class in my current project that has this feature build in. The relevant parts are given below. In descendents I just override the Initialize method.

{ ... }
type
  {The base class for frames}
  TPLC_BaseFrame = class(TFrame)
  protected
    procedure SetParent(aParent: TWinControl); override;
  public
    procedure Initialize; virtual;
    procedure UnInitialize; virtual;
    destructor Destroy; override;
  end;

procedure TPLC_BaseFrame.Initialize;
begin
  {Override as needed in descendents}
end;

procedure TPLC_BaseFrame.UnInitialize;
begin
  {Override as needed in descendents}
end;

procedure TPLC_BaseFrame.SetParent(aParent: TWinControl);
var
  oldparent: TWinControl;
begin
  oldparent := Parent;
  inherited;
  if (oldparent = nil) and (aParent <> nil) then
    Initialize;
end;

destructor TPLC_BaseFrame.Destroy;
begin
  Uninitialize;
  inherited;
end;

2010. december 12., vasárnap

Make a window system modal


Problem/Question/Abstract:

Make a window system modal

Answer:

You need to make a window system modal? The following function does the job (in 16bit Windows only):


SetSysModalWindow(Form1.handle);

2010. december 11., szombat

Make a single cell in a TStringGrid readonly


Problem/Question/Abstract:

How to make a single cell in a TStringGrid readonly

Answer:

You can use OnSetEditText event for that. Something like:

procedure TForm1.StringGrid1SetEditText(Sender: TObject; ACol, ARow: Integer; const
  Value: string);
begin
  if (ACol = 1) and (ARow = 1) then
    StringGrid1.Cells[ACol, ARow] := 'Read Only!';
end;

2010. december 10., péntek

Change the border color of a TPanel


Problem/Question/Abstract:

Can I change black line color of the TPanel border (BorderStyle = bsSingle) into i.e. blue line color? I tried to trap the WM_NCPAINT message and to draw over the border line, but it's not working. The border line color is still black.

Answer:

That color is the COLOR_WINDOWFRAME, so you probably do not want to change it in general. But the NC paint handler should work. Here's some sample code to draw a border in red:

{ ... }
type
  TMyPanel = class(TPanel)
  protected
    procedure WM_NCPaint(var Msg: TWMNCPaint); message WM_NCPaint;
  end;

procedure TMyPanel.WM_NCPaint(var Msg: TWMNCPaint);
var
  DC: HDC;
  OldBrush: HBRUSH;
  OldPen: HPEN;
begin
  DC := 0;
  OldBrush := 0;
  OldPen := 0;
  try
    {Must use a WindowDC or you can't draw outside the client area}
    DC := GetWindowDC(Handle);
    {Use a "clear" brush and an appropriately colored pen}
    OldBrush := SelectObject(DC, GetStockObject(NULL_BRUSH));
    Canvas.Pen.Color := clRed;
    OldPen := SelectObject(DC, Canvas.Pen.Handle);
    {Draw the border}
    Rectangle(DC, 0, 0, Width, Height);
    {Tell Windows you did it}
    Msg.Result := 0;
  finally
    {Clean up the mess you made}
    if DC <> 0 then
    begin
      if OldPen <> 0 then
        SelectObject(DC, OldPen);
      if OldBrush <> 0 then
        SelectObject(DC, OldBrush);
      ReleaseDC(Handle, DC);
    end;
  end;
end;

{Dynamic panel creation}

{ ... }
Panel := TMyPanel.Create(Self);
with Panel do
begin
  Parent := Self;
  Left := 10;
  Top := 10;
  {Don't try to do 3D borders or add beveling - keep it simple}
  BevelOuter := bvNone;
  BorderStyle := bsSingle;
  Ctl3d := False;
end;
{ ... }

2010. december 9., csütörtök

Clear a TStringGrid


Problem/Question/Abstract:

I have a form with a stringgrid on it. I use the grid to display some data regarding a certain part. How do I clear the grid, that still holds data from the first display, before I display data of a different part?

Answer:

You have to loop over the rows or cols or even cells (depends on how the grid is layed out):

with StringGrid1 do
begin
  perform(WM_SETREDRAW, 0, 0); {block visual updates}
  try
    for i := fixedRows to Rowcount - 1 do
      Rows[i].Clear;
  finally
    perform(WM_SETREDRAW, 1, 0);
    invalidate;
  end;
end;

Since this wipes complete rows it would not be suitable if you have a fixed column on the left that should be preserved, for example.

2010. december 8., szerda

Using code completion for assignments


Problem/Question/Abstract:

Using code completion for assignments

Answer:

Code Completion is not only good for macros, it can prompt you with a listbox of possible arguments for an assignment statement. To see Code Completion in action, have this piece of code in your OnCreate event handler:

var
  temp1: string;
  temp2: integer;
  temp3: string;
begin
  Form1.Caption :=

After typing the ":=", press [Ctrl][space]. In a moment, you'll see a list of several the variables, methods, and objects that are in scope, and potentially valid assignments.

Since the left side of the assignment (Caption) is a string, you will see temp1 and temp3, but not temp2.

Some of the choices will have an ellipsis (...) after them, indicating an object or record that contains compatible methods or fields for the assignment.

2010. december 7., kedd

How to parse TAB delimited text files


Problem/Question/Abstract:

How would I go about parsing TAB delimited text files? I'm having difficulty with the chr(9) character.

Answer:

Solve 1:

{ ... }
var
  t: Textfile;
  line: string;
  elements: TStringlist;
begin
  Assignfile(t, filename);
  Reset(t);
  try
    elements := TStringlist.Create;
    try
      while not Eof(t) do
      begin
        ReadLn(t, line);
        {The following ignores empty lines}
        if IScan(#9, line, 1) > 0 then
        begin
          elements.clear;
          SplitString(line, #9, elements);
          ProcessElements(elements); {you write this}
        end;
      end;
    finally
      elements.Free
    end;
  finally
    Closefile(t);
  end;

{Return the position of the first instance of ch in S after position fromPos, or 0 if ch was not found}

function IScan(ch: Char; const S: string; fromPos: Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := fromPos to Length(S) do
  begin
    if S[i] = ch then
    begin
      Result := i;
      Break;
    end;
  end;
end;

{Split the passed string into substrings at the position of the separator character and add the substrings to the passed list. The list is not cleared first!}

procedure SplitString(const S: string; separator: Char; substrings: TStrings);
var
  i, n: Integer;
begin
  if Assigned(substrings) and (Length(S) > 0) then
  begin
    i := 1;
    repeat
      n := IScan(separator, S, i);
      if n = 0 then
        n := Length(S) + 1;
      substrings.Add(Copy(S, i, n - i));
      i := n + 1;
    until
      i > Length(S);
  end;
end;


Solve 2:

procedure DelimitedListToStringList(const S: AnsiString; Delimiter: Char;
  List: TStrings; NullValue: AnsiString);
var
  iPos: Integer;
  Temp, Temp1: AnsiString;
begin
  if not Assigned(List) then
    Exit;
  List.Clear;
  Temp := S;
  iPos := Pos(Delimiter, S);
  while iPos > 0 do
  begin
    SetLength(Temp1, iPos - 1);
    Temp1 := Copy(Temp, 1, iPos - 1);
    if Temp1 = '' then
    begin
      SetLength(Temp1, Length(NullValue));
      Temp1 := NullValue;
    end;
    List.Add(Temp1);
    Delete(Temp, 1, iPos);
    iPos := Pos(Delimiter, Temp);
  end;
  if Temp > '' then
    List.Add(Temp);
end;

2010. december 6., hétfő

How to remove the client edge of a MDI parent form


Problem/Question/Abstract:

How to remove the client edge of a MDI parent form

Answer:

Apparently, in Delphi 4, the logic for the MDI client window edge was changed. If you have the source for Forms.pas, you can see the MDI client window procedure (TCustomForm.ClientWndProc) explicitly changes the client edge on a certain mysterious message ($3F) by calling ShowMDIClientEdge.

Unfortunately, simply replacing the client window procedure doesn't work well. I've finally been able to work out a hack, that replaces the client window procedure, and changes the form style on the fly. This makes sure the form's FormStyle property is not fsMDIForm when the client window procedure wants to call ShowMDIClientEdge, which it does only if the FormStyle property is fsMDIForm. When the FormStyle property changes, however, the window is destroyed (to be recreated when needed). To prevent this, I've overriden the DestroyWnd method.

The following unit is my MDI main form, displayed without a sunken edge:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    FUpdating: Boolean;
    OldWndProc: TFarProc;
    NewWndProc: Pointer;
    procedure ClientWndProc(var Message: TMessage);
  protected
    procedure DestroyWnd; override;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.ClientWndProc(var Message: TMessage);

  procedure DefProc;
  begin
    with Message do
      Result := CallWindowProc(OldWndProc, ClientHandle, Msg, wParam, lParam);
  end;

begin
  if Message.Msg = $3F then
  begin
    FUpdating := True;
    FormStyle := fsNormal;
    DefProc;
    FormStyle := fsMDIForm;
    FUpdating := False;
  end
  else
    DefProc;
end;

procedure TForm1.DestroyWnd;
begin
  if not FUpdating then
    inherited DestroyWnd;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  OldWndProc := Pointer(GetWindowLong(ClientHandle, GWL_WNDPROC));
  NewWndProc := MakeObjectInstance(ClientWndProc);
  SetWindowLong(ClientHandle, GWL_WNDPROC, Longint(NewWndProc));
  SetWindowLong(ClientHandle, GWL_EXSTYLE, GetWindowLong(ClientHandle,
    GWL_EXSTYLE) and not WS_EX_CLIENTEDGE);
  SetWindowPos(ClientHandle, 0, 0, 0, 0, 0, SWP_NOACTIVATE or SWP_NOMOVE
    or SWP_NOSIZE or SWP_NOZORDER or SWP_FRAMECHANGED);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Pointer(GetWindowLong(ClientHandle, GWL_WNDPROC)) = NewWndProc then
  begin
    SetWindowLong(ClientHandle, GWL_WNDPROC, LongInt(OldWndProc));
    FreeObjectInstance(NewWndProc);
  end;
end;

end.

2010. december 5., vasárnap

Closing Internet Explorer from Delphi (Second Way)


Problem/Question/Abstract:

Kill all opened Internet Explorer windows, second way

Answer:

procedure TForm1.Button1Click(Sender: TObject);
// RadikalQ3, www.q3.nu/trucomania
  procedure CierraInternetExplorer;
  var
    Mango: THandle;
  begin
    //Cerramos todas las ventanas del Internet Explorer:
    repeat
      Mango := FindWindow('CabinetWClass', nil);
      if Mango <> 0 then
        SendMessage(Mango, WM_NCDestroy, 0, 0);
    until (Mango = 0);

    repeat
      Mango := FindWindow('IEFrame', nil);
      if Mango <> 0 then
        SendMessage(Mango, WM_NCDestroy, 0, 0);
    until (Mango = 0);
  end;

end;

2010. december 4., szombat

How to limit MDI child form movement to the client area of the MDI parent form


Problem/Question/Abstract:

Is it possible to limit the MDI client form movement, so that the form cannot be moved outside the client area of the MDI form?

Answer:

Yes, you can handle the WM_WINDOWPOSCHANGING message in the child forms and modify the message parameters if needs be to keep the child fully visible. Of course this is a breach of the standard Windows behaviour.

private {in form declaration}

procedure WMWINDOWPOSCHANGING(var msg: TWMWINDOWPOSCHANGING);
  message WM_WINDOWPOSCHANGING;

procedure TForm1.WMWINDOWPOSCHANGING(var msg: TWMWINDOWPOSCHANGING);
var
  r: TRect;
begin
  with msg.Windowpos^ do
  begin
    if (flags and SWP_NOMOVE) = 0 then
    begin
      r := GetClientrect(Application.Mainform.handle, r);
      if x < 0 then
        x := 0;
      if y < 0 then
        y := 0;
      if (x + cx) > r.right then
        x := r.right - cx;
      if (y + cy) > r.bottom then
        y := r.bottom - cy;
    end;
    inherited;
  end;
end;

2010. december 3., péntek

How to create a TCheckBox with a transparent caption


Problem/Question/Abstract:

Does anyone know how to make the label on a TCheckbox transparent. Just like TLabel?

Answer:

In order to make a check box transparent, you should include the WS_EX_Transparent constant to the extended window style and try to draw caption on your own. Example:

{ ... }
type
  TMyCheckBox = class(TCheckBox)
  protected
    procedure CNDrawItem(var Message: TWMDrawItem); message CN_DRAWITEM;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure CreateWnd; override;
    procedure SetButtonStyle;
  end;

procedure TMyCheckBox.CNDrawItem(var Message: TWMDrawItem);
var
  XCanvas: TCanvas;
  XCaptionRect, XGlyphRect: TRect;

  procedure xxDrawBitMap(ACanvas: TCanvas);
  const
    xx_h = 13;
    xx_w = 13;
  var
    xxGlyph: TBitmap;
    xxX, xxY, xxStepY, xxStepX: integer;
  begin
    xxGlyph := TBitmap.Create;
    try
      xxGlyph.Handle := LoadBitmap(0, PChar(OBM_CHECKBOXES));
      xxY := XGlyphRect.Top + (XGlyphRect.Bottom - XGlyphRect.Top - xx_h) div 2;
      xxX := 2;
      xxStepX := 0;
      xxStepY := 0;
      case State of
        cbChecked: xxStepX := xxStepX + xx_w;
        cbGrayed: xxStepX := xxStepX + xx_w * 3;
      end;
      ACanvas.CopyRect(Rect(xxX, xxY, xxX + xx_w, xxY + xx_h), xxGlyph.Canvas,
        Rect(xxStepX, xxStepY, xx_w + xxStepX, xx_h + xxStepY));
    finally
      xxGlyph.Free;
    end;
  end;

  procedure xxDrawCaption;
  var
    xXFormat: longint;
  begin
    xXFormat := DT_VCENTER + DT_SINGLELINE + DT_LEFT;
    xXFormat := DrawTextBiDiModeFlags(xXFormat);
    DrawText(Message.DrawItemStruct.hDC, PChar(Caption), length(Caption),
      XCaptionRect, xXFormat);
  end;

begin
  XGlyphRect := Message.DrawItemStruct.rcItem;
  XGlyphRect.Right := 20;
  XCaptionRect := Message.DrawItemStruct.rcItem;
  XCaptionRect.Left := XGlyphRect.Right;
  XCanvas := TCanvas.Create;
  try
    XCanvas.Handle := Message.DrawItemStruct.hDC;
    XCanvas.Brush.Style := bsClear;
    xxDrawBitMap(XCanvas);
    xxDrawCaption;
  finally
    XCanvas.Free;
  end;
end;

procedure TMyCheckBox.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.ExStyle := Params.ExStyle or WS_EX_Transparent;
end;

procedure TMyCheckBox.CreateWnd;
begin
  inherited CreateWnd;
  SetButtonStyle;
end;

procedure TMyCheckBox.SetButtonStyle;
const
  BS_MASK = $000F;
var
  Style: Word;
begin
  if HandleAllocated then
  begin
    Style := BS_CHECKBOX or BS_OWNERDRAW;
    if GetWindowLong(Handle, GWL_STYLE) and BS_MASK <> Style then
      SendMessage(Handle, BM_SETSTYLE, Style, 1);
  end;
end;

2010. december 2., csütörtök

Modify the idapi.cfg settings through code


Problem/Question/Abstract:

Is there a way to change the IDAPI.CFG file from Delphi coding using the BDE API, since I wish to avoid having my users utilize the BDECFG.EXE utility?

Answer:

Here is a unit that is supposed to allow changing the config file:

unit CFGTOOL;

interface

uses
  SysUtils, Classes, DB, DbiProcs, DbiTypes, DbiErrs;

type
  TBDEConfig = class(TComponent)
  private
    FLocalShare: Boolean;
    FMinBufSize: Integer;
    FMaxBufSize: Integer;
    FSystemLangDriver: string;
    FParadoxLangDriver: string;
    FMaxFileHandles: Integer;
    FNetFileDir: string;
    FTableLevel: string;
    FBlockSize: Integer;
    FDefaultDriver: string;
    FStrictIntegrity: Boolean;
    FAutoODBC: Boolean;

    procedure Init;
    procedure SetLocalShare(Value: Boolean);
    procedure SetMinBufSize(Value: Integer);
    procedure SetMaxBufSize(Value: Integer);
    procedure SetSystemLangDriver(Value: string);
    procedure SetParadoxLangDriver(Value: string);
    procedure SetMaxFileHandles(Value: Integer);
    procedure SetNetFileDir(Value: string);
    procedure SetTableLevel(Value: string);
    procedure SetBlockSize(Value: Integer);
    procedure SetDefaultDriver(Value: string);
    procedure SetAutoODBC(Value: Boolean);
    procedure SetStrictIntegrity(Value: Boolean);
    procedure UpdateCFGFile(path, item, value: string);

  protected

  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property LocalShare: Boolean read FLocalShare write SetLocalShare;
    property MinBufSize: Integer read FMinBufSize write SetMinBufSize;
    property MaxBufSize: Integer read FMaxBufSize write SetMaxBufSize;
    property SystemLangDriver: string read FSystemLangDriver write
      SetSystemLangDriver;
    property ParadoxLangDriver: string read FParadoxLangDriver write
      SetParadoxLangDriver;
    property MaxFileHandles: Integer read FMaxFileHandles write SetMaxFileHandles;
    property NetFileDir: string read FNetFileDir write SetNetFileDir;
    property TableLevel: string read FTableLevel write SetTableLevel;
    property BlockSize: Integer read FBlockSize write SetBlockSize;
    property DefaultDriver: string read FDefaultDriver write SetDefaultDriver;
    property AutoODBC: Boolean read FAutoODBC write SetAutoODBC;
    property StrictIntegrity: Boolean read FStrictIntegrity write SetStrictIntegrity;

  end;

procedure Register;

implementation

function StrToBoolean(Value: string): Boolean;
begin
  if (UpperCase(Value) = 'TRUE') or (UpperCase(Value) = 'ON') or
    (UpperCase(Value) = 'YES') or (UpperCase(Value) = '.T.') then
    Result := True
  else
    Result := False;
end;

function BooleanToStr(Value: Boolean): string;
begin
  if Value then
    Result := 'TRUE'
  else
    Result := 'FALSE';
end;

procedure Register;
begin
  RegisterComponents('Data Access', [TBDEConfig]);
end;

procedure TBDEConfig.Init;
var
  h: hDBICur;
  pCfgDes: pCFGDesc;
  n, v: string;
begin
  Check(DbiOpenCfgInfoList(nil, dbiREADWRITE, cfgPersistent, '\SYSTEM\INIT', h));
  GetMem(pCfgDes, sizeof(CFGDesc));
  try
    FillChar(pCfgDes^, sizeof(CFGDesc), #0);
    while (DbiGetNextRecord(h, dbiWRITELOCK, pCfgDes, nil) = DBIERR_NONE) do
    begin
      n := StrPas(pCfgDes^.szNodeName);
      v := StrPas(pCfgDes^.szValue);
      if n = 'LOCAL SHARE' then
        FLocalShare := StrToBoolean(v)
      else if n = 'MINBUFSIZE' then
        FMinBufSize := StrToInt(v)
      else if n = 'MAXBUFSIZE' then
        FMaxBufSize := StrToInt(v)
      else if n = 'MAXFILEHANDLES' then
        FMaxFileHandles := StrToInt(v)
      else if n = 'LANGDRIVER' then
        FSystemLangDriver := v
      else if n = 'AUTO ODBC' then
        FAutoODBC := StrToBoolean(v)
      else if n = 'DEFAULT DRIVER' then
        FDefaultDriver := v;
    end;
    if (h <> nil) then
      DbiCloseCursor(h);
    Check(DbiOpenCfgInfoList(nil, dbiREADWRITE, cfgPersistent,
      '\DRIVERS\PARADOX\INIT', h));
    FillChar(pCfgDes^, sizeof(CFGDesc), #0);
    while (DbiGetNextRecord(h, dbiWRITELOCK, pCfgDes, nil) = DBIERR_NONE) do
    begin
      n := StrPas(pCfgDes^.szNodeName);
      v := StrPas(pCfgDes^.szValue);
      if n = 'NET DIR' then
        FNetFileDir := v
      else if n = 'LANGDRIVER' then
        FParadoxLangDriver := v;
    end;
    if (h <> nil) then
      DbiCloseCursor(h);
    Check(DbiOpenCfgInfoList(nil, dbiREADWRITE, cfgPersistent,
      '\DRIVERS\PARADOX\TABLE CREATE', h));
    FillChar(pCfgDes^, sizeof(CFGDesc), #0);
    while (DbiGetNextRecord(h, dbiWRITELOCK, pCfgDes, nil) = DBIERR_NONE) do
    begin
      n := StrPas(pCfgDes^.szNodeName);
      v := StrPas(pCfgDes^.szValue);
      if n = 'LEVEL' then
        FTableLevel := v
      else if n = 'BLOCK SIZE' then
        FBlockSize := StrToInt(v)
      else if n = 'STRICTINTEGRITY' then
        FStrictIntegrity := StrToBoolean(v);
    end;
  finally
    FreeMem(pCfgDes, sizeof(CFGDesc));
    if (h <> nil) then
      DbiCloseCursor(h);
  end;
end;

procedure TBDEConfig.SetLocalShare(Value: Boolean);
begin
  UpdateCfgFile('\SYSTEM\INIT', 'LOCAL SHARE', BooleanToStr(Value));
  FLocalShare := Value;
end;

procedure TBDEConfig.SetMinBufSize(Value: Integer);
begin
  UpdateCfgFile('\SYSTEM\INIT', 'MINBUFSIZE', IntToStr(Value));
  FMinBufSize := Value;
end;

procedure TBDEConfig.SetMaxBufSize(Value: Integer);
begin
  UpdateCfgFile('\SYSTEM\INIT', 'MAXBUFSIZE', IntToStr(Value));
  FMaxBufSize := Value;
end;

procedure TBDEConfig.SetSystemLangDriver(Value: string);
begin
  UpdateCfgFile('\SYSTEM\INIT', 'LANGDRIVER', Value);
  FSystemLangDriver := Value;
end;

procedure TBDEConfig.SetParadoxLangDriver(Value: string);
begin
  UpdateCfgFile('\DRIVERS\PARADOX\INIT', 'LANGDRIVER', Value);
  FParadoxLangDriver := Value;
end;

procedure TBDEConfig.SetMaxFileHandles(Value: Integer);
begin
  UpdateCfgFile('\SYSTEM\INIT', 'MAXFILEHANDLES', IntToStr(Value));
  FMaxFileHandles := Value;
end;

procedure TBDEConfig.SetNetFileDir(Value: string);
begin
  UpdateCfgFile('\DRIVERS\PARADOX\INIT', 'NET DIR', Value);
  FNetFileDir := Value;
end;

procedure TBDEConfig.SetTableLevel(Value: string);
begin
  UpdateCfgFile('\DRIVERS\PARADOX\TABLE CREATE', 'LEVEL', Value);
  FTableLevel := Value;
end;

procedure TBDEConfig.SetBlockSize(Value: Integer);
begin
  UpdateCfgFile('\DRIVERS\PARADOX\TABLE CREATE', 'BLOCK SIZE', IntToStr(Value));
  FBlockSize := Value;
end;

procedure TBDEConfig.SetStrictIntegrity(Value: Boolean);
begin
  UpdateCfgFile('\DRIVERS\PARADOX\TABLE CREATE', 'STRICTINTEGRITY',
    BooleanToStr(Value));
  FStrictIntegrity := Value;
end;

procedure TBDEConfig.SetDefaultDriver(Value: string);
begin
  UpdateCfgFile('\SYSTEM\INIT', 'DEFAULT DRIVER', Value);
  FDefaultDriver := Value;
end;

procedure TBDEConfig.SetAutoODBC(Value: Boolean);
begin
  UpdateCfgFile('\SYSTEM\INIT', 'AUTO ODBC', BooleanToStr(Value));
  FAutoODBC := Value;
end;

procedure TBDEConfig.UpdateCFGFile;
var
  h: hDbiCur;
  pCfgDes: pCFGDesc;
  pPath: array[0..127] of char;
begin
  StrPCopy(pPath, Path);
  Check(DbiOpenCfgInfoList(nil, dbiREADWRITE, cfgPersistent, pPath, h));
  GetMem(pCfgDes, sizeof(CFGDesc));
  try
    FillChar(pCfgDes^, sizeof(CFGDesc), #0);
    while (DbiGetNextRecord(h, dbiWRITELOCK, pCfgDes, nil) = DBIERR_NONE) do
    begin
      if StrPas(pCfgDes^.szNodeName) = item then
      begin
        StrPCopy(pCfgDes^.szValue, value);
        Check(DbiModifyRecord(h, pCfgDes, True));
      end;
    end;
  finally
    FreeMem(pCfgDes, sizeof(CFGDesc));
    if (h <> nil) then
      DbiCloseCursor(h);
  end;
end;

constructor TBDEConfig.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Init;
end;

destructor TBDEConfig.Destroy;
begin
  inherited Destroy;
end;

end.

2010. december 1., szerda

What is a DispInterface?

Problem/Question/Abstract:

What is a DispInterface?

Answer:

Short answer: it is a specification for an IDispatch interface. Long answer: The IDispatch interface is the basis of all automation. It has two methods that allow pointerless scripting languages to call methods by name, instead of using method pointers: GetIDsOfNames, and Invoke. GetIdsOfNames retrieves the numerical ID of a method with a given name (provided that the object implements another interface that has method with that name). Invoke uses the numerical ID of a method to call that method. The numerical ID of a method is called the DispID. For example, suppose you create an interface that looks like this:

IMyIntf = interface(IDispatch)
['{4D733280-C514-11D4-8481-A68F52CBDB56}']
procedure DoThis;
end;

and an object that implements it that looks like this:

TMyDispatchObj = class(TAutoObject, IMyIntf)
public
procedure DoThis;
end;

Delphi will call both GetIDSOfNames and Invoke for you - all you need to do is use a variant. Like this:

{ ... }
var
AVar: OleVariant;
{ ... }

{ ... }
AVar := CreateComObject(CLASS_TMyAutoObj) as IDispatch;
AVar.DoThis;
{ ... }

Every time you call a method of an variant referencing an IDispatch interface, GetIDsOfNames and Invoke are called for you behind the scenes. However, calling GetIdsOfNames for every method is quite slow. And since all it does is find a numerical ID for a given method name, it might be nice if you could look up that ID in advance and pass it to Invoke directly, rather than go through GetIdsOfNames every time. Enter the DispInterface:

IMyDispInterface = dispinterface
['{4D733284-C514-11D4-8481-A68F52CBDB56}']
procedure DoThis; dispid 1;
end;

This declaration tells Delphi the DispID of each method in an interface. So if you use a DispInterface variable, rather than a variant, Delphi can call Invoke directly using that, rather than go through GetIdsOfNames:

{ ... }
var
Disp: IDispatch;
Dispint: IMyDispInterface;
{ ... }
Disp := CreateComObject(CLASS_TMyAutoObj) as IDispatch;
Dispint := IMyDispInterface(Disp);
Dispint.DoThis;
{ ... }

A DispInterface is not a true interface. When you use a DispInterface, you are actually using the IDispatch interface of an object, just as you are when you use a variant. That's why you can cast an IDispatch interface directly to a dispinterface, as in the second line above. All you're doing here is telling the compiler that you already know what other methods a particular object will implement, and what DispID's can be used to invoke them with.