2008. október 31., péntek

How to pop up a TComboBox at the current caret position in a TMemo


Problem/Question/Abstract:

I would like to pop up a TComboBox at the caret position on a TMemo when the key that is pressed is a full stop (.). Has anyone got any code for this?

Answer:

unit CBoxInMemo;

interface

uses
  Windows, Classes, Controls, Graphics, Forms, StdCtrls;

type
  TFrmCboxInMemo = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Label1: TLabel;
    ComboBox1: TComboBox;
    procedure Button1Click(Sender: TObject);
    procedure ComboBox1Exit(Sender: TObject);
    procedure ComboBox1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  FrmCboxInMemo: TFrmCboxInMemo;

implementation

{$R *.DFM}

procedure TFrmCboxInMemo.Button1Click(Sender: TObject);
var
  clientPos: TPoint;
  lineHeight: Integer;
  tmpFont: TFont;
begin
  GetCaretPos(clientPos);
  {Use the following calculation of line height only if you want your combobox
  to appear below the char position you are referencing.}
  tmpFont := Canvas.Font;
  Canvas.Font := Memo1.Font;
  lineHeight := Canvas.TextHeight('Xy');
  Canvas.Font := tmpFont;
  with ComboBox1 do
  begin
    {Adjustment of Top by lineHeight only necessary if combobox is to appear below line.}
    Top := clientPos.Y + Memo1.Top + lineHeight;
    Left := clientPos.X + Memo1.Left;
    Visible := true;
    SetFocus;
  end;
end;

procedure TFrmCboxInMemo.ComboBox1Exit(Sender: TObject);
begin
  ComboBox1.Visible := false;
end;

procedure TFrmCboxInMemo.ComboBox1Click(Sender: TObject);
begin
  ComboBox1.Visible := false;
end;

end.

2008. október 30., csütörtök

Use a lowpass filter to blur images


Problem/Question/Abstract:

Does anyone know of a (preferably online) source of information for blurring algorithms? I'm looking for a simple way of blurring an image.

Answer:

A lowpass filter does the job. Copy the image to memory. Then take a zeroed piece of memory the same size. Pull a 3x3 window over your memory image (for x, for y) within this window, multiply the underlying pixels with the constant window multiplyer :

1 1 1 //
1 1 1
1 1 1

and add them up. This value/9 assign to the new memory image at (x,y).

{ ... }
for x := 0 to image1.width - 1 do
begin
  for y := 0 to image1.height - 1 do
  begin
    s := 0;
    for h := -1 to 1 do
    begin
      for v := -1 to 1 do
      begin
        s := s + memimage[x + h, y + v];
      end;
    end;
    new_memimage[x, y] := s / 9;
  end;
end;
{ ... }

then copy the new memory image to the image. Note that the border of the image has to be treated specially. (array range)

2008. október 29., szerda

How to view dBase records which are marked for deletion


Problem/Question/Abstract:

How to view dBase records which are marked for deletion

Answer:

Call the following function on the AfterOpen event of the table. You must include DBITYPES, DBIERRS, DBIPROCS in the uses clause. To call, send as arguments name of TTable and True / False depending to show / not show deleted records. Example:

procedure TForm1.Table1AfterOpen(DataSet: TDataset);
begin
  SetDelete(Table1, TRUE);
end;

procedure SetDelete(oTable: TTable; Value: Boolean);
var
  rslt: DBIResult;
  szErrMsg: DBIMSG;
begin
  try
    oTable.DisableControls;
    try
      rslt := DbiSetProp(hDBIObj(oTable.Handle), curSOFTDELETEON,
        LongInt(Value));
      if rslt <> DBIERR_NONE then
      begin
        DbiGetErrorString(rslt, szErrMsg);
        raise Exception.Create(StrPas(szErrMsg));
      end;
    except
      on E: EDBEngineError do
        ShowMessage(E.Message);
      on E: Exception do
        ShowMessage(E.Message);
    end;
  finally
    oTable.Refresh;
    oTable.EnableControls;
  end;
end;

2008. október 28., kedd

Swap columns in a TStringGrid


Problem/Question/Abstract:

Does anyone know how to swap two columns in TStringGrid? If you try to exchange() the two columns as if they are TStringLists all hell breaks out (... because they aren't really TStringLists I guess?).

Answer:

I would use an intermediate, temporary string list. Let's say you want to exchange columns 3 and 5:

var
  Temp: TStringList;
begin
  Temp := TStringList.Create;
  Temp.Assign(MyGrid.Cols[3]);
  MyGrid.Cols[3].Assign(MyGrid.Cols[5]);
  MyGrid.Cols[5].Assign(Temp);
  Temp.Free;
end;

2008. október 27., hétfő

How to detect the Windows OS version


Problem/Question/Abstract:

How to detect the Windows OS version

Answer:

Solve 1:

uses
  Windows;

type
  TWinVersion = (Win32, Win9x, WinNt, WinError);

function fWinVersion: TWinVersion;
var
  GV: TOSVersionInfo;
begin
  GV.dwOSVersionInfoSize := Sizeof(GV);
  if GetVersionEx(GV) then
  begin
    case GV.dwPlatformId of
      VER_PLATFORM_WIN32s:
        Result := Win32;
      VER_PLATFORM_WIN32_WINDOWS:
        Result := Win9x;
      VER_PLATFORM_WIN32_NT:
        Result := WinNT;
    else
      Result := WinError;
    end;
  end
  else
    Result := WinError;
end;


Solve 2:

type
  PTransBuffer = ^TTransBuffer;
  TTransBuffer = array[1..4] of smallint;

const
  CInfoStr: array[1..4] of string = ('FileVersion', 'LegalCopyright', 'ProductName',
    'ProductVersion');

procedure TFrmAbout.GetVersionInfo(AVersionList: TStrings);
var
  filename: string;
  i: integer;
  infoSize: DWORD;
  ptrans: PTransBuffer;
  transStr: string;
  typeStr: string;
  value: PChar;
  verBuf: pointer;
  verSize: DWORD;
  wnd: DWORD;
begin
  AVersionList.Clear;
  filename := Application.ExeName;
  infoSize := GetFileVersioninfoSize(PChar(filename), wnd);
  if infoSize <> 0 then
  begin
    GetMem(verBuf, infoSize);
    try
      if GetFileVersionInfo(PChar(filename), wnd, infoSize, verBuf) then
      begin
        VerQueryvalue(verBuf, PChar('\VarFileInfo\Translation'), Pointer(ptrans),
          verSize);
        transStr := IntToHex(ptrans^[1], 4) + IntToHex(ptrans^[2], 4);
        for i := Low(CInfoStr) to High(CInfoStr) do
        begin
          typeStr := 'StringFileInfo\' + transStr + '\' + CInfoStr[i];
          if VerQueryvalue(verBuf, PChar(typeStr), Pointer(value), verSize) then
            {AVersionList.Add(CInfoStr[i] + ': ' + value);}
            AVersionList.Add(value);
        end
      end;
    finally
      FreeMem(verBuf);
    end;
  end;
end;


Solve 3:

Delphi 5 has a variable Win32Platform in SysUtils

var
  Win32Platform: Integer = 0;

It will have 3 values

VER_PLATFORM_32s
VER_PLATFORM_WIN32_WINDOWS
VER_PLATFORM_WIN32_WIN_NT


Solve 4:

{ ... }
case Win32MajorVersion of
  3: {NT 3.51}
    OSLabel.Caption := 'Windows NT 3.51';
  4: {WIn9x/ME, NT 4}
    case Win32MinorVersion of
      0:
        OSLabel.Caption := 'Windows 95';
      10:
        OSLabel.Caption := 'Windows 98';
      90:
        OSLabel.Caption := 'Windows ME';
    else
      if (Win32Platform and VER_PLATFORM_WIN32_NT) <> 0 then
        OSLabel.Caption := 'Windows NT 4.0'
      else
        OSLabel.Caption := 'unknown';
    end;
  5: {Win2K, XP}
    case Win32MinorVersion of
      0:
        OSLabel.Caption := 'Windows 2000';
      1:
        OSLabel.Caption := 'Windows XP or .NET server';
    else
      OSLabel.Caption := 'unknown';
    end;
else
  OSLabel.Caption := 'unknown';
end;


Solve 5:

function GetOSInfo: string;
var
  Platform: string;
  BuildNumber: integer;
begin
  case Win32MajorVersion of
    3: {NT 3.51}
      Platform := 'Windows NT 3.51';
    4: {Win9x/ ME/ NT 4}
      case Win32MinorVersion of
        0:
          Platform := 'Windows 95';
        10:
          Platform := 'Windows 98';
        90:
          Platform := 'Windows ME';
      else
        if (Win32Platform and VER_PLATFORM_WIN32_NT) <> 0 then
          Platform := 'Windows NT 4.0'
        else
          Platform := SUnknown;
      end;
    5: {Win2000/ XP}
      case Win32MinorVersion of
        0:
          Platform := 'Windows 2000';
        1:
          Platform := 'Windows XP or .NET server';
      else
        Platform := SUnknown;
      end;
  else
    Platform := SUnknown;
  end;
  case Win32Platform of
    VER_PLATFORM_WIN32_WINDOWS:
      BuildNumber := Win32BuildNumber and $0000FFFF;
    VER_PLATFORM_WIN32_NT:
      BuildNumber := Win32BuildNumber;
  else
    BuildNumber := 0;
  end;
  if (Win32Platform = VER_PLATFORM_WIN32_WINDOWS) or
    (Win32Platform = VER_PLATFORM_WIN32_NT) then
  begin
    if Win32CSDVersion = '' then
      Result := Format('%s (Build %d)', [Platform, BuildNumber])
    else
      Result := Format('%s (Build %d: %s)', [Platform, BuildNumber, Win32CSDVersion]);
  end
  else
    Result := Platform;
end;

2008. október 26., vasárnap

Adding new methods and properties without registering new components


Problem/Question/Abstract:

Is there a way to add new methods and properties to a component without having its soruce code and having to install a descendant component?

Answer:

Adding new methods and properties

Sometimes we need to add new methods and properties to an existing component (or change the visibility of existing properties). One way of doing this is modifiying the component, but this implies having to recompile its package and we would have to redistribute our changes if we wanted our application to be compiled by others, and that would
be a bother for the recipients. Sometimes we may not even have that choice because we may not have the source code. In these situations, better would be to subclass (derive) the component and add new properties and methods. For example:

type
  TEditX = class(TEdit)
  public
    function GetForeColor: TColor;
    procedure SetForeColor(color: TColor);
    property ForeColor: TColor read GetForeColor write SetForeColor;
  end;

These methods could for example be implemented this way:

function TEditX.GetForeColor: TColor;
begin
  Result := Font.Color;
end;

procedure TEditX.SetForeColor(color: TColor);
begin
  Font.Color := Color;
end;

It's a silly example, of course, but it serves the purpose.

Casting to the new class

We don't need to intall this new component and register it in the components palette or replace existing controls in our applications (which would be an unpayable penalty for such small changes and/or additions). Instead, any time we want to access the new properties and methods, we can just cast the object (for example Edit1) to our new class. For example:

TEditX(Edit1).ForeColor := clRed;

or

TEditX(Edit1).SetForeColor(clRed);

Warning: This casting to a descendant class can only be done if the new class adds new properties and static methods, but without adding new fields and new virtual or dynamic methods, although in theory you can override existing virtual methods. Also, the visibility of existing properties can be changed, as in the InplaceEditor example
explained in the article "Accessing hidden properties".

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

2008. október 25., szombat

Using UDL files to simplify ADO


Problem/Question/Abstract:

How do I get the ADO connection dialog up in Delphi

Answer:

Recently I had to do some work with SQL Server 7 using D4. To minimise the need for ODBC configuration, I chose to use ADO. As D4 lacks the Adoconed unit for displaying the ADO configuration dialogs, I found another way. If the UDL file is absent or corrupted, it displays the dialog then creates a new file. If however an existing configuration file is there then it loads it in and uses it.

For the ADO proper (which I haven&#8217;t shown- this just sets up the Ado connection string) I used the Ado components from http://www.alohaoi.com which are freeware with source and the best I&#8217;ve found.

Just install the Ado components then put a button on the form to test this below.

For anyone that is interested, the Msdasc objects (which manage the connection dialogs) are contained in oledb32.dll- import the type library to get access to this.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, aoADODB_tlb, aomsdasc_tlb, aoADODB, ComObj;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    procedure loadudl;
    procedure NewLink;
    { Private declarations }
  public
    ObjDataLink: Datalinks;
    dbConnection: connection;
    DataInitialize: IDataInitialize;
    WUdlFile: Widestring;
    AdoStr: string;
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

const
  UdlFile = 'Adolink.udl';

procedure Tform1.loadudl;
var
  pwstr: pwidechar;
  wUDLFile: array[0..MAX_PATH - 1] of WideChar;
begin
  DataInitialize := CreateComObject(CLASS_DataLinks) as IDataInitialize;
  StringToWideChar(UDLFile, @wUDLFile, MAX_PATH);
  if Failed(DataInitialize.LoadStringFromStorage(wUDLFile, pwstr)) then
  begin
    ShowMessage('Link file corrupted or missing- please renew');
    Newlink;
  end
  else
  begin
    adostr := pwstr;
  end;
end;

procedure Tform1.NewLink;
var
  str: widestring;
  wUDLFile: array[0..MAX_PATH - 1] of WideChar;
begin
  str := '';
  ObjDataLink := Codatalinks.Create;
  if adostr <> '' then
  begin
    dbconnection := coconnection.create;
    dbconnection.ConnectionString := adostr;
    if ObjDataLink.PromptEdit(idispatch(dbconnection)) then
      str := dbconnection.ConnectionString;
  end
  else
  begin
    dbconnection := ObjDataLink.PromptNew as _connection;
    if assigned(dbconnection) then
      str := dbconnection.ConnectionString;
  end;
  DataInitialize := CreateComObject(CLASS_DataLinks) as IDataInitialize;
  StringToWideChar(UDLFile, @wUDLFile, MAX_PATH);
  sysutils.DeleteFile(udlfile);
  if Failed(DataInitialize.WriteStringToStorage(wUDLFile, pwidechar(Str), CREATE_NEW))
    then
    raise Exception.Create('Can''t write UDL to ' + udlfile);
  adostr := str;
end;

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
  LoadUdl;
end;

end.

2008. október 24., péntek

Creating two horizontal lines on your screen? (TDesktopCanvas)


Problem/Question/Abstract:

How do I create lines (or whatever) on the screen?

Answer:

This program demonstrates a TDesktopCanvas. I wrote this to prepare my self for using Trinitron monitors :)  The code parts are gathered from different parts of the www.

program TrinitronTraining;

uses
  Messages, Windows, Graphics, Forms;

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

  { TDesktopCanvas object }

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

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

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

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

const
  YCount = 2;

var
  desktop: TDesktopCanvas;
  dx, dy: Integer;
  i: Integer;
  F: array[1..YCount] of TForm;

function CreateLine(Y: Integer): TForm;
begin
  Result := TForm.Create(Application);
  with Result do
  begin
    Left := 0;
    Top := y;
    Width := dx;
    Height := 1;
    BorderStyle := bsNone;
    FormStyle := fsStayOnTop;
    Visible := True;
  end;
end;

procedure ProcessMessage;
var
  Msg: TMsg;
begin
  if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
    if Msg.Message = WM_QUIT then
      Application.Terminate;
end;

begin
  desktop := TDesktopCanvas.Create;
  try
    dx := desktop.Width;
    dy := desktop.Height div (YCount + 1);
  finally
    desktop.free;
  end;
  for i := 1 to YCount do
    F[i] := CreateLine(i * dy);
  Application.NormalizeTopMosts;
  ShowWindow(Application.Handle, SW_Hide);

  for i := 1 to YCount do
    SetWindowPos(F[i].Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE + SWP_NOMOVE +
      SWP_NOSIZE);

  { use this if you don't want to stop
    repeat
      ProcessMessage;
    until false;
  {}
  Sleep(15000);

  for i := 1 to YCount do
    F[i].Free;
end.

2008. október 23., csütörtök

An ADO replacement for SQL Explorer


Problem/Question/Abstract:

Most programmers use the SQL Explorer which is installed by default with Delphi. With the announced extinction of the BDE, an alternative is needed for everyday&#8217;s common tasks. This is my solution.

How do I query an Access MDB File with ADO (no BDE installed)?

Answer:

The SQL Explorer has been for years one of the most important tools I used.
Now that I decided to shift from BDE solutions to ADO, I needed an alternative for the SQL Explorer which could gave me at least the same functionalities, so I created this little Delphi project (which I called ADO Explorer) which gaves me the main features a real programmer needs. In the next weeks I will prepare a second article in which I will explain how to customize and empower this program, makin it even a better solution that the Old, original, beloved SQL Explorer.

1. Open your Delphi 5 and create a new blank project.

2. On the form, drop these components:

An ADOConnection
An ADOQuery
A DBGrid
A DataSource
3 Buttons
A Memo component
An Edit component
An OpenDialog and SaveDialog component

3. Connect together the DB components (ADOConnection->ADOQuery->DataSource->DBGrid)

4. Insert in the first button&#8217;s OnClick Event handler this code:

procedure TForm1.Button1Click(Sender: TObject);
begin
  if ADOQuery1.Active then
    ADOQuery1.Close;
  if ADOConnection1.Connected then
    ADOConnection1.Connected := False;
  ADOConnection1.ConnectionString := Edit1.Text;
  ADOQuery1.SQL := Memo1.Lines;
  if UpperCase(Copy(Memo1.Lines[0], 1, 6)) = 'SELECT' then
  begin
    ADOQuery1.Open; // Result Set attended fro mthe operation
  end
  else
  begin
    ADOQuery1.ExecSQL; // No result set -> different method to open
  end;
end;

5. Insert in the second button&#8217;s OnClick Event handler this code:

if OpenDialog1.Execute then
  Memo1.Lines.LoadFromFile(OpenDialog1.FileName);

6. Insert in the third button&#8217;s OnClick Event handler this code:

if SaveDialog1.Execute then
  Memo1.Lines.SaveToFile(SaveDialog1.FileName);

7. Build & compile the project: the new ADO SQL Explorer is done!

The program can archive the most important queries used; it recognizes on its own the correct method to use with the given query and can be used against virtually every kind of OleDB or ODBC supported database.

The Sources of the program will be made available for download soon on my website (http://www.dreamscape.it) or can be asked directly by email to massimo.brini@dreamscape.it.

2008. október 22., szerda

Creating a component at runtime


Problem/Question/Abstract:

I want to create a button in code, put it on a form and attach a procedure to its click event. How can I get the click event linked to a predefined procedure name from code? I assume the IDE linking in the object browser is key to the answer, but I want to do this at run time, not in development.

Answer:

Thank God for object-oriented environments! First of all, you can assign any object's method to another method as long as it has the same form. Look at the code below:

{This method is from another button that when pressed will create
the new button.}

procedure TForm1.Button1Click(Sender: TObject);
var
  btnRunTime: TButton;
begin
  btnRunTime := TButton.Create(form1);
  with btnRunTime do
  begin
    Visible := true;
    Top := 64;
    Left := 200;
    Width := 75;
    Caption := 'Press Me';
    Name := 'MyNewButton';
    Parent := Form1;
    OnClick := ClickMe;
  end;
end;

{This is the method that gets assigned to the new button's OnClick method}

procedure TForm1.ClickMe(Sender: TObject);
begin
  with (Sender as TButton) do
    ShowMessage('You clicked me');
end;

As you can see, I created a new method called ClickMe, which was declared in the private section of Form1:

type
  TForm1 = class(TForm
      ...
      ...
      private

procedure ClickMe(Sender: TObject);
published
  end;

There's no way to write code at run time, so it has to pre-exist. Fortunately with Delphi, you can perform re-assignment of methods to other methods in code. This duplicates assigning all the OnClick methods of a bunch of buttons to a single button's OnClick that you can do in the Object Inspector. You're just doing it in code.

So why does this work?

Event handlers are really nothing more than pointers to procedures. In the object code, they're declared something like the following:

type
  TNotifyEvent = procedure(Sender: TObject) of object;
  TMouseEvent = procedure(Sender: TObject; Button: TMouseButton; Shift:
    TShiftState; X, Y: Integer) of object;

Then, properties in the components are assigned these types. For instance, an OnClick for a button as seen in the Object Inspector is a TNotifyEvent and is declared in the component code as follows:

property OnClick: TNotifyEvent read FOnClick write FOnClick;

All this means is: When this event occurs, execute a method that has the structure that's what I'm expecting (the FOnClick var). In the case of OnClick, it's a method that has a single parameter of TObject &mdash (Sender : TObject). Note that I specifically say "method," which implies that the procedure must be a member function of some object (like a form or another button), and not a generic procedure.

Regarding to the FOnClick, that's just a variable with the same type as the property; as such, it can be assigned any method that has the right structure.

In some but not all components, there's underlying behavior associated with any event that's performed by Windows message handlers. For instance, sometimes it's not enough just to declare an event handler. A button also gets its "button-ness" from the Windows messages it traps as well. For an OnClick, the specific Windows message is WM_LBUTTONUP (OnClick in the help is explained as an event that occurs when the user presses the mouse button down and releases it, which is why the user code is not executed until the button is released), and that is handled in the component code behind the scenes. It executes regardless of the code you assign to the OnClick procedure, and it is executed first. So here's pecking order:

User clicks on a button.
Windows Message code gets executed to ellicit default behavior for the component.
Any code assigned to the user event handler is then executed by a specific Windows message handler.

This is stuff you don't normally hear about, and it's important to understand the intricacies behind why something works the way it does as opposed to me just giving you a "pat" answer. What I've essentially outlined here is the way in which an event handler is created. If you want more information, I suggest you get a book that deals with this. Ray Konopka's book Building Delphi Components (or something like that) is a good reference.

2008. október 21., kedd

Converting SWF to EXE using Delphi


Problem/Question/Abstract:

Converting SWF to EXE using Delphi

Answer:

function Swf2Exe(S, D, F: string): string;
//S = Source file (swf)
//D = Destionation file (exe)
//F = Flash Player
var
  SourceStream, DestinyStream, LinkStream: TFileStream;
  flag: Cardinal;
  SwfFileSize: integer;
begin
  result := 'something error';
  DestinyStream := TFileStream.Create(D, fmCreate);
  try
    LinkStream := TFileStream.Create(F, fmOpenRead or fmShareExclusive);
    try
      DestinyStream.CopyFrom(LinkStream, 0);
    finally
      LinkStream.Free;
    end;

    SourceStream := TFileStream.Create(S, fmOpenRead or fmShareExclusive);
    try
      DestinyStream.CopyFrom(SourceStream, 0);
      flag := $FA123456;
      DestinyStream.WriteBuffer(flag, sizeof(integer));
      SwfFileSize := SourceStream.Size;
      DestinyStream.WriteBuffer(SwfFileSize, sizeof(integer));
      result := '';
    finally
      SourceStream.Free;
    end;
  finally
    DestinyStream.Free;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Swf2Exe('c:\somefile.swf', 'c:\somefile.exe',
    'c:\Program Files\Macromedia\Flash MX\Players\SA\FlashPlayer.exe');
end;

2008. október 20., hétfő

Accessing Web Services from URL


Problem/Question/Abstract:

How can I access the web service through my application

Answer:

This article describes how to call the web services from your application. The MSSoap client ole object will allow the application to make remote procedure calls to the web server over the internet. So we need to create a ole object i.e the "MSSoap.Soapclient" in our application. For this, Microsoft Soap ToolKit must be installed in the machine where the application is running.

For this example will be using the "CurrencyExchangeService" webservice which is provided by www.xmethods.net. This web service gives the currency value of the Country2 with respect to Country1.

function getrate(Country1, Country2: string): Double;
var
  SoapClient: OleVariant;
  vRate: string;
  vURL: string;
begin
  vURL := 'http://www.xmethods.net/sd/CurrencyExchangeService.wsdl';
  vRate := 0;
  try
    SoapClient := CreateOleObject('MSSOAP.SoapClient');
  except
  end;
  try
    SoapClient.mssoapinit(vURL);
    //GetRate is the function in the Web service
    vRate := SoapClient.GetRate(Country1, Country2);
  except
  end;
  try
    FreeAndNil(SoapClient);
  except
  end;
  Result := StrToFloat(vRate);
end;

2008. október 19., vasárnap

Draw a line from the mouse cursor to a fixed point on a form


Problem/Question/Abstract:

How to draw a line from the mouse cursor to a fixed point on a form

Answer:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
    fOldX, fOldY: Integer;
    fLineDrawn: Boolean;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

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

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);

  procedure DrawLine(Color: TColor);
  begin
    Canvas.Pen.Color := Color;
    Canvas.MoveTo(fOldX, fOldY);
    Canvas.LineTo(100, 100);
  end;

begin
  if fLineDrawn then
    DrawLine(Color);
  fOldX := X;
  fOldY := Y;
  DrawLine(clRed);
  fLineDrawn := true;
end;

end.

2008. október 18., szombat

Save all TWebbrowser frame sources

Problem/Question/Abstract:

How to save all TWebbrowser frame sources

Answer:

uses
ActiveX;

function TForm1.GetFrame(FrameNo: Integer): IWebbrowser2;
var
OleContainer: IOleContainer;
enum: IEnumUnknown;
unk: IUnknown;
Fetched: PLongint;
begin
while Webbrowser1.ReadyState <> READYSTATE_COMPLETE do
Application.ProcessMessages;
if Assigned(Webbrowser1.document) then
begin
Fetched := nil;
OleContainer := Webbrowser1.Document as IOleContainer;
OleContainer.EnumObjects(OLECONTF_EMBEDDINGS, Enum);
Enum.Skip(FrameNo);
Enum.Next(1, Unk, Fetched);
Result := Unk as IWebbrowser2;
end
else
Result := nil;
end;

// Load sample page

procedure TForm1.Button1Click(Sender: TObject);
begin
Webbrowser1.Navigate('http://www.warebizprogramming.com/tutorials/html/framesEx1.htm');
end;

// Save all frames in single files

procedure TForm1.Button2Click(Sender: TObject);
var
IpStream: IPersistStreamInit;
AStream: TMemoryStream;
iw: IWebbrowser2;
i: Integer;
sl: TStringList;
begin
for i := 0 to Webbrowser1.OleObject.Document.frames.Length - 1 do
begin
iw := GetFrame(i);
AStream := TMemoryStream.Create;
try
IpStream := iw.document as IPersistStreamInit;
if Succeeded(IpStream.save(TStreamadapter.Create(AStream), True)) then
begin
AStream.Seek(0, 0);
sl := TStringList.Create;
sl.LoadFromStream(AStream);
sl.SaveToFile('c:\frame' + IntToStr(i) + '.txt');
//  memo1.Lines.LoadFromStream(AStream);
sl.Free;
end;
except
end;
AStream.Free;
end;
end;

end.


2008. október 17., péntek

Enable / disable single items in a TRadioGroup

Problem/Question/Abstract:

How can I set single Items.Strings in RadioGroups to Enabled := True or Enabled := False ?

Answer:

Solve 1:

TControl(RadioGroup1.Components[0]).Enabled := false;
TControl(RadioGroup1.Components[1]).Enabled := true;


Solve 2:

This function allows you to modify TRadioButtons in a given RadioGroup. Of course you can modify this to search not for a caption but for an index:

function ButtonOfGroup(rg: TRadioGroup; SearchCaption: string): TRadioButton;
var
i: Integer;
begin
Result := nil;
for i := 0 to rg.ComponentCount - 1 do
if (rg.Components[i] is TRadioButton) and
(CompareStr(TRadioButton(rg.Components[i]).Caption, SearchCaption) = 0) then
begin
Result := TRadioButton(rg.Components[i]);
Break;
end;
end;


Solve 3:

The following code shows how to disable or enable an individual radio button in a TRadioGroup component (the second radio button in this case). Note that the RadioGroup.Controls is a zero based array.

procedure TForm1.Button1Click(Sender: TObject);
begin
TRadioButton(RadioGroup1.Controls[1]).Enabled := False;
end;


2008. október 16., csütörtök

How to stop the DBGrid control from auto-appending a new entry

Problem/Question/Abstract:

How can I stop the DBGrid control from auto-appending a new entry?

Answer:

// Torry's Delphi Tips
// Author Damian Gorski
// Listed 24.02.2003
{How to stop the dbgrid control from auto-appending a new entry when you move
down after the last record in a table.
It creates a new blank line / record in the table. Can this be stopped?}

{A: Add to your TTables's "BeforeInsert"  event the following line:}

procedure TForm1.Tbable1BeforeInsert(DataSet: TDataSet);
begin
Abort; {<<---this line}
end;

{A: It traps the down key and checks for end-of-file.}

procedure TForm8.DBGrid1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key = VK_DOWN) then
begin
TTable1.DisableControls;
TTable1Next;
if TTable1.EOF then
Key := 0
else
TTable1.Prior;
TTable1.EnableControls;
end;
end;

2008. október 15., szerda

Implementation of Wake On Lan procedure

Problem/Question/Abstract:

This procedure will switch on a machine that is connected to a LAN. The MAC address of the machine is needed to be known. See my article "Get MAC Address of Remote or Local" for a function GetMacAddress() that returns a MAC Address String.

The "Wake On Lan" feature of the machine's BIOS must be enabled.

The procedure works by broadcasting a UDP packet containing the "Magic Number" to all machines on the LAN. The machine with the MAC address, if switched of and BIOS WOL enabled will wake up and boot.

The MAC address required is a "-" delimited 17 char string.

Example :

WakeOnLan('00-D0-B7-E2-A1-A0');

Answer:

uses idUDPClient;

// ==========================================================================
// Wakes a machine on lan
// AMacAddress is 17 char MAC address.
// eg.  '00-C0-4F-0A-3A-D7'
// ==========================================================================

procedure WakeOnLan(const AMacAddress: string);
type
TMacAddress = array[1..6] of byte;

TWakeRecord = packed record
Waker: TMACAddress;
MAC: array[0..15] of TMACAddress;
end;

var
i: integer;
WR: TWakeRecord;
MacAddress: TMacAddress;
UDP: TIdUDPClient;
sData: string;
begin
// Convert MAC string into MAC array
fillchar(MacAddress, SizeOf(TMacAddress), 0);
sData := trim(AMacAddress);

if length(sData) = 17 then
begin
for i := 1 to 6 do
begin
MacAddress[i] := StrToIntDef('$' + copy(sData, 1, 2), 0);
sData := copy(sData, 4, 17);
end;
end;

for i := 1 to 6 do
WR.Waker[i] := $FF;
for i := 0 to 15 do
WR.MAC[i] := MacAddress;
// Create UDP and Broadcast data structure
UDP := TIdUDPClient.Create(nil);
UDP.Host := '255.255.255.255';
UDP.Port := 32767;
UDP.BroadCastEnabled := true;
UDP.SendBuffer(WR, SizeOf(TWakeRecord));
UDP.BroadcastEnabled := false;
UDP.Free;
end;


2008. október 14., kedd

Change the color of a disabled TEdit

Problem/Question/Abstract:

How can I change the color of a disabled (Edit1.Enabled := false;) control? I do not want the normal grey color.

Answer:

Two options: Place the control on a panel and disable the panel instead of the control. This way the color stays to whatever you set it. Or make a descendent and take over the painting when it is disabled. Here is an example:

unit PBExEdit;

interface

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

type
TPBExEdit = class(Tedit)
private
{ Private declarations }
FDisabledColor: TColor;
FDisabledTextColor: TColor;
procedure WMPaint(var msg: TWMPaint); message WM_PAINT;
procedure WMEraseBkGnd(var msg: TWMEraseBkGnd); message WM_ERASEBKGND;
procedure SetDisabledColor(const Value: TColor); virtual;
procedure SetDisabledTextColor(const Value: TColor); virtual;
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(aOwner: TComponent); override;
published
{ Published declarations }
property DisabledTextColor: TColor read FDisabledTextColor write
SetDisabledTextColor
default clGrayText;
property DisabledColor: TColor read FDisabledColor write SetDisabledColor default
clWindow;
end;

procedure Register;

implementation

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

constructor TPBExEdit.Create(aOwner: TComponent);
begin
inherited;
FDisabledColor := clWindow;
FDisabledTextColor := clGrayText;
end;

procedure TPBExEdit.SetDisabledColor(const Value: TColor);
begin
if FDisabledColor <> Value then
begin
FDisabledColor := Value;
if not Enabled then
Invalidate;
end;
end;

procedure TPBExEdit.SetDisabledTextColor(const Value: TColor);
begin
if FDisabledTextColor <> Value then
begin
FDisabledTextColor := Value;
if not Enabled then
Invalidate;
end;
end;

procedure TPBExEdit.WMEraseBkGnd(var msg: TWMEraseBkGnd);
var
canvas: TCanvas;
begin
if Enabled then
inherited
else
begin
canvas := TCanvas.Create;
try
canvas.Handle := msg.DC;
SaveDC(msg.DC);
try
canvas.Brush.Color := FDisabledColor;
canvas.Brush.Style := bsSolid;
canvas.Fillrect(clientrect);
msg.result := 1;
finally
RestoreDC(msg.DC, -1);
end;
finally
canvas.free
end;
end;
end;

procedure TPBExEdit.WMPaint(var msg: TWMPaint);
var
canvas: TCanvas;
ps: TPaintStruct;
callEndPaint: Boolean;
begin
if Enabled then
inherited
else
begin
callEndPaint := False;
canvas := TCanvas.Create;
try
if msg.DC <> 0 then
begin
canvas.Handle := msg.DC;
ps.fErase := true;
end
else
begin
BeginPaint(handle, ps);
callEndPaint := true;
canvas.handle := ps.hdc;
end;
if ps.fErase then
Perform(WM_ERASEBKGND, canvas.handle, 0);
SaveDC(canvas.handle);
try
canvas.Brush.Style := bsClear;
canvas.Font := Font;
canvas.Font.Color := FDisabledTextColor;
canvas.TextOut(1, 1, Text);
finally
RestoreDC(canvas.handle, -1);
end;
finally
if callEndPaint then
EndPaint(handle, ps);
canvas.free
end;
end;
end;

end.


2008. október 13., hétfő

Calling a C++ DLL which exports a class

Problem/Question/Abstract:

As I stated in an earlier article, it's possible to get an object-reference out from a DLL. This technique is known under the name DLL+. But how about the DLL is written in c++?

Answer:

First of all, you have to translate the header-file (should be delivered with the DLL), which is like an interface-section in ObjectPascal. Headers in c usually contain all sorts of definitions which are relevant outside the
module. In our c++ example it looks like:

/*FILE: income.h */
class CIncome
{
public:
virtual double __stdcall GetIncome( double aNetto ) = 0 ;
virtual void   __stdcall SetRate( int aPercent, int  aYear ) = 0 ;
virtual void   __stdcall FreeObject() = 0 ;
} ;

Then you translate it to an Abstract Class in a unit of her own:

//FILE: income.pas
interface
type
IIncome = class
public
function GetIncome(const aNetto: double): double;
virtual; stdcall; abstract;
procedure SetRate(const aPercent: Integer; aYear: integer);
virtual; stdcall; abstract;
procedure FreeObject; virtual; stdcall; abstract;
end;

In the c++ dll, there is a procedure FreeObject this is necessary because of differences in memory management between C++ and ObjectPascal:

void __stdcall FreeObject()
{
delete this ;
}

When you call the DLL written in C or C++, you have to use the stdcall or cdecl convention. Otherwise, you
will end up in violation troubles and from time to time the application may crash. By the way the DLL, you are calling, should be on the search path;).

So these conventions pass parameters from right to left. With this convention, the caller (that's Delphi)has to remove the parameters from the stack when the call returns.

At least the DLL-call is simple:

incomeRef: IIncome; //member of the reference

function CreateIncome: IIncome;
stdcall; external('income_c.dll');

procedure TfrmIncome.FormCreate(Sender: TObject);
begin
incomeRef := createIncome;
end;

procedure TfrmIncome.btncplusClick(Sender: TObject);
var
cIncome: Double;
begin
// this is the c++ dll+ call ;)
incomeRef.SetRate(strToInt(edtZins.text),
strToInt(edtJahre.text));
cIncome := incomeRef.GetIncome(StrToFloat(edtBetrag.Text));
edtBetrag.text := Format('%f', [cIncome]);
end;


Component Download: http://max.kleiner.com/download/cpluscall.ziphttp://max.kleiner.com/download/cpluscall.zip

2008. október 12., vasárnap

Wait until a TForm is actually painted on screen


Problem/Question/Abstract:

How can I wait until the form is actually painted on screen, before starting the processing so that I can be sure that any exceptions are displayed after the form is painted. I've considered a short timer in the OnCreate. Is there a better way (i.e. catching a Windows message)?

Answer:

Use an custom message:

const
  UM_AFTERSHOW = WM_USER + 1001;

type
  TMyForm = class(TForm)
    procedure FormShow(Sender: TObject);
  private
    procedure UMAfterShow(var Msg: TMessage); message UM_AFTERSHOW;
  end;

implementation

procedure TMyForm.FormShow(Sender: TObject);
begin
  PostMessage(Self.Handle, UM_AFTERSHOW, 0, 0);
end;

procedure TMyForm.UMAfterShow(var Msg: TMessage);
begin
  {your code here}
end;

2008. október 11., szombat

How to write a custom TAction to control the visibility of a TStatusBar


Problem/Question/Abstract:

I am trying to write a custom action that will set the visible property of a TStatusBar on and off. I assigned this action to a menu item and when I select this menu item at runtime the status bar is hidden. The problem is that the menu item (connected to the action) is disabled, so I can't view the statusbar again. I think that it's a matter of how the TMenuActionLink behaves (the action controls the enabled property of the menu ). I tried to set the enabled property in the action to true , but no avail. The menu is still disabled. Is there any way to do this?

Answer:

I think that the best solution would be to write an action, which will have a StatusBar property and, in case this property was assigned, set the statusbar's visibility in the overridden Execute method. Here's an example:

{ ... }
TMyAction = class(TAction)
protected
  FStatusBar: TStatusBar;
  procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  procedure SetStatusBar(AValue: TStatusBar);
public
  constructor Create(AOwner: TComponent); override;
  function Execute: Boolean; override;
published
  property StatusBar: TStatusBar read FStatusBar write SetStatusBar;
end;

{ ... }

constructor TMyAction.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  DisableIfNoHandler := false;
  FStatusBar := nil;
  Caption := 'Turn On/ Off Status Bar';
end;

function TMyAction.Execute: Boolean;
begin
  Result := inherited Execute;
  if Assigned(FStatusBar) then
  begin
    FStatusBar.Visible := not FStatusBar.Visible;
    Checked := FStatusBar.Visible;
  end;
end;

procedure TMyAction.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if (Operation = opRemove) and (AComponent = StatusBar) then
    StatusBar := nil;
end;

procedure TMyAction.SetStatusBar(AValue: TStatusBar);
begin
  if FStatusBar <> AValue then
  begin
    FStatusBar := AValue;
    if Assigned(FStatusBar) then
    begin
      FStatusBar.FreeNotification(Self);
      Checked := FStatusBar.Visible;
    end
    else
      Checked := false;
  end;
end;

2008. október 10., péntek

TMediaPlayer: What track am I on?


Problem/Question/Abstract:

TMediaPlayer: What track am I on?

Answer:

Although writing multimedia applications using Delphi is a three-step process (click, drag and drop!), some people still ask how to find out what track is currently playing on the CD player. Just get that info, just drop a TMediaPlayer component on the form, with all the properties correctly set and bound to the CD player. Also, add "MMSystem" to the uses clause in the calling form. To complete, create a TTimer and put the code below in its OnTimer event:

var
  Trk, Min, Sec: word;
begin
  with MediaPlayer1 do
  begin
    Trk := MCI_TMSF_TRACK(Position);
    Min := MCI_TMSF_MINUTE(Position);
    Sec := MCI_TMSF_SECOND(Position);
    Label1.Caption := Format('%.2d', [Trk]);
    Label2.Caption := Format('%.2d:%.2d', [Min, Sec]);
  end;
end;

2008. október 9., csütörtök

How to create a brush using CreateBrushIndirect


Problem/Question/Abstract:

How to create a brush using CreateBrushIndirect

Answer:

procedure TForm1.Button1Click(Sender: TObject);
var
  Region: HRGN;
  LogBrush: TLogBrush;
  NewBrush: hBrush;
begin
  with LogBrush do
  begin
    lbStyle := BS_HATCHED;
    lbColor := clBlue;
    lbHatch := HS_CROSS
  end;
  NewBrush := CreateBrushIndirect(LogBrush);
  Region := CreateEllipticRgnIndirect(PaintBox1.BoundsRect);
  FillRgn(PaintBox1.Canvas.Handle, Region, NewBrush);
  DeleteObject(NewBrush);
  DeleteObject(Region)
end;

2008. október 8., szerda

SQL Server Security Setting


Problem/Question/Abstract:

How I can Set SQL Server Security in SQL Server Authentication without windows Authentication.

Answer:

You must install new sql server and set authentication in mix mode or SQL Server and Windows authentication.

In Sql Manager, Add New Group and add New SQL Server and set connection type to SQL Server Authentication with checking check box (Always Prompt).

In Security/Login Section you should set BUILTIN/Adminstrator to Access Deny.

Every Users with windows authentication should had appropriate access with Deny Access.

Every Users with Funcional access such as creating, droping,... tables should had SQL Server Authentication with appropriate Password.

Sa should SQL Authentication wih appropriate password.

2008. október 7., kedd

Change the color of the tabs on a PageControl


Problem/Question/Abstract:

How do I change the color of the tabs on a PageControl?

Answer:

The example below uses the OnDrawCell event to change the colour of the active Tab and of the Font used:

procedure TForm1.TabControl1DrawTab(Control: TCustomTabControl;
  TabIndex: Integer; const Rect: TRect; Active: Boolean);
var
  s: string;
  r: TRect;
begin
  s := form1.TabControl1.Tabs.Strings[tabindex];
  r := Rect;
  with Control.Canvas do
  begin
    if Active then
    begin
      Brush.Color := clinfoBK;
      Font.Color := clBlue;
    end;
    Windows.FillRect(Handle, r, Brush.Handle);
    OffsetRect(r, 0, 1);
    DrawText(Handle, PChar(s), Length(s), r, DT_CENTER or DT_SINGLELINE or
      DT_VCENTER);
  end;
end;

2008. október 6., hétfő

How to create a pie chart


Problem/Question/Abstract:

Can anyone point me in the direction of an code snippet for drawing a pie/ circle given the following definition:

procedure Pie(ACanvas: TCanvas; ACenter: TPoint; ARadius: Integer; AStartDeg, AEndDeg: Float);

which draws a pie as a section of a circle starting at AStartDeg dregrees (0 being straight up - or whatever) and ending at AEndDeg (360 beging straight up - or whatever) using ACanvas default drawing parameters (brush and pen).

Answer:

The TCanvas.Pie can be used to get what you want - with a little trig. The following has 0 degrees being to the right (as in trig classes) witha positive angle in the counterclockwise direction (as in trig classes):

uses
  Math; {DegToRad}

procedure DrawPieSlice(const Canvas: TCanvas; const Center: TPoint;
  const Radius: Integer; const StartDegrees, StopDegrees: Double);
const
  Offset = 0; {to make 0 degrees start to the right}
var
  X1, X2, X3, X4: Integer;
  Y1, Y2, Y3, Y4: Integer;
begin
  X1 := Center.X - Radius;
  Y1 := Center.Y - Radius;
  X2 := Center.X + Radius;
  Y2 := Center.Y + Radius;
  {negative signs on "Y" values to correct for "flip" from normal math defintion for "Y" dimension}
  X3 := Center.X + Round(Radius * COS(DegToRad(Offset + StartDegrees)));
  Y3 := Center.y - Round(Radius * SIN(DegToRad(Offset + StartDegrees)));
  X4 := Center.X + Round(Radius * COS(DegToRad(Offset + StopDegrees)));
  Y4 := Center.y - Round(Radius * SIN(DegToRad(Offset + StopDegrees)));
  Canvas.Pie(X1, Y1, X2, Y2, X3, Y3, X4, Y4);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Center: TPoint;
  Bitmap: TBitmap;
  Radius: Integer;
begin
  Assert(Image1.Width = Image1.Height); {Assume square for now}
  Bitmap := TBitmap.Create;
  try
    Bitmap.Width := Image1.Width;
    Bitmap.Height := Image1.Height;
    Bitmap.PixelFormat := pf24bit;
    Bitmap.Canvas.Brush.Color := clRed;
    Bitmap.Canvas.Pen.Color := clBlue;
    Center := Point(Bitmap.Width div 2, Bitmap.Height div 2);
    Radius := Bitmap.Width div 2;
    DrawPieSlice(Bitmap.Canvas, Center, Radius, 0, 30);
    DrawPieSlice(Bitmap.Canvas, Center, Radius, 90, 120);
    Image1.Picture.Graphic := Bitmap;
  finally
    Bitmap.Free;
  end;
end;

2008. október 5., vasárnap

How to control the volume of an audio card without disturbing the balance


Problem/Question/Abstract:

I can easily adjust the left and right channels independently on an audio card using the mixer API. My question is: How do I have one trackbar set the overall volume without disturbing the current balance between channels? Or how do I use two trackbars, but "lock" them together? Basically I need to directly manipulate one channel, but have the other one follow while keeping the relative relationship between the two channels the same.

Answer:

Here is the method I use. It uses a mixer component though, but I believe the code should make sense.


procedure setVolume(percent: real);
var
  value: integer;
  balance: real;
  highChannel, lowChannel: byte;
begin
  if percent > 1 then
    percent := 1;
  if percent < 0 then
    percent := 0;
  value := high(word) - round(percent * high(word));
  if value > high(word) then
    value := high(word);
  if value < 0 then
    value := 0;
  aMixer.outputs[0].inputs[0].volume.beginUpdate;
  if (aMixer.outputs[0].inputs[0].Volume.position[0] = 0) and
    (aMixer.outputs[0].inputs[0].Volume.position[1] = 0) then
  begin
    {Both are muted, get old balance}
    if oldBalance = 10 then
      {oldBalance is set to 10 on program start (dummy value)}
    begin
      balance := 1;
      highChannel := 0;
      lowChannel := 1
    end
    else
    begin
      if oldBalance < 0 then
      begin
        highChannel := 0;
        lowChannel := 1;
        balance := oldBalance * -1
      end
      else
      begin
        highChannel := 1;
        lowChannel := 0;
        balance := oldBalance
      end
    end
  end
  else
  begin
    if aMixer.outputs[0].inputs[0].Volume.position[0] >
      aMixer.outputs[0].inputs[0].Volume.position[1] then
    begin
      highChannel := 0;
      lowChannel := 1
    end
    else
    begin
      highChannel := 1;
      lowChannel := 0
    end;
    balance := aMixer.outputs[0].inputs[0].Volume.position[lowChannel] /
      aMixer.outputs[0].inputs[0].Volume.position[highChannel]
  end;
  aMixer.outputs[0].inputs[0].Volume.position[highChannel] := value;
  aMixer.outputs[0].inputs[0].Volume.position[lowChannel] := round(value * balance);
  if value > 0 then
  begin
    oldBalance := balance;
    if highChannel = 0 then
      oldBalance := oldBalance * -1
  end;
  aMixer.outputs[0].inputs[0].volume.endUpdate
end;

2008. október 4., szombat

Check if the current printer is ready to print in color


Problem/Question/Abstract:

How can I find out whether the current printer is ready to print in colour, rather than just capable of printing in colour?

Answer:

Solve 1:

This works for some but not all printers, depending on the driver capabilities:

{ ... }
var
  Dev, Drv, Prt: array[0..255] of Char;
  DM1: PDeviceMode;
  DM2: PDeviceMode;
  Sz: Integer;
  DevM: THandle;
begin
  Printer.PrinterIndex := -1;
  Printer.GetPrinter(Dev, Drv, Prt, DevM);
  DM1 := nil;
  DM2 := nil;
  Sz := DocumentProperties(0, 0, Dev, DM1^, DM2^, 0);
  GetMem(DM1, Sz);
  DocumentProperties(0, 0, Dev, DM1^, DM2^, DM_OUT_BUFFER);
  if DM1^.dmColor > 1 then
    Label1.Caption := Dev + ': Color'
  else
    Label1.Caption := Dev + ': Black and White';
  if DM1^.dmFields and DM_Color <> 0 then
    Label2.Caption := 'Printer supports color printing'
  else
    Label2.Caption := 'Printer does not support color printing';
  FreeMem(DM1);
end;


Solve 2:

function IsColorPrinter: bool;
var
  Device: array[0..MAX_PATH] of char;
  Driver: array[0..MAX_PATH] of char;
  Port: array[0..MAX_PATH] of char;
  hDMode: THandle;
  PDMode: PDEVMODE;
begin
  result := False;
  Printer.PrinterIndex := Printer.PrinterIndex;
  Printer.GetPrinter(Device, Driver, Port, hDMode);
  if hDMode <> 0 then
  begin
    pDMode := GlobalLock(hDMode);
    if pDMode <> nil then
    begin
      if ((pDMode^.dmFields and dm_Color) = dm_Color) then
      begin
        result := True;
      end;
      GlobalUnlock(hDMode);
    end;
  end;
end;

function SetPrinterColorMode(InColor: bool): bool;
var
  Device: array[0..MAX_PATH] of char;
  Driver: array[0..MAX_PATH] of char;
  Port: array[0..MAX_PATH] of char;
  hDMode: THandle;
  PDMode: PDEVMODE;
begin
  result := False;
  Printer.PrinterIndex := Printer.PrinterIndex;
  Printer.GetPrinter(Device, Driver, Port, hDMode);
  if hDMode <> 0 then
  begin
    pDMode := GlobalLock(hDMode);
    if pDMode <> nil then
    begin
      if (pDMode^.dmFields and dm_Color) = dm_Color then
      begin
        if (InColor) then
        begin
          pDMode^.dmColor := DMCOLOR_COLOR;
        end
        else
        begin
          pDMode^.dmColor := DMCOLOR_MONOCHROME;
        end;
        result := True;
      end;
      GlobalUnlock(hDMode);
      Printer.PrinterIndex := Printer.PrinterIndex;
    end;
  end;
end;


Solve 3:

It is usually better to use DeviceCapabilities to examine what the printer supports. Unfortunately this will only work on Win2K and XP, not on older platforms.

uses
  printers, winspool;

function PrinterSupportsColor: Boolean;
var
  Device, Driver, Port: array[0..255] of Char;
  hDevMode: THandle;
begin
  Printer.GetPrinter(Device, Driver, Port, hDevmode);
  Result := WinSpool.DeviceCapabilities(Device, Port, DC_COLORDEVICE, nil, nil) <> 0;
end;

2008. október 3., péntek

Bold nodes in standard TTreeview component


Problem/Question/Abstract:

How make a some nodes in standard TTreeview component as bold?

Answer:

Me frequently ask as I in SMReport Explorer form realized selection by the bold font some nodes.

Today I have decided to describe this very simple way (but very useful). It does not require an override of any custom drawing methods/events, creating a new component etc. It's a real standard way.

The standard Windows Treeview control have a few state flags (TVIS_BOLD and TVIS_CUT in our example), due to which it's possible to reach wished.

At first, let's write the procedure SetNodeState:

procedure SetNodeState(node: TTreeNode; Flags: Integer);
var
  tvi: TTVItem;
begin
  FillChar(tvi, SizeOf(tvi), 0);
  tvi.hItem := node.ItemID;
  tvi.Mask := TVIF_STATE;
  tvi.StateMask := TVIS_BOLD or TVIS_CUT;
  tvi.State := Flags;
  TreeView_SetItem(node.Handle, tvi);
end;

And now we can set a wished flags:

SetNodeState(node, TVIS_BOLD) - to set the node as Bold
SetNodeState(node, TVIS_CUT) - to set the node as Cutted
SetNodeState(node, TVIS_BOLD or TVIS_CUT) - to set the node as Bold and Cutted
SetNodeState(node, 0) - to set a node as normal

2008. október 2., csütörtök

How to do syntax highlighting in a TRichEdit


Problem/Question/Abstract:

How to do syntax highlighting in a TRichEdit

Answer:

{Content of the TRichEdit for example:

This is a test to show how to find the @ character in a rich text.
The @ character occurs twice in the text.
}

procedure MarkFirstWord(RE: TRichEdit; TheWord: string; Color: TColor; Style:
  TFontStyles);
var
  i, CharPos, noChars: Integer;
begin
  CharPos := 0;
  noChars := 0;
  for i := 0 to Pred(RE.Lines.Count) do
    noChars := noChars + Length(RE.Lines[i]);
  CharPos := RE.FindText(TheWord, CharPos, noChars, [stWholeWord]);
  RE.SelStart := CharPos;
  RE.SelLength := Length(TheWord);
  RE.SelAttributes.Color := Color;
  RE.SelAttributes.Style := Style;
  RE.SelLength := 0;
end;

procedure MarkAllWords(RE: TRichEdit; TheWord: string; Color: TColor; Style:
  TFontStyles);
var
  i, CharPos, CharPos2, noChars: Integer;
begin
  CharPos := 0;
  noChars := 0;
  for i := 0 to Pred(RE.Lines.Count) do
    noChars := noChars + Length(RE.Lines[i]);
  repeat
    CharPos2 := RE.FindText(TheWord, CharPos, noChars, [stWholeWord]);
    CharPos := CharPos2 + 1;
    RE.SelStart := CharPos2;
    RE.SelLength := Length(TheWord);
    RE.SelAttributes.Color := Color;
    RE.SelAttributes.Style := Style;
  until
    charpos = 0;
  RE.SelLength := 0;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  {Will mark only the first occurance of '@' in Red}
  MarkFirstWord(RichEdit1, '@', clRed, [fsBold]);
  {Will mark all occurances of @ in Teal and italic}
  MarkAllWords(RichEdit1, '@', clTeal, [fsItalic, fsBold]);
end;

2008. október 1., szerda

Implement forms that are modal only towards their owner form


Problem/Question/Abstract:

I have a form of which any number of instances can be created and used at once. From each of these forms the user can open another form. This form must however be modal to its creator (the first form), but not to the rest of the application.

Answer:

Create the modal form with the calling form as Owner, not with pplication as owner. In the modal form override the CreateParams method as

{ ... }
inherited;
params.WndParent := (Owner as TForm).handle;

Add a public class function to the modal form:

class function ShowForm(aOwner: TForm): TModalform;

implemented as

class function TModalForm.ShowForm(aOwner: TForm): TModalForm;
begin
  Result := TModalForm.Create(aOwner);
  aOwner.Enabled := False;
  Result.Show;
end;

Use this method to create instances of the form instead of calling the constructor directly. We also need a handler for OnClose that does

Action := caFree;
(Owner as TForm).Enabled := true;

If you do need to communicate back a modal result to the caller you will need to add a public event to the pseudomodal form to which the caller can attach a handler. This event would then be called from the OnClose event handler, passing the modalresult. Note that any buttons that should close the form with a modalresult need to explicitely call the Close method, since the modal message loop is not used that will not happen on its own.