2007. július 31., kedd

Save and load a TTreeView to / from a stream


Problem/Question/Abstract:

I'd like to use the TreeView component for hierarchizing some data. For this, I need to add to every node an integer property which will refer to the primary key field of an associated database. Alas, I fear that the TreeView "SaveToFile" and "LoadFromFile" methods save/ load only the Text property! Then, how can I save and load this integer property (and by the way, the imageindex) for every node?

Answer:

By writing your own save and load routines. Untested! Don't forget to rewind the stream before loading when you test this code.

procedure SaveTreeviewToStream(tv: TTreeview; S: TStream);
var
  writer: TWriter;
  node: TTreeNode;
begin
  Assert(Assigned(tv));
  Assert(Assigned(S));
  writer := TWriter.Create(S, 4096);
  try
    node := tv.Items[0];
    writer.WriteListBegin;
    while node <> nil do
    begin
      writer.WriteInteger(node.level);
      writer.WriteString(node.Text);
      writer.WriteInteger(node.Imageindex);
      writer.WriteInteger(Integer(node.data));
      node := node.GetNext;
    end;
    writer.WriteListEnd;
    writer.FlushBuffer;
  finally
    writer.Free;
  end;
end;

procedure LoadTreeviewFromStream(tv: TTreeview; S: TStream);
var
  reader: TReader;
  node: TTreeNode;
  level: Integer;
begin
  Assert(Assigned(tv));
  Assert(Assigned(S));
  tv.Items.BeginUpdate;
  try
    tv.Items.Clear;
    reader := TReader.Create(S, 4096);
    try
      node := nil;
      reader.ReadListBegin;
      while not Reader.EndOfList do
      begin
        level := reader.ReadInteger;
        if node = nil then
          {create root node, ignore its level}
          node := tv.Items.Add(nil, '')
        else
        begin
          if level = node.level then
            node := tv.Items.Add(node, '')
          else if level > node.level then
            node := tv.Items.AddChild(node, '')
          else
          begin
            while Assigned(node) and (level < node.level) do
              node := node.Parent;
            node := tv.Items.Add(node, '');
          end;
        end;
        node.Text := Reader.ReadString;
        node.ImageIndex := Reader.ReadInteger;
        node.Data := Pointer(Reader.ReadInteger);
      end;
      reader.ReadListEnd;
    finally
      reader.Free;
    end;
  finally
    tv.items.Endupdate;
  end;
end;

I'd rather suggest to use the data pointer as a pointer to a real object, not as integer (in the SaveTreeviewToStream procedure). You could add more complex info inside this object, like type information, or even the data objects itself. Type information is essential if your treeview browses through different tables of your database.

2007. július 30., hétfő

Count the number of Mondays between two given dates


Problem/Question/Abstract:

How to count the number of Mondays between two given dates

Answer:

Solve 1:

function NumMondays(dt1, dt2: TDateTime): integer;
var
  Date1, Date2, DateSpan: integer;
  Weekday1, DaysInStub: integer;
  MondayInStub: Boolean;
begin
  {Make sure date 1 is smaller than date 2}
  Date1 := MinIntValue([Trunc(dt1), Trunc(dt2)]);
  Date2 := MaxIntValue([Trunc(dt1), Trunc(dt2)]);
  {First approximation: complete weeks}
  DateSpan := Date2 - Date1 + 1;
  result := DateSpan div 7;
  {Now check if there's a Monday in the stub}
  MondayInStub := false;
  DaysInStub := DateSpan mod 7;
  Weekday1 := DayOfWeek(Date1);
  case Weekday1 of
    {Sunday}
    1: MondayInStub := DaysInStub > 0;
    {Monday}
    2: MondayInStub := true; {Starts and ends with Monday}
    {Sunday}
    3..7: MondayInStub := (Weekday1 + DaysInStub > 9 {2+7});
  end;
  if MondayInStub then
    inc(result);
end;


Solve 2:

Something like this should do the trick. I included the variable setup and display of results from my little test so that it will be obvious what I did.

procedure TForm1.Button1Click(Sender: TObject);
var
  cnt: integer;
  StartDate, EndDate: TDate;
begin
  cnt := 0;
  StartDate := StrToDate('4/21/2003');
  EndDate := StrToDate('5/30/2003');
  {Actual Monday counting}
  repeat
    if DayOfWeek(StartDate) = 2 then {2 = Monday (Sun = 1 .. Sat = 7) }
      inc(cnt);
    StartDate := StartDate + 1;
  until
    StartDate = EndDate;
  label1.Caption := IntToStr(cnt);
end;

2007. július 29., vasárnap

How to test for resource depletion


Problem/Question/Abstract:

I have a process that, under some conditions, can deplete a machine's resources. Is there a way for my application to check how close it is to that point?

Answer:

unit Sysresources;

interface

uses
  Windows, Sysutils;

const
  GFSR_SYSTEMRESOURCES = 0;
  GFSR_GDIRESOURCES = 1;
  GFSR_USERRESOURCES = 2;

function GetSystemResources(typ: Word): Integer;

implementation

var
  hDll: HMODULE;
  pProc: function(typ: word): Integer stdcall;

function GetSystemResources(typ: word): Integer;
begin
  result := pProc(typ);
end;

function InternalGetSystemresources(typ: Word): Integer; stdcall;
begin
  result := -1;
end;

initialization
  pProc := InternalGetSystemresources;
  if Win32Platform <> VER_PLATFORM_WIN32_NT then
  begin
    hdll := LoadLibrary('rsrc32.dll');
    if hdll <> 0 then
    begin
      @pProc := getProcAddress(hdll, '_MyGetFreeSystemResources32@4');
      if @pProc = nil then
        pProc := InternalGetSystemresources;
    end;
  end;

finalization
  if hDLL <> 0 then
    FreeLibrary(hdll);
end.

2007. július 28., szombat

Right-align a menu item (2)


Problem/Question/Abstract:

I want to write a TMenu component where all items and sub menus are right justified.

Answer:

You can change the item's justification, if you add the MFT_RIGHTJUSTIFY constant to the item's type (fType member of the TMenuItemInfo structure). You can do it in the main menu's OnChange event handler. Here's an example:

procedure TForm1.MainMenu1Change(Sender: TObject; Source: TMenuItem; Rebuild:
  Boolean);
var
  XHandle: HMENU;
  XMenuItemInfo: TMenuItemInfo;
  XBuffer: array[0..79] of Char;
begin
  XHandle := TMainMenu(Sender).Handle;
  XMenuItemInfo.cbSize := 44;
  XMenuItemInfo.fMask := MIIM_TYPE;
  XMenuItemInfo.dwTypeData := XBuffer;
  XMenuItemInfo.cch := SizeOf(XBuffer);
  if GetMenuItemInfo(XHandle, 0, true, XMenuItemInfo) then
  begin
    XMenuItemInfo.fType := XMenuItemInfo.fType or MFT_RIGHTORDER or MFT_RIGHTJUSTIFY;
    XMenuItemInfo.fMask := MIIM_TYPE;
    SetMenuItemInfo(XHandle, 0, true, XMenuItemInfo);
  end;
end;

2007. július 27., péntek

Attach a TComboBox to the column of a TStringGrid


Problem/Question/Abstract:

How to attach a TComboBox to the column of a TStringGrid

Answer:

Solve 1:

Here is one way to do it, using a single combobox that moves from cell to cell as required.

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    StringGrid1: TStringGrid;
    ComboBox1: TComboBox;
    procedure ComboBox1Exit(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var
      CanSelect: Boolean);
  private
    { Private declarations }
    procedure CMDialogKey(var msg: TCMDialogKey); message CM_DIALOGKEY;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.CMDialogKey(var msg: TCMDialogKey);
begin
  if Activecontrol = ComboBox1 then
  begin
    if msg.CharCode = VK_TAB then
    begin
      {set focus back to the grid and pass the tab key to it}
      stringgrid1.setfocus;
      stringgrid1.perform(WM_KEYDOWN, msg.charcode, msg.keydata);
      {swallow this message}
      msg.result := 1;
      Exit;
    end;
  end;
  inherited;
end;

procedure TForm1.ComboBox1Exit(Sender: TObject);
begin
  with sender as TComboBox do
  begin
    hide;
    if itemindex >= 0 then
      with stringgrid1 do
        cells[col, row] := items[itemindex];
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ComboBox1.visible := false;
end;

procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
  var CanSelect: Boolean);
var
  R: TRect;
  org: TPoint;
begin
  with Sender as TStringGrid do
    if (ACol = 2) and (ARow >= FixedRows) then
    begin
      {entered the column associated to the combobox}
      {get grid out of selection mode}
      perform(WM_CANCELMODE, 0, 0);
      {position the control on top of the cell}
      R := CellRect(Acol, Arow);
      org := Self.ScreenToClient(ClientToScreen(R.topleft));
      with ComboBox1 do
      begin
        setbounds(org.X, org.Y, r.right - r.left, height);
        itemindex := Items.IndexOf(Cells[acol, arow]);
        Show;
        BringTofront;
        {focus the combobox and drop down the list}
        SetFocus;
        DroppedDown := true;
      end;
    end;
end;

end.


Solve 2:

unit GridCombo;

interface

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

type
  TFrmGridCombo = class(TForm)
    StringGrid1: TStringGrid;
    BtnSave: TButton;
    StringGrid2: TStringGrid;
    BtnLoad: TButton;
    procedure StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var
      CanSelect: Boolean);
  private
    FCBox: TComboBox;
    procedure ComboClick(Sender: TObject);
  public
    { Public declarations }
  end;

var
  FrmGridCombo: TFrmGridCombo;

implementation

{$R *.DFM}

procedure TFrmGridCombo.StringGrid1SelectCell(Sender: TObject; ACol,
  ARow: Integer; var CanSelect: Boolean);
var
  thisRect: TRect; {Notational clarity.}
begin
  if (ARow = 1) and (ACol <> 0) then
  begin
    if Assigned(FCBox) then
      FCBox.Free;
    FCBox := TComboBox.Create(self);
    FCBox.Parent := self;
    thisRect := StringGrid1.CellRect(ACol, ARow);
    FCBox.Left := thisRect.Left + StringGrid1.Left + 2;
    FCBox.Top := thisRect.Top + StringGrid1.Top + 2;
    FCBox.Width := (thisRect.Right - thisRect.Left);
    FCBox.Height := (thisRect.Bottom - thisRect.Top);
    FCBox.Items.LoadFromFile('File2.Txt');
    FCBox.SetFocus;
    FCBox.OnClick := ComboClick;
  end
  else if Assigned(FCBox) then
  begin
    FCBox.Free;
    FCBox := nil;
  end;
end;

procedure TFrmGridCombo.ComboClick(Sender: TObject);
begin
  if Sender is TComboBox then
    StringGrid1.Cells[StringGrid1.Col, StringGrid1.Row] := TComboBox(Sender).Text;
end;

end.

2007. július 26., csütörtök

How to convert an integer value to a Roman Numeral representation


Problem/Question/Abstract:

How to convert an integer value to a Roman Numeral representation

Answer:

Converts an integer value to a string containing a roman numeric code ("XVII"):

{Parameters:   - Num: Integer to convert.
Return Value:  - Roman numerical representation of the passed integer value.
History:  12/7/99 "Philippe Ranger" (PhilippeRanger@compuserve.com)}

function IntToRoman(num: Cardinal): string; {returns num in capital roman digits}
const
  Nvals = 13;
  vals: array[1..Nvals] of word = (1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900,
    1000);
  roms: array[1..Nvals] of string[2] = ('I', 'IV', 'V', 'IX', 'X', 'XL', 'L', 'XC',
    'C', 'CD', 'D', 'CM', 'M');
var
  b: 1..Nvals;
begin
  result := '';
  b := Nvals;
  while num > 0 do
  begin
    while vals[b] > num do
      dec(b);
    dec(num, vals[b]);
    result := result + roms[b]
  end;
end;

2007. július 25., szerda

How to keep your application focused at all times


Problem/Question/Abstract:

I want my application to keep focus at any time. So, if someone clicks another window, I want my application to retrieve back focus.

Answer:

Solve 1:

To get your application into the foreground in W98, ME, W2K and XP, instead of using SetForegroundWindow, try this:


procedure ShowMe;
var
  Th1, Th2: Cardinal;
begin
  Th1 := GetCurrentThreadId;
  Th2 := GetWindowThreadProcessId(GetForegroundWindow, nil);
  AttachThreadInput(Th2, Th1, true);
  try
    SetForegroundWindow(Application.Handle);
  finally
    AttachThreadInput(Th2, Th1, false);
  end;
end;


Solve 2:

As well as the SetForegroundWindow (if you are using Win9X and not WinNT/2000), you could trick the system that your application is a running screensaver. In this case it will not loose focus, for screensavers by design maintain focus.

{ ... }
var
  old: Bool;
begin
  {Make it a Screensaver}
  SystemParametersInfo(SPI_SCREENSAVERRUNNING, Word(True), @old, 0);

or

{Make it not a Screensaver}
SystemParametersInfo(SPI_SCREENSAVERRUNNING, Word(False), @old, 0);

2007. július 24., kedd

MS-SQL : connection is in use by another statement


Problem/Question/Abstract:

MS-SQL : connection is in use by another statement

Answer:

When porting a larger database application (130k LOC) that worked fine with Oracle and InterBase to MS-SQL (6.5), I frequently got the error message 'connection is in use by another statement'.

At first, creating a new TDatabase for each TTable/ TQuery seemed to be necessary.

Then I found what was 'wrong' (not really wrong.. :-)

To speed up some of my queries, I had set the property Unidirectional to true. Delphi creates for such queries only one cursor (versus two for bidirectional queries or TTables). After removing the assignments of Unidirectional := true the error message disappeared and everything worked fine.

The following code resulted in the exception 'connection is in use by another statement':

// dataBaseNameS : string  is the name of the alias (MS-SQL 6.5)
begin
  Query1 := TQuery.Create(Application);
  with Query1 do
  begin
    DatabaseName := dataBaseNameS;
    SQL.Text := 'SELECT * FROM ABLESTOP';
    // the exception disappears if the following is removed
    Unidirectional := True;
    Open;
  end;
  ShowMessage('ok')

  Table1 := TTable.Create(Self);
  with Table1 do
  begin
    DatabaseName := dataBaseNameS;
    TableName := 'COMPONENT_PLAN';
    UpdateMode := upWhereKeyOnly;
    Open
  end;

  Table1.Insert;
  Table1.FieldByName('PARTNO').AsString := IntToStr(GetTickCount);
  Table1.FieldByName('ID').AsString := 'WWxx';
  Table1.FieldByName('VERSION').AsInteger := 1;
  // the exception will occurr in the next statement:
  //     "Connection is in use by another statement"
  Table1.Post;
end;

2007. július 23., hétfő

Play Musical Notes via PC Speaker Class


Problem/Question/Abstract:

Play Musical Notes via PC Speaker Class

Answer:

This is a simple class that plays a formatted musical string. It is reminiscent of the old GWBASIC days whereby one could play a string of notes via the PC speaker. I know that WAV and MIDI files are available in todays technology, but sometimes one does not need all that overhead. The class is useful for certain types of alarms (specially if the user has his sound card volume muted) or simple "Cell Phone" like jingles. The trick of the matter in Delphi is that the standard DELPHI implementation of BEEP takes no arguments and has only one sound. However the WIN API BEEP() takes two arguments.

ie.

BOOL Beep(
          DWORD dwFreq,     // sound frequency, in hertz
          DWORD dwDuration  // sound duration, in milliseconds
         );


Parameters

dwFreq

   Windows NT:
   Specifies the frequency, in hertz, of the sound. This parameter   must be in the range 37 through 32,767 (0x25        through 0x7FFF).

   Windows 95:
   The parameter is ignored.

dwDuration

   Windows NT:
   Specifies the duration, in milliseconds, of the sound.

   Windows 95:
   The parameter is ignored.

As can be seen it appears that BEEP() is NOT supported on WIN95, but is OK from there upwards. (I have not tested it on WIN95, but assume you will just get a monotone ???? - anyone for comment)

It is easily called by prefixing the unit
ie. Windows.Beep(Freq,Duration)

The format of the "Music String" is a comma delimited (",<" terminated) string in the following formats. (The string is CASE-INSENSITIVE and [] means optional with defaults).

A..G[+ or -][0..5][/BEATS] and

@[/BEATS]

Where A..G   is the Note to be played.
      + or - is optional Sharp or Flat designator respectively. (default is normal NULL)
      0..5   is optional Octave range (default = 1)
      /BEATS is number of 100ms to hold the note (default = 1)

where @      is a musical pause
      /BEATS is the number of beats to pause for (default = 1)

where ,<     is the END OF STRING terminator.

Properties:
DefaultOctave                         : Used if no 0..5 designator specified in format. (System Default = 1)  
BetweenNotesPause         : Use to set number MS gap between notes (faster or slower default = 100ms)

Simple Example:

procedure TForm1.Button3Click(Sender: TObject);
var
  Organ: TMusicPlayer;
begin
  Organ := TMusicPlayer.Create;
  Organ.Play('A,C,C+,D/3,C,A,C,A,@,F,D/4,<');
  Organ.Play('A,A3/2,G4,G/3,@/2,D-0/4,<');
  Organ.Free;
end;

Any enhancements or additional ideas welcome. Happy jingeling.

unit Music;
interface

uses Windows, SysUtils;

// ===========================================================================
// Mike Heydon May 2002
// Simple Music Player Class Win98/2000 (Win95 not supported)
// Implements Notes A,A#/Bb,C,C#/Db,D,D#,Eb,E,F,F#/Gb,G,G#/Ab
// Caters for Octaves 0..5
// In Between Note Pause setable.
// Defailt Octave setable.
//
// Based on Frequency Matrix
//
//         Octave0   Octave1   Octave2   Octave3   Octave4   Octave5
// A       55.000    110.000   220.000   440.000   880.000   1760.000
// A#/Bb   58.270    116.541   233.082   466.164   932.328   1864.655
// B       61.735    123.471   246.942   493.883   987.767   1975.533
// C       65.406    130.813   261.626   523.251   1046.502  2093.005
// C#/Db   69.296    138.591   277.183   554.365   1108.731  2217.461
// D       73.416    146.832   293.665   587.330   1174.659  2349.318
// D#/Eb   77.782    155.563   311.127   622.254   1244.508  2489.016
// E       82.407    164.814   329.628   659.255   1318.510  2637.020
// F       87.307    174.614   349.228   698.456   1396.913  2793.826
// F#/Gb   92.499    184.997   369.994   739.989   1479.978  2959.955
// G       97.999    195.998   391.995   783.991   1567.982  3135.963
// G#/Ab   103.826   207.652   415.305   830.609   1661.219  3322.438
//
// @ = Pause
// < = End of Music String Marker
//
// ===========================================================================

type
  TOctaveNumber = 0..5;
  TNoteNumber = -1..11;

  TMusicPlayer = class(TObject)
  private
    Octave,
      FDefaultOctave: TOctaveNumber;
    NoteIdx: TNoteNumber;
    FBetweenNotesPause,
      Duration: integer;
  protected
    function ParseNextNote(var MS: string): boolean;
  public
    constructor Create;
    procedure Play(const MusicString: string);
    property DefaultOctave: TOctaveNumber read FDefaultOctave
      write FDefaultOctave;
    property BetweenNotesPause: integer read FBetweenNotesPause
      write FBetweenNotesPause;
  end;

  // ---------------------------------------------------------------------------
implementation

const
  MAXSTRING = 2048; // ASCIIZ String max length

  MHERTZ: array[0..11, 0..5] of integer = // Array of Note MHertz
  ((55, 110, 220, 440, 880, 1760), // A
    (58, 117, 233, 466, 932, 1865), // A+ B-
    (62, 123, 247, 494, 988, 1976), // B
    (65, 131, 262, 523, 1047, 2093), // C
    (69, 139, 277, 554, 1109, 2217), // C+ D-
    (73, 147, 294, 587, 1175, 2349), // D
    (78, 156, 311, 622, 1245, 2489), // D+ E-
    (82, 165, 330, 659, 1319, 2637), // E
    (87, 1745, 349, 698, 1397, 2794), // F
    (92, 185, 370, 740, 1480, 2960), // F+ G-
    (98, 196, 392, 784, 1568, 3136), // G
    (105, 208, 415, 831, 1661, 3322) // G+ A-
    );

  // =======================================
  // Create the object and set defaults
  // =======================================

constructor TMusicPlayer.Create;
begin
  FDefaultOctave := 1;
  FBetweenNotesPause := 100;
end;

// ===========================================================
// Parse the next note and set Octave,NoteIdx and Duration
// ===========================================================

function TMusicPlayer.ParseNextNote(var MS: string): boolean;
var
  NS: string; // Note String
  Beats,
    CommaPos: integer;
  Retvar: boolean;
begin
  Retvar := false; // Assume Error Condition
  Beats := 1;
  Duration := 0;
  NoteIdx := 0;
  Octave := FDefaultOctave;
  CommaPos := pos(',', MS);

  if (CommaPos > 0) then
  begin
    NS := trim(copy(MS, 1, CommaPos - 1)); // Next Note info
    MS := copy(MS, CommaPos + 1, MAXSTRING); // Remove note from music string

    if (length(NS) >= 1) and (NS[1] in ['@'..'G']) then
    begin
      Retvar := true; // Valid Note - set return type true

      // Resolve NoteIdx
      NoteIdx := byte(NS[1]) - 65; // Map 'A'..'G' into 0..11 or -1
      NS := copy(NS, 2, MAXSTRING); // Remove the Main Note ID

      // Handle the @ Pause first
      if NoteIdx = -1 then
      begin
        if (length(NS) >= 1) and (NS[1] = '/') then
          Beats := StrToIntDef(copy(NS, 2, MAXSTRING), 1);
        Sleep(100 * Beats);
        Retvar := false; // Nothing to play
        NS := ''; // Stop further processing
      end;

      // Resolve Sharp or Flast
      if (length(NS) >= 1) and (NS[1] in ['+', '-']) then
      begin
        if NS[1] = '+' then // # Sharp
          inc(NoteIdx)
        else if NS[1] = '-' then // b Flat
          dec(NoteIdx);

        if NoteIdx = -1 then
          NoteIdx := 11; // Roll A Flat to G Sharp
        NS := copy(NS, 2, MAXSTRING); // Remove Flat/Sharp ID
      end;

      // Resolve Octave Number - Default := FDefaultOctave
      if (length(NS) >= 1) and (NS[1] in ['0'..'5']) then
      begin
        Octave := byte(NS[1]) - 48; // map '0'..'5' to 0..5 decimal
        NS := copy(NS, 2, MAXSTRING); // Remove Octave Number
      end;

      // Resolve Number of Beats - Default = 1
      if (length(NS) >= 1) and (NS[1] = '/') then
        Beats := StrToIntDef(copy(NS, 2, MAXSTRING), 1);

      Duration := 100 * Beats;
    end;
  end
  else
    MS := ''; // Signal end of music string

  Result := Retvar;
end;

// ===================================
// Play the passed music string
// ===================================

procedure TMusicPlayer.Play(const MusicString: string);
var
  MS: string; // Music String
begin
  MS := trim(UpperCase(MusicString));

  while (MS <> '') do
  begin
    if ParseNextNote(MS) then
    begin
      Windows.Beep(MHERTZ[NoteIdx, Octave], Duration);
      Sleep(FBetweenNotesPause);
    end;
  end;
end;

end.

2007. július 22., vasárnap

How to display hints on the status bar


Problem/Question/Abstract:

How to display hints on the status bar

Answer:

The following unit demonstrates displaying the hint on the status bar:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    StatusBar1: TStatusBar;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure ApplicationHint(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.ApplicationHint(Sender: TObject);
begin
  StatusBar1.Panels[0].Text := GetLongHint(Application.Hint);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnHint := ApplicationHint;
end;

end.

2007. július 21., szombat

How to disable copying to clipboard from a TRichEdit


Problem/Question/Abstract:

I have been trying all day to keep a descendant of TRichEdit from copying/ cutting to the clipboard with the use of the [ctrl] +[c] and [ctrl] + [x] keyboard shortcuts with no luck at all.

Answer:

Try a different approach: to copy something there has to be a selection, otherwise there is nothing to copy. So attach a handler to the controls OnSelectionChange event. This handler contains the line:

with Sender as TRichEdit do
  SelLength := 0;

2007. július 20., péntek

How to load a main menu into a TTreeView


Problem/Question/Abstract:

How to load a main menu into a TTreeView

Answer:

procedure AddToTree(Menu: TMenuItem; Tree: TTreeView; PNode: TTreeNode);
var
  x: Integer;
  Node: TTreeNode;
begin
  Node := Tree.Items.AddChild(PNode, Menu.Caption);
  if Menu.Count > 0 then
  begin
    for x := 0 to Menu.Count - 1 do
    begin
      AddToTree(Menu.Items[x], Tree, Node);
    end;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  x: Integer;
begin
  TreeView1.Items.Clear;
  for x := 0 to MainMenu1.Items.Count - 1 do
  begin
    AddToTree(MainMenu1.Items[x], TreeView1, nil);
  end;
end;

2007. július 19., csütörtök

Delphi code window doesn't receive focus on hitting breakpoints


Problem/Question/Abstract:

We have one computer at work running Delphi 4 on Win98.
When we attempt to debug our D4 exe on this machine by setting breakpoints, the D4 code window becomes the topmost window, but DOES NOT receive focus!

Answer:

Check that your WIN.INI file contains the following:

[Compatibility95]
DELPHI32=0x00000002

2007. július 18., szerda

How to create an icon from a TBitmap


Problem/Question/Abstract:

How to create an icon from a TBitmap

Answer:

You must create two bitmaps, a mask bitmap (called the "AND" bitmap) and a image bitmap (called the XOR bitmap). You can pass the handles to the "AND" and "XOR" bitmaps to the Windows API function CreateIconIndirect() and use the returned icon handle in your application:

procedure TForm1.Button1Click(Sender: TObject);
var
  IconSizeX: Integer;
  IconSizeY: Integer;
  AndMask: TBitmap;
  XOrMask: TBitmap;
  IconInfo: TIconInfo;
  Icon: TIcon;
begin
  {Getting the icon size}
  IconSizeX := GetSystemMetrics(SM_CXICON);
  IconSizeY := GetSystemMetrics(SM_CYICON);
  {Creating the "And" mask}
  AndMask := TBitmap.Create;
  AndMask.Monochrome := true;
  AndMask.Width := IconSizeX;
  AndMask.Height := IconSizeY;
  {Drawing on the "And" mask}
  AndMask.Canvas.Brush.Color := clWhite;
  AndMask.Canvas.FillRect(Rect(0, 0, IconSizeX, IconSizeY));
  AndMask.Canvas.Brush.Color := clBlack;
  AndMask.Canvas.Ellipse(4, 4, IconSizeX - 4, IconSizeY - 4);
  {Test drawing}
  Form1.Canvas.Draw(IconSizeX * 2, IconSizeY, AndMask);
  {Creating the "XOR" mask}
  XOrMask := TBitmap.Create;
  XOrMask.Width := IconSizeX;
  XOrMask.Height := IconSizeY;
  {Drawing onto the "XOR" mask}
  XOrMask.Canvas.Brush.Color := ClBlack;
  XOrMask.Canvas.FillRect(Rect(0, 0, IconSizeX, IconSizeY));
  XOrMask.Canvas.Pen.Color := clRed;
  XOrMask.Canvas.Brush.Color := clRed;
  XOrMask.Canvas.Ellipse(4, 4, IconSizeX - 4, IconSizeY - 4);
  {Test drawing}
  Form1.Canvas.Draw(IconSizeX * 4, IconSizeY, XOrMask);
  {Create an icon}
  Icon := TIcon.Create;
  IconInfo.fIcon := true;
  IconInfo.xHotspot := 0;
  IconInfo.yHotspot := 0;
  IconInfo.hbmMask := AndMask.Handle;
  IconInfo.hbmColor := XOrMask.Handle;
  Icon.Handle := CreateIconIndirect(IconInfo);
  {Destroying temporary bitmaps}
  AndMask.Free;
  XOrMask.Free;
  {Test drawing}
  Form1.Canvas.Draw(IconSizeX * 6, IconSizeY, Icon);
  {Assigning the application's icon}
  Application.Icon := Icon;
  {Forcing a repaint}
  InvalidateRect(Application.Handle, nil, true);
  {Free the icon}
  Icon.Free;
end;

2007. július 17., kedd

Add an image to an Excel spreadsheet


Problem/Question/Abstract:

I try to embed an image (jpg or bmp) into a spreadsheet. It will go in the top left of the spreadsheet - like a letterhead, sort of. I've seen the methods that use late binding, but the code I'm modifying use the early binding object TExcelApplication (the instance is called ExcelApplication1). The following doesn't compile:

ExcelApplication1.ActiveSheet.Pictures.Insert('c:\translogo.bmp')

I get the error "undeclared identifier: Pictures". Any suggestions?

Answer:

If WS is your worksheet:

{ ... }
WS.Shapes.AddPicture('C:\Pictures\Small.Bmp', EmptyParam, EmptyParam, 10, 160,
  EmptyParam, EmptyParam);

or

{ ... }
var
  Pics: Excel2000.Pictures; {or whichever Excel}
  Pic: Excel2000.Picture;
  Pic: Excel2000.Shape;
  Left, Top: integer;
{ ... }
Pics := (WS.Pictures(EmptyParam, 0) as Pictures);
Pic := Pics.Insert('C:\Pictures\Small.Bmp', EmptyParam);
Pic.Top := WS.Range['D4', 'D4'].Top;
Pic.Left := WS.Range['D4', 'D4'].Left;
{ ... }

EmptyParam a special variant (declared in Variants.pas in D6+). However in later versions of Delphi some conversions cause problems. This should work:

uses
  OfficeXP;

{ ... }
WS.Shapes.AddPicture('H:\Pictures\Game\Hills.bmp', msoFalse, msoTrue, 10, 160, 100,
  100);

But you may have to use a TBitmap to find out how large the picture should be.

2007. július 16., hétfő

How to create an multiple colored Stringgrid ? / How to draw in a StringGrid-Cell ?


Problem/Question/Abstract:

The standard Delphi-StringGrid can only hold one color for all cells. How to create an multiple colored Stringgrid ?

Answer:

I's easier than you assumed. You must simply override the DrawCell and manuelly draw some data on the canvas of the Stringgrid-Cell .

Feel free to copy and reuse this sweet tiny component....

I hope this article is helpful for you ....

TBWStringGrid = class(TStringGrid)
private
protected
  procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
    AState: TGridDrawState); override;
public
  { just hold some data for each cell }
  CellColor: array of array of TColor;
  CellFontColor: array of array of TColor;
  CellData: array of array of REAL;
  procedure RebuildDynColorArray; // net gut !!!!
  procedure ResizeGrid(ColCount: Integer; RowCount: Integer; ClearAllFields: Boolean =
    TRUE);
  procedure ResetGridCellData;
  procedure ResetGrid;
  procedure UnselectAll;
published
end;

function InvertColor(Color: TColor): TColor;

{ TBWStringGrid }

//>Created at 05-Jul-2002 (14:12:19 ) by benjamin wittfoth
  procedure TBWStringGrid.DrawCell(ACol, ARow: Integer; ARect: TRect;
    AState: TGridDrawState);
  begin
    inherited;
    if CellColor[ACol, ARow] = clBlack then
      EXIT;
    with Canvas do
    begin
      if (gdSelected in AState) then
      begin // wenn selektiert -> INVERTIEREN
        Font.Color := InvertColor(CellFontColor[ACol, ARow]);
        Brush.Color := InvertColor(CellColor[ACol, ARow]);
      end
      else
      begin // Ansonsten nicht !
        Brush.Color := CellColor[ACol, ARow];
        Font.Color := CellFontColor[ACol, ARow];
      end;
      Brush.Style := bsSolid;
      FillRect(ARect);
      TextRect(ARect, ARect.left + 2, ARect.top + 2, Cells[ACol, ARow]);
    end;
  end;

  //>Created at 05-Jul-2002 (14:56:33 ) by benjamin wittfoth
  procedure TBWStringGrid.RebuildDynColorArray;
  begin
    SetLength(CellColor, ColCount, RowCount);
    SetLength(CEllFontColor, ColCount, RowCount);
    SetLength(CellData, ColCount, RowCount);
  end;
  //>Created at 10-Jul-2002 (08:11:25 ) by benjamin wittfoth
  procedure TBWStringGrid.ResizeGrid(ColCount: Integer; RowCount: Integer;
    ClearAllFields: Boolean = TRUE);
  begin
    Self.RowCount := RowCount;
    Self.ColCount := ColCount;
    RebuildDynColorArray;
    if ClearAllFields then
      ResetGrid;
  end;
  //>Created at 10-Jul-2002 (08:11:29 ) by benjamin wittfoth
  procedure TBWStringGrid.ResetGridCellData;
  var
    X, Y: Integer;
  begin
    for Y := 0 to RowCount - 1 do
      for X := 0 to ColCount - 1 do
        CellData[X, Y] := 0;
  end;
  //>Created at 09-Jul-2002 (16:54:43 ) by benjamin wittfoth
  procedure TBWStringGrid.ResetGrid;
  var
    X, Y: Integer;
  begin
    for Y := 0 to RowCount - 1 do
    begin
      for X := 0 to ColCount - 1 do
      begin
        CellData[X, Y] := 0;
        CellColor[X, Y] := clWhite;
        CellFontColor[X, Y] := clBlack;
        Cells[X, Y] := '';
      end;
    end;
  end;

  //>Created at 09-Jul-2002 (11:08:35 ) by benjamin wittfoth
  procedure TBWStringGrid.UnselectAll;
  var
    ARect: TGridRect;
  begin
    ARect.Left := 0;
    ARect.Top := 0;
    ARect.Right := 0;
    ARect.Bottom := 0;
    Selection := ARect;
  end;

  function InvertColor(Color: TColor): TColor;
  begin
    case Color of
      clAqua: RESULT := clTeal;
      clBlack: RESULT := clWhite;
      clBlue: RESULT := clMaroon;
      clDkGray: RESULT := clFuchsia;
      clFuchsia: RESULT := clDkGray;
      //    clGray      : RESULT:=clPurple;
      clGreen: RESULT := clRed;
      clLime: RESULT := clSilver; //clYellow;
      clLtGray: RESULT := clLime;
      clMaroon: RESULT := clOlive; //clBlue;
      clNavy: RESULT := clNavy;
      clOlive: RESULT := clMaroon; //clNavy;
      clPurple: RESULT := clGray;
      clRed: RESULT := clYellow; //clGreen;
      //    clSilver    : RESULT:=clLtGray;
      clTeal: RESULT := clAqua;
      clWhite: RESULT := clBlack;
      clYellow: RESULT := clRed; //clLime;
    end;
  end;

2007. július 15., vasárnap

How to call procedures by name using an array of records


Problem/Question/Abstract:

I have a unit that all it does is store SQL statement for me to load and right now I'm doing:

if ReportName = "Some_Report_Name" then
  LoadSomeReportNameSql;
else if ReportName = "Some_Other_Report" then
  LoadSomeOtherReportSql;

I have about 200 reports so far...would a case statment be faster? I would, of course, change the identifier for the report to a numeric identifier, rather than a string identifier. My concern is that there will begin to be a very noticable difference once I get up to the 500 or so reports.

Answer:

With that many reports, there are two better solutions than an if/ then or case statement.


Solve 1:

An array of records containing the report name and report procedure might be faster and easier to maintain. The list could be sorted on the report name, and a binary search algorithm could be used to quickly locate the correct report procedure to execute.

This method is not new, but works very well. It is not automagical, so the programmer has to do some typing. It could be improved in a myriad of ways, like array of const parameters, TVarRec results, action identifers and encapsulation in a class. The last could get hairy if you expect that class to serve objects of other classes as well, but it is possible.


unit NamedFunctions;

interface

const
  MaxFuncs = 3;
  MaxFuncName = 13;

type
  TFuncRange = 1..MaxFuncs;
  TNamedFunc = function(args: string): string;
  TFuncName = string[MaxFuncName];
  TFuncInfo = record
    Name: TFuncName;
    Func: TNamedFunc
  end; { TNamedFunc }

  TFuncList = array[TFuncRange] of TFuncInfo;
function XSqrt(args: string): string;
function XUpStr(args: string): string;
function XToggle(args: string): string;

const
  {This list must be sorted for the function to be found}
  FuncList: TFuncList = ((Name: 'xsqrt'; Func: XSqrt), (Name: 'xtoggle'; Func: XToggle), (Name: 'xupstr'; Func: XUpStr));

function ExecFunc(AName: TFuncName; args: string): string;

implementation

uses
  Dialogs, SysUtils;

function ExecFunc(AName: TFuncName; args: string): string;
{ Binary search is overkill for a small number of functions. }
var
  CompRes, i, j, m: integer;
  Found: boolean;
begin
  AName := LowerCase(AName);
  i := 1;
  j := MaxFuncs;
  m := (i + j) shr 1;
  Found := false;
  while not Found and (i <= j) do
  begin
    CompRes := AnsiCompareStr(AName, FuncList[m].Name);
    if CompRes < 0 then
      j := m - 1
    else if CompRes > 0 then
      i := m + 1
    else
      Found := true;
    if not Found then
      m := (i + j) shr 1
  end;
  if Found then
    Result := FuncList[m].Func(args)
  else
  begin
    Result := '';
    ShowMessage('Function ' + AName + ' not found in list')
  end;
end;

function XSqrt(args: string): string;
var
  value: real;
begin
  value := 0;
  try
    value := StrToFloat(args)
  except
    on EConvertError do
      ShowMessage(args + ' is not a valid real number (XStr)')
  end;
  if value >= 0 then
    Result := FloatToStr(sqrt(value))
  else
  begin
    Result := '0.0';
    ShowMessage('Negative number passed to XSqrt')
  end;
end;

function XUpStr(args: string): string;
begin
  Result := UpperCase(args)
end;

function XToggle(args: string): string;
{ Anything other than 'TRUE' or 'T' is assumed false. }
begin
  args := UpperCase(args);
  if (args = 'TRUE') or ((length(args) = 1) and (args = 'T')) then
    Result := 'FALSE'
  else
    Result := 'TRUE'
end;

end.


Solve 2:

Another way to go would be to use the GetProcAddress Win32 API function to locate the report procedure based on the report name. This way you could store the report names and report procedure names in a text file or database. (Tip: EXEs can export routines just like DLLs can. GetProcAddress only finds exported routine names). The code might look something like this (off the top of my head...):


unit MyReports;

interface

type
  TReportProcedure = procedure;

procedure LoadSomeReportNameSql;
procedure LoadSomeOtherReportSql;
procedure ExecuteReport(AReportName: string);

implementation

procedure ExecuteReport(AReportName: string);
var
  ReportProc: TReportProcedure;
  ProcPointer: TFarProc;
begin
  {Table contains two columns: "Report Name" and "Report Procedure".  Primary key is "Report Name"}
  try
    Table1.Open;
    if Table1.FindKey([AReportName]) then
    begin
      {Get the address of the exported report procedure}
      ProcPointer := GetProcAddress(HInstance, Table1.FieldByName('Report Procedure').AsString);
      if Assigned(ProcPointer) then
      begin
        ReportProcedure := TReportProcedure(ProcPointer);
        ReportProcedure;
      end;
    end;
  finally
    Table1.Close;
  end;
end;

procedure LoadSomeReportNameSql;
begin
end;

procedure LoadSomeOtherReportSql;
begin
end;

exports
  LoadSomeReportNameSql;
LoadSomeOtherReportSql;

end.

2007. július 14., szombat

How to create a Typing Simulation in an about box


Problem/Question/Abstract:

In my about box, I'd like (when the about box window is shown) to start "typing" something (maybe in a paintbox). I mean, instead of showing some labels with the information, the program could start typing it by itself , maybe reading the information from an invisible memo on the same form and start writing it on the paintbox. For example, it could start writing letters one by one and making them sentences, also leave the spaces required and also show a graphic " | " after anything written (like in the edit controls). Could someone show some code of doing that? To better explain, I'd like to simulate the "typing" like someone is typing on the screen, leaving spaces, writing pauses, semicolons , etc.

Answer:

A way would be to put a timer on the aboutbox, and every timer click draw add an extra character. It may be best to use a TImage and draw the chars to the image. That way they will stay without needing a redraw. Here are the basic steps:

Include the text to type in a memo.

On create move the memo off screen (make invisible), set variables of say ml=memo, line=0, sp=string, position=-1, Xpos:= 0, Ypos:= 0, tmpstr:= ''

On a timer do


begin
  if (sp = -1) then
  begin
    ypos := ypos + image1.canvas.textheight('A');
    xpos := 0;
    tmpstr := memo1.lines[ml];
    Inc(ml);
    sp := 1;
  end;
  Image1.Canvas.TextOut(xpos, ypos, tmpstr[sp]);
  Inc(sp)
    xpos := xpos + image1.canvas.textwidth('A');
  if (sp > Length(TmpStr)) then
    sp := -1;
end;

2007. július 13., péntek

How to create a flat TComboBox


Problem/Question/Abstract:

How to create a flat TComboBox

Answer:

{$IFDEF BCB}
{$OBJEXPORTALL ON}
{$ENDIF}
unit DebsFlatComboBox;

interface

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

type
  TDebsCustomFlatComboBox = class(TCustomComboBox)
  private
    FFlatButton: boolean;
    FOnChooseItem: TNotifyEvent;
    FOnCloseUp: TNotifyEvent;
    procedure SetFlatButton(const Value: boolean);
  protected
    procedure ChooseItem; virtual;
{$IFNDEF VER140}
    procedure CloseUp; virtual;
{$ENDIF}
    procedure CNCommand(var Message: TWMCommand); message CN_COMMAND;
    procedure DrawButton(const DC: HDC); virtual;
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
    property FlatButton: boolean read FFlatButton write SetFlatButton default False;
    property OnChooseItem: TNotifyEvent read FOnChooseItem write FOnChooseItem;
    property OnCloseUp: TNotifyEvent read FOnCloseUp write FOnCloseUp;
  end;

  TDebsFlatComboBox = class(TDebsCustomFlatComboBox)
  published
    property Style; {Must be published before Items}
    property Anchors;
{$IFDEF VER140}
    property AutoComplete;
    property AutoDropDown;
{$ENDIF}
    property BiDiMode;
    property CharCase;
    property Color;
    property Constraints;
    property Ctl3D;
    property DragCursor;
    property DragKind;
    property DragMode;
    property DropDownCount;
    property Enabled;
    property FlatButton;
    property Font;
    property ImeMode;
    property ImeName;
    property ItemHeight;
    property ItemIndex default -1;
    property MaxLength;
    property ParentBiDiMode;
    property ParentColor;
    property ParentCtl3D;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property ShowHint;
    property Sorted;
    property TabOrder;
    property TabStop;
    property Text;
    property Visible;
    property OnChange;
    property OnChooseItem;
    property OnClick;
    property OnCloseUp;
    property OnContextPopup;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnDrawItem;
    property OnDropDown;
    property OnEndDock;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMeasureItem;
    property OnStartDock;
    property OnStartDrag;
    property Items; {Must be published after OnMeasureItem}
  end;

procedure Register;

implementation

uses
  Graphics;

procedure Register;
begin
  RegisterComponents('Debs', [TDebsFlatComboBox]);
end;

{TDebsCustomFlatComboBox}

procedure TDebsCustomFlatComboBox.ChooseItem;
begin
  if Assigned(FOnChooseItem) then
    FOnChooseItem(Self);
end;

{$IFNDEF VER140}

procedure TDebsCustomFlatComboBox.CloseUp;
begin
  if Assigned(FOnCloseUp) then
    FOnCloseUp(Self);
end;
{$ENDIF}

procedure TDebsCustomFlatComboBox.CNCommand(var Message: TWMCommand);
begin
  case Message.NotifyCode of
    CBN_SELCHANGE:
      begin
        Text := Items[ItemIndex];
        Click;
        Change;
        ChooseItem;
      end;
    CBN_CLOSEUP:
      begin
        CloseUp;
        Invalidate;
      end;
  else
    inherited;
  end;
end;

procedure TDebsCustomFlatComboBox.DrawButton(const DC: HDC);
var
  BtnState: integer;
  BtnRect: TRect;
begin
  BtnRect := ClientRect;
  BtnRect.Left := BtnRect.Right - GetSystemMetrics(SM_CXVSCROLL) - 2;
  BtnState := DFCS_SCROLLDOWN;
  if DroppedDown then
    InflateRect(BtnRect, -1, -1) {Draw line inside button for recessed look}
  else if FFlatButton then
    BtnState := BtnState or DFCS_FLAT
  else
    BtnRect.Top := BtnRect.Top + 1; {Allow room for 3d highlight}
  if not Enabled then
    BtnState := BtnState or DFCS_INACTIVE;
  if DroppedDown then
    BtnState := BtnState or DFCS_PUSHED;
  DrawFrameControl(DC, BtnRect, DFC_SCROLL, BtnState);
end;

procedure TDebsCustomFlatComboBox.SetFlatButton(const Value: boolean);
begin
  FFlatButton := Value;
  Invalidate;
end;

procedure TDebsCustomFlatComboBox.WMPaint(var Message: TWMPaint);
var
  DC: HDC;
  DrawRect: TRect;
  PS: TPaintStruct;
begin
  if not Ctl3d then
  begin
    DC := Message.DC;
    if (DC = 0) then
      DC := BeginPaint(Handle, PS);
    try
      DrawRect := ClientRect;
      Brush.Color := clWindowFrame;
      FrameRect(DC, DrawRect, Brush.Handle);
      InflateRect(DrawRect, -1, -1);
      Brush.Color := Color;
      FillRect(DC, DrawRect, Brush.Handle);
      {Draw the borders and the button}
      if Style <> csSimple then
      begin
        DrawButton(DC);
        DrawRect.Right := DrawRect.Right - GetSystemMetrics(SM_CXVSCROLL) - 2;
      end;
      {Clip the region  to stop Windows painting over our work}
      InflateRect(DrawRect, -1, -1);
      IntersectClipRect(DC, DrawRect.Left, DrawRect.Top, DrawRect.Right, DrawRect.Bottom);
      {Now get Windows to fill in the combo text}
      PaintWindow(DC);
    finally
      if Message.DC = 0 then
        EndPaint(Handle, PS);
    end;
  end
  else
    inherited;
end;

end.

2007. július 12., csütörtök

Disable the close button of a TForm


Problem/Question/Abstract:

How to disable the close button of a TForm

Answer:

Solve 1:

procedure EnableCloseButton(const bEnable: Boolean);
const
  MenuFlags: array[Boolean] of Integer = (MF_DISABLED, MF_ENABLED);
var
  hSysMenu: HMENU;
begin
  hSysMenu := GetSystemMenu(Handle, False);
  if hSysMenu > 0 then
    EnableMenuItem(hSysMenu, SC_CLOSE, MF_BYCOMMAND or MenuFlags[bEnable]);
end;


Solve 2:

The usual approach is to disable or enable the corresponding item in the forms system menu. However, that does not work for all of them. You can always trap the WM_SYSCOMMAND message caused by clicking on the items and not pass it on, but this way the border icons do not appear disabled.

{ ... }
EnableMenuItem(GetSystemMenu(handle, False), SC_CLOSE, MF_BYCOMMAND or MF_GRAYED);        

That will disable the close box, for example.


Solve 3:

Remember that certain combinations will cause different results (ie. removing the system menu will also disable minimize/ maximize etc.)

procedure TForm1.Button1Click(Sender: TObject);
var
  Style: Integer;
begin
  Style := GetWindowLong(Handle, GWL_STYLE);
  {disable minimize}
  Style := Style - WS_MINIMIZEBOX;
  {disable maximize}
  Style := Style - WS_MAXIMIZEBOX;
  {remove system menu}
  Style := Style - WS_SYSMENU;
  {set new style}
  SetWindowLong(Handle, GWL_STYLE, Style);
  {repaint the title bar}
  RedrawWindow(Handle, nil, 0, RDW_FRAME or RDW_INVALIDATE);
end;

2007. július 11., szerda

Send files to the recycle bin


Problem/Question/Abstract:

How to send files to the recycle bin

Answer:

Solve 1:


unit Recycle;

interface

uses
  Windows, Messages, SysUtils, Classes, Controls, Forms, Dialogs, ShellAPI;

function RecycleFile(FileToRecycle: TFilename): boolean;
function RecycleFileEx(FileToRecycle: TFilename; Confirm: boolean): boolean;

implementation

function RecycleFile(FileToRecycle: TFilename): boolean;
begin
  Result := RecycleFileEx(FileToRecycle, True);
end;

function RecycleFileEx(FileToRecycle: TFilename; Confirm: boolean): boolean;
var
  Struct: TSHFileOpStruct;
  tmp: string;
  Resultval: integer;
begin
  tmp := FileToRecycle + #0#0;
  Struct.wnd := 0;
  Struct.wFunc := FO_DELETE;
  Struct.pFrom := PChar(tmp);
  Struct.pTo := nil;
  Struct.fFlags := FOF_ALLOWUNDO;
  if not Confirm then
    Struct.fFlags := Struct.fFlags or FOF_NOCONFIRMATION;
  Struct.fAnyOperationsAborted := false;
  Struct.hNameMappings := nil;
  try
    Resultval := ShFileOperation(Struct);
  except
    on e: Exception do
    begin
      e.Message := 'Tried to recycle file:' + FileToRecycle + #13#10 + e.Message;
      raise;
    end;
  end;
  Result := (Resultval = 0);
end;

end.


Solve 2:

uses ShellApi;

function DeleteFilesToRecycleBin(const APath: string): Boolean;
var
  AStruct: TShFileOpStruct;
begin
  if Length(APath) = 0 then
    Exit;
  AStruct.Wnd := 0;
  AStruct.wFunc := FO_DELETE;
  AStruct.pFrom := PChar(APath);
  AStruct.fFlags := FOF_ALLOWUNDO;
  Result := ShFileOperation(AStruct) <> 0;
end;


Solve 3:

function RecycleFile(FileToRecycle: string): boolean;
var
  Struct: TSHFileOpStruct;
  pFromc: PChar;
  Resultval: integer;
begin
  if not FileExists(FileToRecycle) then
  begin
    RecycleFile := False;
    exit;
  end
  else
  begin
    pfromc := PChar(ExpandFileName(FileToRecycle) + #0#0);
    Struct.wnd := 0;
    Struct.wFunc := FO_DELETE;
    Struct.pFrom := pFromC;
    Struct.pTo := nil;
    Struct.fFlags := FOF_ALLOWUNDO;
    Struct.fAnyOperationsAborted := false;
    Struct.hNameMappings := nil;
    Resultval := ShFileOperation(Struct);
    RecycleFile := (Resultval = 0);
  end;
end;

2007. július 10., kedd

Find the TTabSheet in a pagecontrol, an active control is sitting on


Problem/Question/Abstract:

How to find the TTabSheet in a pagecontrol, an active control is sitting on

Answer:

The forms ActiveControl property points to the active control on the form (if any, it may be Nil under certain circumstances). If you want to find the tabsheet in a pagecontrol, this controls is sitting on, you walk up its parent chain:

var
  ctrl: TWinControl;
begin
  if ActiveControl <> nil then
  begin
    ctrl := ActiveControl.Parent;
    while (ctrl <> nil) and not (ctrl is TTabsheet) do
      ctrl := ctrl.parent;
    if ctrl <> nil then
      {... found the tabsheet, can cast it as TTabsheet( ctrl ) to access
                        its properties}

2007. július 9., hétfő

Testing if two objects are "related" or "identical" (RTTI)


Problem/Question/Abstract:

Testing if two objects are "related" or "identical" (RTTI)

Answer:

Do you need to know whether object a is of a derived class from the class that another object b is of? Or if they may even be of the same class?

The following little code snippet tells it..


program dummy;

var
  a, b: TObject;

begin
  // some code to assign the pointers
  // ...

  // now evaluate the RTTI of two instantiated objects
  if a is b then
    ShowMessage('a is derived from b or same class');

  if a.classtype = b.classtype then
    ShowMessage('a and b are of the same class');

  // alternative to ClassType comparison (slower!)
  if a.ClassName = b.ClassName then
    ShowMessage('a and b are of the same class')
end.

2007. július 8., vasárnap

How to flip, rotate and mirror bitmaps


Problem/Question/Abstract:

How to flip, rotate and mirror bitmaps

Answer:

You'll have to manually set the bitmap's PixelFormat to pf8bit, pf16bit, pf24bit, or pf32bit before calling these routines. At the end of this page are three wrappers that will call the appropriate routine based on pixel format.

{ ... }
type
  TPixel8 = Byte;
  TPixel16 = Word;
  TPixel24 = packed record
    Blue: Byte;
    Green: Byte;
    Red: Byte;
  end;

  TPixel32 = LongWord;

function GetBitmapPixelSize(const Bitmap: TBitmap): Integer;
begin
  case Bitmap.PixelFormat of
    pf8Bit:
      Result := 1;
    pf16Bit:
      Result := 2;
    pf24Bit:
      Result := 3;
    pf32Bit:
      Result := 4;
  else
    Result := 0;
  end;
end;

function GetBitmapScanlineSize(const Bitmap: TBitmap): Integer;
var
  SL0: Pointer;
  SL1: Pointer;
begin
  if (Bitmap.Height > 1) then
  begin
    SL0 := Bitmap.Scanline[0];
    SL1 := Bitmap.Scanline[1];
    Result := LongInt(SL1) - LongInt(SL0);
  end
  else
    Result := 0;
end;

procedure FlipBitmap_8(const Bitmap: TBitmap);
type
  TPixel = TPixel8; {Dependent on Bitmap.PixelFormat}
  PPixel = ^TPixel;
var
  Buffer: TPixel;
  PPixel1: PPixel;
  PPixel2: PPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  RowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  RowSize := GetBitmapScanlineSize(Bitmap);
  PPixel1Start := Bitmap.Scanline[0];
  PPixel2Start := Bitmap.Scanline[Bitmap.Height - 1];
  for I := 1 to (Bitmap.Height div 2) do {Ignore middle row}
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to Bitmap.Width do
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Inc(PPixel2);
    end;
    Inc(LongInt(PPixel1Start), RowSize);
    Dec(LongInt(PPixel2Start), RowSize);
  end;
end;

procedure FlipBitmap_16(const Bitmap: TBitmap);
type
  TPixel = TPixel16; {Dependent on PixelFormat}
  PPixel = ^TPixel;
var
  Buffer: TPixel;
  PPixel1: PPixel;
  PPixel2: PPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  RowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  RowSize := GetBitmapScanlineSize(Bitmap);
  PPixel1Start := Bitmap.Scanline[0];
  PPixel2Start := Bitmap.Scanline[Bitmap.Height - 1];
  for I := 1 to (Bitmap.Height div 2) do {Ignore middle row}
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to Bitmap.Width do
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Inc(PPixel2);
    end;
    Inc(LongInt(PPixel1Start), RowSize);
    Dec(LongInt(PPixel2Start), RowSize);
  end;
end;

procedure FlipBitmap_24(const Bitmap: TBitmap);
type
  TPixel = TPixel24; {Dependent on PixelFormat}
  PPixel = ^TPixel;
var
  Buffer: TPixel;
  PPixel1: PPixel;
  PPixel2: PPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  RowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  RowSize := GetBitmapScanlineSize(Bitmap);
  PPixel1Start := Bitmap.Scanline[0];
  PPixel2Start := Bitmap.Scanline[Bitmap.Height - 1];
  for I := 1 to (Bitmap.Height div 2) do {Ignore middle row}
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to Bitmap.Width do
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Inc(PPixel2);
    end;
    Inc(LongInt(PPixel1Start), RowSize);
    Dec(LongInt(PPixel2Start), RowSize);
  end;
end;

procedure FlipBitmap_32(const Bitmap: TBitmap);
type
  TPixel = TPixel32; {Dependent on PixelFormat.}
  PPixel = ^TPixel;
var
  Buffer: TPixel;
  PPixel1: PPixel;
  PPixel2: PPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  RowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  RowSize := GetBitmapScanlineSize(Bitmap);
  PPixel1Start := Bitmap.Scanline[0];
  PPixel2Start := Bitmap.Scanline[Bitmap.Height - 1];
  for I := 1 to (Bitmap.Height div 2) do {Ignore middle row}
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to Bitmap.Width do
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Inc(PPixel2);
    end;
    Inc(LongInt(PPixel1Start), RowSize);
    Dec(LongInt(PPixel2Start), RowSize);
  end;
end;

procedure MirrorBitmap_8(const Bitmap: TBitmap);
type
  TPixel = TPixel8; {Dependent on PixelFormat}
  PPixel = ^TPixel;
var
  PPixel1: PPixel;
  PPixel2: PPixel;
  Buffer: TPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  RowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  RowSize := GetBitmapScanlineSize(Bitmap);
  PPixel1Start := Bitmap.Scanline[0];
  PPixel2Start := PPixel1Start;
  Inc(PPixel2Start, Bitmap.Width - 1);
  for I := 1 to Bitmap.Height do
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to (Bitmap.Width div 2) do {Ignore middle column}
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Dec(PPixel2);
    end;
    Inc(LongInt(PPixel1Start), RowSize);
    Inc(LongInt(PPixel2Start), RowSize);
  end;
end;

procedure MirrorBitmap_16(const Bitmap: TBitmap);
type
  TPixel = TPixel16; {Dependent on PixelFormat}
  PPixel = ^TPixel;
var
  PPixel1: PPixel;
  PPixel2: PPixel;
  Buffer: TPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  RowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  RowSize := GetBitmapScanlineSize(Bitmap);
  PPixel1Start := Bitmap.Scanline[0];
  PPixel2Start := PPixel1Start;
  Inc(PPixel2Start, Bitmap.Width - 1);
  for I := 1 to Bitmap.Height do
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to (Bitmap.Width div 2) do {Ignore middle column}
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Dec(PPixel2);
    end;
    Inc(LongInt(PPixel1Start), RowSize);
    Inc(LongInt(PPixel2Start), RowSize);
  end;
end;

procedure MirrorBitmap_24(const Bitmap: TBitmap);
type
  TPixel = TPixel24; {Dependent on PixelFormat}
  PPixel = ^TPixel;
var
  PPixel1: PPixel;
  PPixel2: PPixel;
  Buffer: TPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  RowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  RowSize := GetBitmapScanlineSize(Bitmap);
  PPixel1Start := Bitmap.Scanline[0];
  PPixel2Start := PPixel1Start;
  Inc(PPixel2Start, Bitmap.Width - 1);
  for I := 1 to Bitmap.Height do
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to (Bitmap.Width div 2) do {Ignore middle column}
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Dec(PPixel2);
    end;
    Inc(LongInt(PPixel1Start), RowSize);
    Inc(LongInt(PPixel2Start), RowSize);
  end;
end;

procedure MirrorBitmap_32(const Bitmap: TBitmap);
type
  TPixel = TPixel32; {Dependent on PixelFormat}
  PPixel = ^TPixel;
var
  PPixel1: PPixel;
  PPixel2: PPixel;
  Buffer: TPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  RowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  RowSize := GetBitmapScanlineSize(Bitmap);
  PPixel1Start := Bitmap.Scanline[0];
  PPixel2Start := PPixel1Start;
  Inc(PPixel2Start, Bitmap.Width - 1);
  for I := 1 to Bitmap.Height do
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to (Bitmap.Width div 2) do {Ignore middle column}
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Dec(PPixel2);
    end;
    Inc(LongInt(PPixel1Start), RowSize);
    Inc(LongInt(PPixel2Start), RowSize);
  end;
end;

procedure RotateBitmap180_8(const Bitmap: TBitmap);
type
  TPixel = TPixel8; {Dependent on PixelFormat}
  PPixel = ^TPixel;
var
  PPixel1: PPixel;
  PPixel2: PPixel;
  Buffer: TPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  RowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  RowSize := GetBitmapScanlineSize(Bitmap);
  PPixel1Start := Bitmap.Scanline[0];
  PPixel2Start := Bitmap.Scanline[Bitmap.Height - 1];
  Inc(PPixel2Start, Bitmap.Width - 1);
  for I := 1 to (Bitmap.Height div 2) do {Ignore center row for now}
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to Bitmap.Width do
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Dec(PPixel2);
    end;
    Inc(LongInt(PPixel1Start), RowSize);
    Dec(LongInt(PPixel2Start), RowSize);
  end;
  if (Odd(Bitmap.Height)) then {Process center row}
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to (Bitmap.Width div 2) do {Ignore center pixel}
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Dec(PPixel2);
    end;
  end;
end;

procedure RotateBitmap180_16(const Bitmap: TBitmap);
type
  TPixel = TPixel16; {Dependent on PixelFormat}
  PPixel = ^TPixel;
var
  PPixel1: PPixel;
  PPixel2: PPixel;
  Buffer: TPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  RowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  RowSize := GetBitmapScanlineSize(Bitmap);
  PPixel1Start := Bitmap.Scanline[0];
  PPixel2Start := Bitmap.Scanline[Bitmap.Height - 1];
  Inc(PPixel2Start, Bitmap.Width - 1);
  for I := 1 to (Bitmap.Height div 2) do {Ignore center row for now}
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to Bitmap.Width do
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Dec(PPixel2);
    end;
    Inc(LongInt(PPixel1Start), RowSize);
    Dec(LongInt(PPixel2Start), RowSize);
  end;
  if (Odd(Bitmap.Height)) then {Process center row}
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to (Bitmap.Width div 2) do {Ignore center pixel}
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Dec(PPixel2);
    end;
  end;
end;

procedure RotateBitmap180_24(const Bitmap: TBitmap);
type
  TPixel = TPixel24; {Dependent on PixelFormat}
  PPixel = ^TPixel;
var
  PPixel1: PPixel;
  PPixel2: PPixel;
  Buffer: TPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  RowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  RowSize := GetBitmapScanlineSize(Bitmap);
  PPixel1Start := Bitmap.Scanline[0];
  PPixel2Start := Bitmap.Scanline[Bitmap.Height - 1];
  Inc(PPixel2Start, Bitmap.Width - 1);
  for I := 1 to (Bitmap.Height div 2) do {Ignore center row for now}
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to Bitmap.Width do
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Dec(PPixel2);
    end;
    Inc(LongInt(PPixel1Start), RowSize);
    Dec(LongInt(PPixel2Start), RowSize);
  end;
  if (Odd(Bitmap.Height)) then {Process center row}
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to (Bitmap.Width div 2) do {Ignore center pixel}
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Dec(PPixel2);
    end;
  end;
end;

procedure RotateBitmap180_32(const Bitmap: TBitmap);
type
  TPixel = TPixel32; {Dependent on PixelFormat}
  PPixel = ^TPixel;
var
  PPixel1: PPixel;
  PPixel2: PPixel;
  Buffer: TPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  RowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  RowSize := GetBitmapScanlineSize(Bitmap);
  PPixel1Start := Bitmap.Scanline[0];
  PPixel2Start := Bitmap.Scanline[Bitmap.Height - 1];
  Inc(PPixel2Start, Bitmap.Width - 1);
  for I := 1 to (Bitmap.Height div 2) do {Ignore center row for now}
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to Bitmap.Width do
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Dec(PPixel2);
    end;
    Inc(LongInt(PPixel1Start), RowSize);
    Dec(LongInt(PPixel2Start), RowSize);
  end;
  if (Odd(Bitmap.Height)) then {Process center row}
  begin
    PPixel1 := PPixel1Start;
    PPixel2 := PPixel2Start;
    for J := 1 to (Bitmap.Width div 2) do {Ignore center pixel}
    begin
      Buffer := PPixel1^;
      PPixel1^ := PPixel2^;
      PPixel2^ := Buffer;
      Inc(PPixel1);
      Dec(PPixel2);
    end;
  end;
end;

procedure RotateBitmapCW_32(const Bitmap: TBitmap);
type
  TPixel = TPixel32; {Dependent on Bitmap.PixelFormat}
  PPixel = ^TPixel;
var
  NewBitmap: TBitmap;
  PPixel1: PPixel;
  PPixel2: PPixel;
  PPixel1Start: PPixel;
  PPixel2Start: PPixel;
  I: Integer;
  J: Integer;
  BitmapRowSize: Integer;
  NewBitmapRowSize: Integer;
begin
  Assert(GetBitmapPixelSize(Bitmap) = SizeOf(TPixel));
  Assert(Bitmap.Height > 0);
  Assert(Bitmap.Width > 0);
  NewBitmap := TBitmap.Create;
  try
    NewBitmap.PixelFormat := Bitmap.PixelFormat;
    NewBitmap.Height := Bitmap.Width;
    NewBitmap.Width := Bitmap.Height;
    BitmapRowSize := GetBitmapScanlineSize(Bitmap);
    NewBitmapRowSize := GetBitmapScanlineSize(NewBitmap);
    PPixel1Start := Bitmap.Scanline[0];
    PPixel2Start := NewBitmap.Scanline[0];
    Inc(PPixel2Start, NewBitmap.Width - 1);
    for I := 0 to (Bitmap.Height - 1) do
    begin
      PPixel1 := PPixel1Start;
      PPixel2 := PPixel2Start;
      for J := 0 to (Bitmap.Width - 1) do
      begin
        PPixel2^ := PPixel1^;
        Inc(PPixel1);
        Inc(Integer(PPixel2), NewBitmapRowSize);
      end;
      Inc(Integer(PPixel1Start), BitmapRowSize);
      Dec(PPixel2Start);
    end;
    Bitmap.Assign(NewBitmap);
  finally
    NewBitmap.Free;
  end;
end;

procedure FlipBitmap(const Bitmap: TBitmap);
begin
  case Bitmap.PixelFormat of
    pf8Bit:
      FlipBitmap_8(Bitmap);
    pf16Bit:
      FlipBitmap_16(Bitmap);
    pf24Bit:
      FlipBitmap_24(Bitmap);
    pf32Bit:
      FlipBitmap_32(Bitmap);
  else
    Assert(False);
  end;
end;

procedure MirrorBitmap(const Bitmap: TBitmap);
begin
  case Bitmap.PixelFormat of
    pf8Bit:
      MirrorBitmap_8(Bitmap);
    pf16Bit:
      MirrorBitmap_16(Bitmap);
    pf24Bit:
      MirrorBitmap_24(Bitmap);
    pf32Bit:
      MirrorBitmap_32(Bitmap);
  else
    Assert(False);
  end;
end;

procedure RotateBitmap180(const Bitmap: TBitmap);
begin
  case Bitmap.PixelFormat of
    pf8Bit:
      RotateBitmap180_8(Bitmap);
    pf16Bit:
      RotateBitmap180_16(Bitmap);
    pf24Bit:
      RotateBitmap180_24(Bitmap);
    pf32Bit:
      RotateBitmap180_32(Bitmap);
  else
    Assert(False);
  end;
end;

procedure RotateBitmapCW(const Bitmap: TBitmap);
begin
  case Bitmap.PixelFormat of
    pf8Bit:
      ;
    pf16Bit:
      ;
    pf24Bit:
      ;
    pf32Bit:
      RotateBitmapCW_32(Bitmap);
  else
    Assert(False);
  end;
end;

procedure RotateBitmapCCW(const Bitmap: TBitmap);
begin
  case Bitmap.PixelFormat of
    pf8Bit:
      ;
    pf16Bit:
      ;
    pf24Bit:
      ;
    pf32Bit:
      ;
  else
    Assert(False);
  end;
end;

2007. július 7., szombat

How can i write a TColor to a TInifile


Problem/Question/Abstract:

How can i write a TColor to a TInifile?

Answer:

To save the color to an INI, simply call IntToStr on the color (since TColor is an Integer) and then write the value as you would any other string.
TIniFile.WriteInteger(....), no need to convert to a string.


var
  t: TIniFile;
  c: color;
begin
  t := Tinifile.create('test.ini');
  //to write it
  t.writeinteger('section', 'identifier', integer(c));
  //to read it
  c := TColor(t.readinteger('section', 'identifier', clblack));
end;

2007. július 6., péntek

How to save object property data to a stream


Problem/Question/Abstract:

How can I save properties of a TList to a stream? I need the entire list to be saved as a whole and not as individual objects.

Answer:

Solve 1:

A TList doesn't have any intrinsic streaming capability built into it, but it is very easy to stream anything that you want with a little elbow grease. Think about it: a stream is data. Classes have properties, whose values are data. It isn't too hard to write property data to a stream. Here's a simple example to get you going. This is but just one of many possible approaches to saving object property data to a stream:

unit uStreamableExample;

interface

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

type
  TStreamableObject = class(TPersistent)
  protected
    function ReadString(Stream: TStream): string;
    function ReadLongInt(Stream: TStream): LongInt;
    function ReadDateTime(Stream: TStream): TDateTime;
    function ReadCurrency(Stream: TStream): Currency;
    function ReadClassName(Stream: TStream): ShortString;
    procedure WriteString(Stream: TStream; const Value: string);
    procedure WriteLongInt(Stream: TStream; const Value: LongInt);
    procedure WriteDateTime(Stream: TStream; const Value: TDateTime);
    procedure WriteCurrency(Stream: TStream; const Value: Currency);
    procedure WriteClassName(Stream: TStream; const Value: ShortString);
  public
    constructor CreateFromStream(Stream: TStream);
    procedure LoadFromStream(Stream: TStream); virtual; abstract;
    procedure SaveToStream(Stream: TStream); virtual; abstract;
  end;

  TStreamableObjectClass = class of TStreamableObject;

  TPerson = class(TStreamableObject)
  private
    FName: string;
    FBirthDate: TDateTime;
  public
    constructor Create(const AName: string; ABirthDate: TDateTime);
    procedure LoadFromStream(Stream: TStream); override;
    procedure SaveToStream(Stream: TStream); override;
    property Name: string read FName write FName;
    property BirthDate: TDateTime read FBirthDate write FBirthDate;
  end;

  TCompany = class(TStreamableObject)
  private
    FName: string;
    FRevenues: Currency;
    FEmployeeCount: LongInt;
  public
    constructor Create(const AName: string; ARevenues: Currency; AEmployeeCount:
      LongInt);
    procedure LoadFromStream(Stream: TStream); override;
    procedure SaveToStream(Stream: TStream); override;
    property Name: string read FName write FName;
    property Revenues: Currency read FRevenues write FRevenues;
    property EmployeeCount: LongInt read FEmployeeCount write FEmployeeCount;
  end;

  TStreamableList = class(TStreamableObject)
  private
    FItems: TObjectList;
    function Get_Count: LongInt;
    function Get_Objects(Index: LongInt): TStreamableObject;
  public
    constructor Create;
    destructor Destroy; override;
    function FindClass(const AClassName: string): TStreamableObjectClass;
    procedure Add(Item: TStreamableObject);
    procedure Delete(Index: LongInt);
    procedure Clear;
    procedure LoadFromStream(Stream: TStream); override;
    procedure SaveToStream(Stream: TStream); override;
    property Objects[Index: LongInt]: TStreamableObject read Get_Objects; default;
    property Count: LongInt read Get_Count;
  end;

  TForm1 = class(TForm)
    SaveButton: TButton;
    LoadButton: TButton;
    procedure SaveButtonClick(Sender: TObject);
    procedure LoadButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    Path: string;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

resourcestring
  DEFAULT_FILENAME = 'test.dat';

procedure TForm1.SaveButtonClick(Sender: TObject);
var
  List: TStreamableList;
  Stream: TStream;
begin
  List := TStreamableList.Create;
  try
    List.Add(TPerson.Create('Rick Rogers', StrToDate('05/20/68')));
    List.Add(TCompany.Create('Fenestra', 1000000, 7));
    Stream := TFileStream.Create(Path + DEFAULT_FILENAME, fmCreate);
    try
      List.SaveToStream(Stream);
    finally
      Stream.Free;
    end;
  finally
    List.Free;
  end;
end;

{ TPerson }

constructor TPerson.Create(const AName: string; ABirthDate: TDateTime);
begin
  inherited Create;
  FName := AName;
  FBirthDate := ABirthDate;
end;

procedure TPerson.LoadFromStream(Stream: TStream);
begin
  FName := ReadString(Stream);
  FBirthDate := ReadDateTime(Stream);
end;

procedure TPerson.SaveToStream(Stream: TStream);
begin
  WriteString(Stream, FName);
  WriteDateTime(Stream, FBirthDate);
end;

{ TStreamableList }

procedure TStreamableList.Add(Item: TStreamableObject);
begin
  FItems.Add(Item);
end;

procedure TStreamableList.Clear;
begin
  FItems.Clear;
end;

constructor TStreamableList.Create;
begin
  FItems := TObjectList.Create;
end;

procedure TStreamableList.Delete(Index: LongInt);
begin
  FItems.Delete(Index);
end;

destructor TStreamableList.Destroy;
begin
  FItems.Free;
  inherited;
end;

function TStreamableList.FindClass(const AClassName: string): TStreamableObjectClass;
begin
  Result := TStreamableObjectClass(Classes.FindClass(AClassName));
end;

function TStreamableList.Get_Count: LongInt;
begin
  Result := FItems.Count;
end;

function TStreamableList.Get_Objects(Index: LongInt): TStreamableObject;
begin
  Result := FItems[Index] as TStreamableObject;
end;

procedure TStreamableList.LoadFromStream(Stream: TStream);
var
  StreamCount: LongInt;
  I: Integer;
  S: string;
  ClassRef: TStreamableObjectClass;
begin
  StreamCount := ReadLongInt(Stream);
  for I := 0 to StreamCount - 1 do
  begin
    S := ReadClassName(Stream);
    ClassRef := FindClass(S);
    Add(ClassRef.CreateFromStream(Stream));
  end;
end;

procedure TStreamableList.SaveToStream(Stream: TStream);
var
  I: Integer;
begin
  WriteLongInt(Stream, Count);
  for I := 0 to Count - 1 do
  begin
    WriteClassName(Stream, Objects[I].ClassName);
    Objects[I].SaveToStream(Stream);
  end;
end;

{ TStreamableObject }

constructor TStreamableObject.CreateFromStream(Stream: TStream);
begin
  inherited Create;
  LoadFromStream(Stream);
end;

function TStreamableObject.ReadClassName(Stream: TStream): ShortString;
begin
  Result := ReadString(Stream);
end;

function TStreamableObject.ReadCurrency(Stream: TStream): Currency;
begin
  Stream.Read(Result, SizeOf(Currency));
end;

function TStreamableObject.ReadDateTime(Stream: TStream): TDateTime;
begin
  Stream.Read(Result, SizeOf(TDateTime));
end;

function TStreamableObject.ReadLongInt(Stream: TStream): LongInt;
begin
  Stream.Read(Result, SizeOf(LongInt));
end;

function TStreamableObject.ReadString(Stream: TStream): string;
var
  L: LongInt;
begin
  L := ReadLongInt(Stream);
  SetLength(Result, L);
  Stream.Read(Result[1], L);
end;

procedure TStreamableObject.WriteClassName(Stream: TStream; const Value: ShortString);
begin
  WriteString(Stream, Value);
end;

procedure TStreamableObject.WriteCurrency(Stream: TStream; const Value: Currency);
begin
  Stream.Write(Value, SizeOf(Currency));
end;

procedure TStreamableObject.WriteDateTime(Stream: TStream; const Value: TDateTime);
begin
  Stream.Write(Value, SizeOf(TDateTime));
end;

procedure TStreamableObject.WriteLongInt(Stream: TStream; const Value: LongInt);
begin
  Stream.Write(Value, SizeOf(LongInt));
end;

procedure TStreamableObject.WriteString(Stream: TStream; const Value: string);
var
  L: LongInt;
begin
  L := Length(Value);
  WriteLongInt(Stream, L);
  Stream.Write(Value[1], L);
end;

{ TCompany }

constructor TCompany.Create(const AName: string; ARevenues: Currency;
  AEmployeeCount: Integer);
begin
  FName := AName;
  FRevenues := ARevenues;
  FEmployeeCount := AEmployeeCount;
end;

procedure TCompany.LoadFromStream(Stream: TStream);
begin
  FName := ReadString(Stream);
  FRevenues := ReadCurrency(Stream);
  FEmployeeCount := ReadLongInt(Stream);
end;

procedure TCompany.SaveToStream(Stream: TStream);
begin
  WriteString(Stream, FName);
  WriteCurrency(Stream, FRevenues);
  WriteLongInt(Stream, FEmployeeCount);
end;

procedure TForm1.LoadButtonClick(Sender: TObject);
var
  List: TStreamableList;
  Stream: TStream;
  Instance: TStreamableObject;
  I: Integer;
begin
  Stream := TFileStream.Create(Path + DEFAULT_FILENAME, fmOpenRead);
  try
    List := TStreamableList.Create;
    try
      List.LoadFromStream(Stream);
      for I := 0 to List.Count - 1 do
      begin
        Instance := List[I];
        if Instance is TPerson then
          ShowMessage(TPerson(Instance).Name);
        if Instance is TCompany then
          ShowMessage(TCompany(Instance).Name);
      end;
    finally
      List.Free;
    end;
  finally
    Stream.Free;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Path := ExtractFilePath(Application.ExeName);
end;

initialization
  RegisterClasses([TPerson, TCompany]);

end.


Solve 2:

The solution above will work, but it forces you to implement streaming support for each of the TStreamableObject objects. Delphi has already implemented this mechanism in for the TPersistent class and the TComponent class, and you can use this mechanism. The class I include here does the job. It holds classes that inherit from TUmbCollectionItem (which in turn inherits from Delphi TCollectionItem), and handles all the streaming of the items. As the items are written with the Delphi mechanism, all published data is streamed.

Notes: This class does not support working within the delphi IDE like TCollection. All objects inheriting from TUmbCollectionItem must be registered using the Classes.RegisterClass function. All objects inheriting from TUmbCollectionItem must implement the assign function. By default, the TUmbCollection owns its items (frees them when the collection is freed), but this functionality can be changed.

unit UmbCollection;

interface

uses
  Windows, Messages, SysUtils, Classes, contnrs;

type
  TUmbCollectionItemClass = class of TUmbCollectionItem;
  TUmbCollectionItem = class(TCollectionItem)
  private
    FPosition: Integer;
  public
    {when overriding this method, you must call the inherited assign.}
    procedure Assign(Source: TPersistent); override;
  published
    {the position property is used by the streaming mechanism to place the object in the
    right position when reading the items. do not use this property.}
    property Position: Integer read FPosition write FPosition;
  end;

  TUmbCollection = class(TObjectList)
  private
    procedure SetItems(Index: Integer; Value: TUmbCollectionItem);
    function GetItems(Index: Integer): TUmbCollectionItem;
  public
    function Add(AObject: TUmbCollectionItem): Integer;
    function Remove(AObject: TUmbCollectionItem): Integer;
    function IndexOf(AObject: TUmbCollectionItem): Integer;
    function FindInstanceOf(AClass: TUmbCollectionItemClass; AExact: Boolean = True;
      AStartAt: Integer = 0): Integer;
    procedure Insert(Index: Integer; AObject: TUmbCollectionItem);

    procedure WriteToStream(AStream: TStream); virtual;
    procedure ReadFromStream(AStream: TStream); virtual;

    property Items[Index: Integer]: TUmbCollectionItem read GetItems write SetItems;
      default;
  published
    property OwnsObjects;
  end;

implementation

{ TUmbCollection }

function ItemsCompare(Item1, Item2: Pointer): Integer;
begin
  Result := TUmbCollectionItem(Item1).Position - TUmbCollectionItem(Item2).Position;
end;

function TUmbCollection.Add(AObject: TUmbCollectionItem): Integer;
begin
  Result := inherited Add(AObject);
end;

function TUmbCollection.FindInstanceOf(AClass: TUmbCollectionItemClass;
  AExact: Boolean; AStartAt: Integer): Integer;
begin
  Result := inherited FindInstanceOf(AClass, AExact, AStartAt);
end;

function TUmbCollection.GetItems(Index: Integer): TUmbCollectionItem;
begin
  Result := inherited Items[Index] as TUmbCollectionItem;
end;

function TUmbCollection.IndexOf(AObject: TUmbCollectionItem): Integer;
begin
  Result := inherited IndexOf(AObject);
end;

procedure TUmbCollection.Insert(Index: Integer; AObject: TUmbCollectionItem);
begin
  inherited Insert(Index, AObject);
end;

procedure TUmbCollection.ReadFromStream(AStream: TStream);
var
  Reader: TReader;
  Collection: TCollection;
  ItemClassName: string;
  ItemClass: TUmbCollectionItemClass;
  Item: TUmbCollectionItem;
  i: Integer;
begin
  Clear;
  Reader := TReader.Create(AStream, 1024);
  try
    Reader.ReadListBegin;
    while not Reader.EndOfList do
    begin
      ItemClassName := Reader.ReadString;
      ItemClass := TUmbCollectionItemClass(FindClass(ItemClassName));
      Collection := TCollection.Create(ItemClass);
      try
        Reader.ReadValue;
        Reader.ReadCollection(Collection);
        for i := 0 to Collection.Count - 1 do
        begin
          item := ItemClass.Create(nil);
          item.Assign(Collection.Items[i]);
          Add(Item);
        end;
      finally
        Collection.Free;
      end;
    end;
    Sort(ItemsCompare);
    Reader.ReadListEnd;
  finally
    Reader.Free;
  end;
end;

function TUmbCollection.Remove(AObject: TUmbCollectionItem): Integer;
begin
  Result := inherited Remove(AObject);
end;

procedure TUmbCollection.SetItems(Index: Integer; Value: TUmbCollectionItem);
begin
  inherited Items[Index] := Value;
end;

procedure TUmbCollection.WriteToStream(AStream: TStream);
var
  Writer: TWriter;
  CollectionList: TObjectList;
  Collection: TCollection;
  ItemClass: TUmbCollectionItemClass;
  ObjectWritten: array of Boolean;
  i, j: Integer;
begin
  Writer := TWriter.Create(AStream, 1024);
  CollectionList := TObjectList.Create(True);
  try
    Writer.WriteListBegin;
    {init the flag array and the position property of the TCollectionItem objects.}
    SetLength(ObjectWritten, Count);
    for i := 0 to Count - 1 do
    begin
      ObjectWritten[i] := False;
      Items[i].Position := i;
    end;
    {write the TCollectionItem objects. we write first the name of the objects class,
    then write all the object of the same class.}
    for i := 0 to Count - 1 do
    begin
      if ObjectWritten[i] then
        Continue;
      ItemClass := TUmbCollectionItemClass(Items[i].ClassType);
      Collection := TCollection.Create(ItemClass);
      CollectionList.Add(Collection);
      {write the items class name}
      Writer.WriteString(Items[i].ClassName);
      {insert the items to the collection}
      for j := i to Count - 1 do
        if ItemClass = Items[j].ClassType then
        begin
          ObjectWritten[j] := True;
          (Collection.Add as ItemClass).Assign(Items[j]);
        end;
      {write the collection}
      Writer.WriteCollection(Collection);
    end;
  finally
    CollectionList.Free;
    Writer.WriteListEnd;
    Writer.Free;
  end;
end;

{ TUmbCollectionItem }

procedure TUmbCollectionItem.Assign(Source: TPersistent);
begin
  if Source is TUmbCollectionItem then
    Position := (Source as TUmbCollectionItem).Position
  else
    inherited;
end;

end.

2007. július 5., csütörtök

Compilerswitch {$HINTS} (Delphi 2/ 3 only)


Problem/Question/Abstract:

Compilerswitch {$HINTS} (Delphi 2/ 3 only)

Answer:

Delphi 2/3 can tell you about minor errors in your code such as declaring a variable and not using it or writing to a variable and not using the stored value.

By default, the hints and warnings are switched off. You can switch them on

either on a global level (for all units in a project): To view these hints for an entire project, open the Project Options dialog box, go on the Compiler page and select the Show Hints checkbox.

even within a small section of a given unit. To view only the hints that apply to a section of code, use the {$HINTS ON} and {$HINTS OFF} compiler directives, as shown below:


{$HINTS ON}
procedure aProc;
var
  X: Integer;
begin
  ShowMessage('X is not used');
end;
{$HINTS OFF}

2007. július 4., szerda

How to adjust a memo to the height required to show all text without scrollbars


Problem/Question/Abstract:

How would I find out how many viewed lines are in a memo? For example, if one line is wrapped once, it would count as two. I need to stretch it so that all lines are visible.

Answer:

Solve 1:

Adjusting a memo to the height required to show all text without scrollbars:

procedure TForm1.Button2Click(Sender: TObject);
var
  rect1, rect2: TRect;
  S: string;
begin
  s := Memo1.Text;
  memo1.Perform(EM_GETRECT, 0, longint(@rect1));
  rect2 := rect1;
  canvas.font := memo1.font;
  DrawTextEx(canvas.handle, Pchar(S), Length(S), rect2, DT_CALCRECT or
    DT_EDITCONTROL or DT_WORDBREAK or DT_NOPREFIX, nil);
  memo1.Height := memo1.height + rect2.bottom - rect1.bottom;
end;


Solve 2:

I use the following:

with TControlCanvas.Create do
try
  Control := MmoView;
  Font.Assign(MmoView.Font);
  FFontHeight := TextHeight('Q');
  FFontWidth :=
    TextWidth('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') div 52;
finally
  Free;
end;

FMaxBuf := (MmoView.ClientHeight div FFontHeight) * (MmoView.ClientWidth div
  FFontWidth);
FMaxLines := (MmoView.ClientHeight div FFontHeight) - 1;

2007. július 3., kedd

I/O Error during OpenFile OF_READWRITE


Problem/Question/Abstract:

Why am I getting the error "I/O Error during "OpenFile OF_READWRITE" operation for file XXX" when attempting to open/connect to a database?

Answer:

This error is caused by an invalid file name. Either the file or the directory do not exist, or the user has insufficient rights to the database.

2007. július 2., hétfő

Bug in StringReplace (Handling Null characters) (fixed)


Problem/Question/Abstract:

I've noticed a problem when you try to use StringReplace on a string which contains NULL (#0) characters (not null terminated).

Answer:

There is an undocumented bug in the StringReplace function.

It appears that it does not handle strings will NULL (#0) characters in them.

Here is a better routine which handles NULL correctly.


function customStringReplace(OriginalString, Pattern, Replace: string): string;

{-----------------------------------------------------------------------------

��Procedure: customStringReplace
��Date:������07-Feb-2002
��Arguments: OriginalString, Pattern, Replace: string
��Result:����string
��Description:
����Replaces Pattern with Replace in string OriginalString.
����Taking into account NULL (#0) characters.
����I cheated. This is ripped almost directly from Borland's
����StringReplace Function. The bug creeps in with the ANSIPos
����function. (Which does not detect #0 characters)

-----------------------------------------------------------------------------}

var
  ��SearchStr, Patt, NewStr: string;
  ��Offset: Integer;
begin
  ��Result := '';
  ��SearchStr := OriginalString;
  ��Patt := Pattern;
  ��NewStr := OriginalString;
  ��while SearchStr <> '' do
  ��begin
  ����Offset := Pos(Patt, SearchStr); // Was AnsiPos
  ����if Offset = 0 then
  ����begin
  ������Result := Result + NewStr;
  ������Break;
  ����end;
  ����Result := Result + Copy(NewStr, 1, Offset - 1) + Replace;
  ����NewStr := Copy(NewStr, Offset + Length(Pattern), MaxInt);
  ����SearchStr := Copy(SearchStr, Offset + Length(Patt), MaxInt);
  ��end;
end;

2007. július 1., vasárnap

Catching Browsers URLs with DDE


Problem/Question/Abstract:

I try to catch all URLs that all instances of the web browser visit. I try to achieve this with DDE, but sometimes miss URLs.

Answer:

I did the same job not long ago. The code works alright with Netscape but with IE you will run into a problem.

If you start IE and then open additional windows with the 'New Window' menu item, then you are running only ONE instance and you will get successfully all URLs.

BUT:

If you start a second instance of IE by clicking on the icon in your start menu a second.. then only one of your two instances will report DDE messages to your Delphi program. Usually the first started one seems to reply.

There is no clean solution around it.

I ended up enumerating top level windows and manually checking each top level window whether it was an IE instance. Then I would move through the child window chain with FindWindow(), GetWindowClass() and so on.. and retrieve the URL that way. The same code actually worked for IE 4, IE 5.0 and IE 5.5. I did not test with IE 6.

The code needed a modification for Netscape, naturally.