2006. július 31., hétfő

Tile the open windows when calling an external application from your program


Problem/Question/Abstract:

I want to execute an external program from my Delphi application when I click on a button. However, when the program is executed, I want both my Delphi application and the external program to tile side by side (just like in Windows own "Tile windows") vertically.

Answer:

The problem boils down to finding the started programs window handle. Once you have that the tiling boils down to a single call to the TileWIndow API function. The following is starting an application and tiling its window with the current application window.

procedure StartAppAndTileWindows(const appname: string; const windows: array of HWND;
  tileHorizontal: Boolean = true);
const
  tileflags: array[Boolean] of UINT = (MDITILE_VERTICAL, MDITILE_HORIZONTAL);
var
  currhwnd: HWND;
  newhwnd: HWND;
  counter: Integer;
  wndArray: array of HWND;
begin
  currhwnd := GetForegroundWindow;
  if ShellExecute(currhwnd, nil, Pchar(appname), nil, nil, SW_SHOWNORMAL) > 32 then
  begin
    counter := 0;
    repeat
      Sleep(100);
      newhwnd := GetForegroundWindow;
      if (newhwnd <> 0) and (newhwnd <> currhwnd) then
      begin
        SetLength(wndArray, High(windows) + 2);
        Move(windows[0], wndArray[0], (High(windows) + 1) * sizeof(HWND));
        wndArray[High(wndArray)] := newhwnd;
        TileWindows(0, {tile on desktop}
          tileflags[tileHorizontal], {mode as per param}
          nil, {use desktops full client area}
          Length(wndArray), {count of windows to tile}
          @wndArray[0]); {array of window handles to tile}
        Exit;
      end
      else
        Inc(counter);
    until
      counter >= 20;
    raise Exception.CreateFmt('Could not find main window of %s', [appname]);
  end;
  raise Exception.CreateFmt('Could not execute %s', [appname]);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  StartAppAndTileWindows('notepad.exe', [handle]);
end;

2006. július 30., vasárnap

Display a long text in a TLabel with an end ellipsis


Problem/Question/Abstract:

How to display a long text in a TLabel with an end ellipsis

Answer:

You must turn off AutoSize for the label, of course. Otherwise the current content of the label determines where the string will be trimmed.

{ ... }
var
  S: string;
  R: TRect;
  { ... }
begin
  {Get the text you want into S}
  S := 'this is way too long to fit into a label - trim it down';
  {Make it unique, because DrawText is going to change it}
  UniqueString(S);
  {Get the rectangle available for drawing}
  R := Label1.ClientRect;
  {Make sure you're using the same font the label will use}
  Label1.Canvas.Font := Label1.Font;
  {Ask Windows to trim it up with out replacing "&" by an underscore}
  DrawText(Label1.Canvas.Handle, PChar(S), Length(S), R, DT_END_ELLIPSIS or
    DT_MODIFYSTRING or DT_NOPREFIX);
  {Now plug that trimmed text into Label1}
  Label1.Caption := S;
  { ... }

2006. július 29., szombat

Rotate a polygon


Problem/Question/Abstract:

How to rotate a polygon

Answer:

Start a new project add this OnPaint handler to the form:

procedure TForm1.FormPaint(Sender: TObject);

  function Translate(APoint: TPoint; AX, AY: Integer): TPoint;
  begin
    Result.X := APoint.X + AX;
    Result.Y := APoint.Y + AY;
  end;

  function Rotate(APoint: TPoint; AAngle: Double): TPoint;
  begin
    Result.X := Round(APoint.X * Cos(AAngle) - APoint.Y * Sin(AAngle));
    Result.Y := Round(APoint.X * Sin(AAngle) + APoint.Y * Cos(AAngle));
  end;

var
  po: array of TPoint;
  pr: array of TPoint;
  i: Integer;
begin
  {Setup the polygon}
  SetLength(po, 5);
  SetLength(pr, Length(po));
  po[0] := Point(-50, -50);
  po[1] := Point(50, -50);
  po[2] := Point(50, 50);
  po[3] := Point(-50, 50);
  po[4] := Point(-75, 0);
  for i := 0 to Pred(Length(po)) do
  begin
    pr[i] := Translate(Rotate(po[i], GetTickCount / 1000), ClientWidth div 2,
      ClientHeight div 2);
  end;
  Canvas.Polygon(pr);
  Invalidate;
end;

2006. július 28., péntek

Mouse cursor hourglass/normal changer unit


Problem/Question/Abstract:

Howto change the mousecursor into a hourglass/normal.

Answer:

unit waitcursor;
{ WaitCursor  -  Copyright (c) 2001 by E.J.Molendijk

  A mousecursor hourglass/normal changer tool. Supports layered requests.

  WCursor.SetWait     = mouse cursor as Hourglass
  WCursor.SetWaitSQL  = mouse cursor as SQL Hourglass
  WCursor.SetNormal   = mouse cursor as normal

  example:
      WCursor.setWait;
      try
        DoSometingVerySlow;
      finally
        WCursor.setNormal;
      end;
}

interface

type
  TWaitCursor = class
  private
    FCursor: integer;
    FCnt: integer;
  public
    constructor Create;
    destructor Destroy; override;
    procedure SetCursor(Index: integer);
    procedure SetWait;
    procedure SetWaitSQL;
    procedure SetNormal;
  end;

var
  WCursor: TWaitCursor;

implementation

uses Forms, Controls;

constructor TWaitCursor.Create;
begin
  inherited;
  // depth is zero
  FCnt := 0;

  // remember default cursor
  FCursor := Screen.Cursor;
end;

destructor TWaitCursor.Destroy;
begin
  // Reset to default cursor
  Screen.Cursor := FCursor;
  inherited;
end;

procedure TWaitCursor.SetCursor(Index: integer);
begin
  // select cursor
  Screen.Cursor := Index;
end;

procedure TWaitCursor.SetNormal;
begin
  // decrease depth
  if FCnt > 0 then
    Dec(FCnt);

  // if we reach depth 0 we restore default cursor
  if FCnt = 0 then
    SetCursor(FCursor);
end;

procedure TWaitCursor.SetWait;
begin
  // increase depth
  Inc(FCnt);

  // select wait cursor
  SetCursor(crHourglass);
end;

procedure TWaitCursor.SetWaitSQL;
begin
  // increase depth
  Inc(FCnt);

  // select wait cursor
  SetCursor(crSQLWait);
end;

initialization
  WCursor := TWaitCursor.Create;
finalization
  WCursor.Free;
end.

2006. július 27., csütörtök

Type casting and type checking with Interfaces


Problem/Question/Abstract:

If you are new to interfaces and you are use to type casting and type checking with objects you will find that Interfaces must be dealt with differently.  This artical shows you the basics to get started with doing type checking and type casting with Interfaces.

Answer:

If you are like me, you are impatient and can get the point without a bunch of explaining and you hate reading of bunch of stuff just to get a couple bits of info. So for you guys/girls here is the summary and then if you like you can read the rest of the artical:

SUMMARY

First off, your interface must have a GUID, use delphi Shift-Ctrl-G to create one. It should be entered as the first line in your interface definition, ex:

ITypeX = interface
  ['{A002AF60-5684-11D5-B4F9-525405F6BE8D}']
  procedure ShowX;
end;

Object Type Cast

Customer := TCustomer(SomeObject);

Interface Way

You cannot do it this way with interfaces

Object Type Cast (with type checking)

Customer := SomeObject as TCustomer;

Interface Way

Customer := SomeInterface as ICustomer; // assuming Customer is declared as ICustomer

Object Type Checking

if SomeObject is TCustomer then
  // Do Something

interface Way

if SomeInterface.QueryInterface(ICustomer, Customer) = S_OK then
begin
  Customer.DoSomething;
end;

Read further if you like more explaining

First I will summarize type casting and type checking with objects to get us on the same ground and then show how it is done when using interfaces.

Type casting with objects is done in 2 ways

Direct without type checking

Customer := TCustomer(SomeObject);

Using "built in" type checking

Customer := SomeObject as TCustomer

The second will raise an exception if SomeObject is not a TCustomer object or a descendent of one.

Type checking with objects can be done as....

if (SomeObject is TCustomer) then
  // Do something

Now for Interfaces, you MUST have a GUID for your interface before you can do any type checking or type casting. To make your interface have a GUID simply insert it as the first line in your interface definition like this:

ITypeX = interface
  ['{A002AF60-5684-11D5-B4F9-525405F6BE8D}']
  procedure ShowX;
end;

First off you may ask where the heck do I get a GUID, in Delphi just put your cursor where you want the GUID and then press Shift-Ctrl-G and Delphi will insert one for you.  That easy.

These are the interfaces I will use in my explaining:

ITypeX = interface
  ['{A002AF60-5684-11D5-B4F9-525405F6BE8D}']
  procedure ShowX;
end;

ITypeY = interface
  ['{56DA8BE0-5685-11D5-B4F9-525405F6BE8D}']
  procedure ShowY;
end;

Classes to implement them
TTypeX = class(TInterfacedObject, ITypeX)
public
  procedure ShowX;
end;

TTypeY = class(TInterfacedObject, ITypeY)
public
  procedure ShowY;
end;

TTypeXY = class(TInterfacedObject, ITypeX, ITypeY)
public
  procedure ShowX;
  procedure ShowY;
end;

Type casting.

The following code will not work!

procedure TForm_Interfaces.TypeCastXtoY;
var
  X: ITypeX;
begin
  X := TTypeXY.Create;
  ITypeY(X).ShowY;
end;

What happens is that the method ShowX gets called rather than ShowY, I do not know the technical reason... but bottom line, it does not work and you should not type cast this way.

The proper way to do it is this way:

procedure TForm_Interfaces.TypeCastXtoY;
var
  X: ITypeX;
begin
  X := TTypeXY.Create;
  (X as ITypeY).ShowY;
end;

You must use the as operator, also this way of doing it will raise an exception if X in some way does not implement Y.  If you need to do type checking then read more...

The "is" operator does not work with interfaces, so code like this will not compile

if X is ITypeY then
  // Do Something

The proper way to do it is this way:

U := TTypeY.Create;

if U.QueryInterface(ITypeX, Ret) = S_OK then
  ShowMessage('Supports ITypeX')
else
  ShowMessage('DOES NOT Support ITypeX');

if U.QueryInterface(ITypeY, Ret) = S_OK then
  ShowMessage('Supports ITypeY')
else
  ShowMessage('DOES NOT Support ITypeY')

Ret will contain a reference to the interface when the result is S_OK.

Note: If you read the Delphi help for QueryInterface you will see this line
After successfully obtaining an interface by calling QueryInterface, clients should increase the reference count by calling the IUnknown AddRef method.

I wrote code to verify this statement and found that the reference count was incermented and I did not have to call AddRef.

Full Source of my Research, you can also download the app from my website using the component link.

PAS

unit Frm_Interfaces;
{
  Discovery Typecasting interfaces

  You cannot typecast an interface in this manner,
  seems your method pointers are messed up
  X:= ITypeX(SomeInterface);
  // See "TypeCastXtoY" method to see the error, notice the message

  You must type cast in either of 2 ways
  1. X:= SomeInterface as ITypeX
  // See "TypeCastXtoY" method, this can raise an exception

  or
  2. if SomeInterface.QueryInterface(ITypeX, X) = S_OK then
       X.ShowX;
  // See "TypeChecking" method, this does not raise an exception

  Difference between 1 and 2 is that 1 will raise an exception and 2 will not.

  *** IMPORTANT ***
  In order for this to work you MUST include a GUID in your interface, it is
  the first line in the interface definition, use keyboard Shift-Ctrl-G to
  create a GUID.
}

interface

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

type

  ITypeX = interface
    ['{A002AF60-5684-11D5-B4F9-525405F6BE8D}']
    procedure ShowX;
  end;

  ITypeY = interface
    ['{56DA8BE0-5685-11D5-B4F9-525405F6BE8D}']
    procedure ShowY;
  end;

  TTypeX = class(TInterfacedObject, ITypeX)
  public
    procedure ShowX;
  end;

  TTypeY = class(TInterfacedObject, ITypeY)
  public
    procedure ShowY;
  end;

  TTypeXY = class(TInterfacedObject, ITypeX, ITypeY)
  public
    procedure ShowX;
    procedure ShowY;
  end;

  TForm_Interfaces = class(TForm)
    FbtnTestCreate: TButton;
    FbtnTypeCastX2Y: TButton;
    FbtnTypeCastU2Y: TButton;
    FbtnTypeChecking: TButton;
    procedure Ev_FbtnTestCreateClick(Sender: TObject);
    procedure Ev_FbtnTypeCastX2YClick(Sender: TObject);
    procedure Ev_FbtnTypeCastU2YClick(Sender: TObject);
    procedure Ev_FbtnTypeCheckingClick(Sender: TObject);
  private
    { Private declarations }
    procedure TestCreate;
    procedure TypeCastXtoY;
    procedure TypeCastUnknownToY;
    procedure TypeChecking;
  public
    { Public declarations }
  end;

var
  Form_Interfaces: TForm_Interfaces;

implementation

{$R *.DFM}

{ TTypeX }

procedure TTypeX.ShowX;
begin
  ShowMessage('TTypeX.ShowX: Supports "ITypeX" only');
end;

{ TTypeY }

procedure TTypeY.ShowY;
begin
  ShowMessage('TTypeY.ShowY: Supports "ITypeY" only');
end;

{ TTypeXY }

procedure TTypeXY.ShowX;
begin
  ShowMessage('TTypeXY.ShowX: Supports both "ITypeX" and "ITypeY"');
end;

procedure TTypeXY.ShowY;
begin
  ShowMessage('TTypeXY.ShowY: Supports both "ITypeX" and "ITypeY"');
end;

{ TForm1 }

procedure TForm_Interfaces.Ev_FbtnTestCreateClick(Sender: TObject);
begin
  TestCreate;
end;

procedure TForm_Interfaces.Ev_FbtnTypeCastX2YClick(Sender: TObject);
begin
  TypeCastXtoY;
end;

procedure TForm_Interfaces.Ev_FbtnTypeCastU2YClick(Sender: TObject);
begin
  TypeCastUnknownToY;
end;

procedure TForm_Interfaces.Ev_FbtnTypeCheckingClick(Sender: TObject);
begin
  TypeChecking;
end;

procedure TForm_Interfaces.TestCreate;
var
  TypeX: ITypeX;
  TypeY: ITypeY;
begin
  TypeX := TTypeX.Create;
  TypeX.ShowX;

  TypeY := TTypeY.Create;
  TypeY.ShowY;

  // Implements both
  TypeX := TTypeXY.Create;
  TypeX.ShowX;

  TypeY := TTypeXY.Create;
  TypeY.ShowY;
end;

procedure TForm_Interfaces.TypeCastXtoY;
var
  X: ITypeX;
begin
  // Notice the message, this does not work.
  X := TTypeXY.Create;
  ITypeY(X).ShowY;

  // This does work
  (X as ITypeY).ShowY;
end;

procedure TForm_Interfaces.TypeCastUnknownToY;
var
  U: IUnknown;
  Y: ITypeY;
begin
  U := TTypeXY.Create;
  Y := U as ITypeY;
  Y.ShowY;
end;

procedure TForm_Interfaces.TypeChecking;
var
  U: IUnknown;
  Ret: IUnknown;
begin
  // Select whichever you want below
//********************************
  //  U:= TTypeXY.Create;
  //  U:= TTypeX.Create;
  U := TTypeY.Create;
  //********************************

    // "Is" does not work with interfaces.
  {
    if U is ITypeX then
      ShowMessage('Supports ITypeX')
    else
      ShowMessage('DOES NOT Support ITypeX')

    if U is ITypeY then
      ShowMessage('Supports ITypeY')
    else
      ShowMessage('DOES NOT Support ITypeY')
  }

  if U.QueryInterface(ITypeX, Ret) = S_OK then
    ShowMessage('Supports ITypeX')
  else
    ShowMessage('DOES NOT Support ITypeX');

  if U.QueryInterface(ITypeY, Ret) = S_OK then
    ShowMessage('Supports ITypeY')
  else
    ShowMessage('DOES NOT Support ITypeY')
end;

end.

DFM

object Form_Interfaces: TForm_Interfaces
  Left = 273
    Top = 278
    BorderStyle = bsDialog
    Caption = 'Experimenting with Typecasting interfaces'
    ClientHeight = 222
    ClientWidth = 341
    Color = clBtnFace
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = []
    OldCreateOrder = False
    Position = poScreenCenter
    PixelsPerInch = 96
    TextHeight = 13
    object FbtnTestCreate: TButton
    Left = 118
      Top = 39
      Width = 105
      Height = 25
      Caption = 'FbtnTestCreate'
      TabOrder = 0
      OnClick = Ev_FbtnTestCreateClick
  end
  object FbtnTypeCastX2Y: TButton
    Left = 118
      Top = 79
      Width = 105
      Height = 25
      Caption = 'FbtnTypeCastX2Y'
      TabOrder = 1
      OnClick = Ev_FbtnTypeCastX2YClick
  end
  object FbtnTypeCastU2Y: TButton
    Left = 118
      Top = 119
      Width = 105
      Height = 25
      Caption = 'FbtnTypeCastU2Y'
      TabOrder = 2
      OnClick = Ev_FbtnTypeCastU2YClick
  end
  object FbtnTypeChecking: TButton
    Left = 118
      Top = 159
      Width = 105
      Height = 25
      Caption = 'FbtnTypeChecking'
      TabOrder = 3
      OnClick = Ev_FbtnTypeCheckingClick
  end
end

Component Download: http://www.eggcentric.com/InterfaceTypeCast.zip

2006. július 26., szerda

The ActiveX Foundry


Problem/Question/Abstract:

The ActiveX Foundry

Answer:

It's spring again, a time when many eyes look to Borland for a new generation of Delphi development tools. This spring brings us the third incarnation of Delphi, a lush garden of new tools and technologies designed to expedite business-critical data handling and analysis.

The most exotic fruits of Borland's year-long labors include the ActiveX Component Foundry, Business Object Broker, multi-tier Remote Data Brokers, Distributed COM, Web Deployment, and a suite of IDE features known collectively as Code Insight. These are just the big ones - items major enough to earn buzzwords on the box cover.

There are so many new technical bits in Delphi 3 that it's difficult to grasp the scope of the whole product simply by looking at its technical pieces. Let's first take a high-level tour, focusing on how Delphi 3's new tools enable new ways to solve tough development and deployment problems. After all, you have a whole year of Delphi Informant articles ahead of you to pick apart the technical bits.

ActiveX Component Foundry

Delphi 3 goes completely overboard to support, adopt, and internalize Microsoft's ActiveX technology initiative. Delphi 3 is to ActiveX creation as Delphi 1 was to Windows application creation. Through a combination of new language extensions, new classes, and new design-time wizards and tools (see Figure 1), Delphi 3 cuts through the Microsoft rhetoric to deliver what Microsoft has been trying to do for ages: a development environment that makes creation, debugging, deployment, and maintenance of ActiveX controls, COM servers, and COM interfaces simple, easy, and reliable.


Figure 1: One aspect of the ActiveX Component Foundry, a page of wizards makes it short work to create an ActiveX control. Delphi 3 makes creation, debugging, deployment, and maintenance of ActiveX controls, COM servers, and COM interfaces simple, easy, and reliable.

Revisionist Terminology

Microsoft's Component Object Model specification, COM, is the standard to which all OLE objects are implemented. COM is the low-level stuff; OLE is a service built on COM. Depending on who you talk to at Microsoft, ActiveX is the new name for OLE Controls (OCXs, the 32-bit replacement for VBXs), the new name for all things formerly known as OLE, or the new name for all things new. Pessimists are already assuming the latter definitions. In any case, ActiveX is also a group of services and standards built on COM interfaces.

Create ActiveX Controls from VCL Components

Delphi 3 has a new wizard that generates an ActiveX control class wrapper around the VCL component you specify (see Figure 2). It's as simple as that. If you write VCL components for a living, you're now just a few button clicks away from selling those components as ActiveX controls to the Visual Basic and C++ markets. If you work in a mixed-tool environment, you can develop your core business objects as VCL components, then spit out ActiveX versions of your work for folks hopelessly shackled to other tools.


Figure 2: Delphi 3 has a new wizard that generates an ActiveX control class wrapper around the VCL component you specify. It's as simple as that.

Create COM Servers and Automation Servers from Scratch

A new OLE type library (typelib) editor (see Figure 3) makes short work of defining a new COM interface definition, saving it into an OLE-standard typelib file, and generating a source code unit with the interface type declaration. You create new COM interfaces whenever you build a new COM server, be it a visual ActiveX control or a non-visual data processing server.


Figure 3: A new OLE type library editor makes short work of defining a new COM interface definition, saving it into an OLE-standard typelib file, and generating a source code unit with the interface type declaration.

OLE typelibs are symbol files that tell other applications what methods are available in your COM server, and how to call them. Just as the IDE form designer and source code editor are linked two-way tools (i.e. modifications to one are reflected immediately in the other), the new typelib editor is also a two-way tool - modifications made to the Pascal interface type declaration source code are reflected in the typelib editor, and vice-versa.

Generate Pascal Declarations from Typelibs

As a side effect of the extensive typelib editor work, you can also generate Object Pascal source-code constants and interface type declarations from any OLE typelib. You can think of this as a vast expansion of the OCX wrapper class generation in Delphi 2. If you can obtain a typelib file for a COM object you want to use in Delphi (ActiveX controls are required to have a typelib), creating Pascal interface declarations to use that COM object are a snap, and considerably more accurate than trying to mechanically convert ambiguous C header files into Pascal declarations. Unfortunately, some COM objects do exist without typelibs; Microsoft's DirectX is probably the biggest offender in this category.

Create VCL components from ActiveX controls

The ActiveX wrapper class generation found in Delphi 2 has been expanded to take advantage of new language features like interface types, and new buzzwords like ActiveX. You can now directly access interfaces provided by ActiveX controls and take advantage of other control features that were previously only available through variant variables (see Figure 4).


Figure 4: You can now directly access interfaces provided by ActiveX controls and take advantage of other control features that were previously only available through variant variables.

ActiveForms

Another nifty spin-off of the core ActiveX development work is the creation of an ActiveX control to encapsulate an entire Delphi form (again, see Figure 1). This is necessary to support ActiveX property pages, but it's also handy for creating mini-application modules that can be automatically downloaded over the Internet and displayed inside a Web browser such as Microsoft Internet Explorer 3.0. The Web browser sees the thing as an ActiveX control, but you can pack an entire application into it.

Web Deployment

To support Web-deployed ActiveX controls, the Delphi 3 IDE includes tools to digitally sign and seal your .DLL or .EXE file with your Software Publisher digital certificate, and deliver it to a directory of your choosing (see Figure 5). This signature is checked by the Microsoft Internet Explorer 3.0 Web browser after downloading the ActiveX control as part of an HTML document to verify that the file is from who it says it's from, and that the file has not been tampered with or corrupted in transfer.


Figure 5: The new Web Deployment Options dialog box. To support Web-deployed ActiveX controls, Delphi 3 can digitally sign and seal your .DLL or .EXE file with your Software Publisher digital certificate, and deliver it to a directory you select.

The IDE deployment wizard can also bundle and compress multiple files into the Microsoft .CAB file format, generate .INF files needed for a downloadable component to refer to required modules downloadable separately, and generate an HTML object tag for you to paste into your Web page, to refer to your downloadable component (see Figure 6).


Figure 6: The Web deployment wizard can also bundle and compress multiple files into the Microsoft .CAB file format, generate .INF files needed for a downloadable component to refer to required modules downloadable separately, and much more.

Distributed COM

Delphi 3 supports Distributed COM, Microsoft's newest implementation of COM that enables an application on one machine to talk to an application on another across the network wire. Basically, DCOM is the heir-apparent to Remote Procedure Calls (RPC). Delphi 3's remote datasets use DCOM to make the hop from the client machine to the middle-tier data broker. You can implement your own middle-tier business logic by creating a COM server in Delphi 3, and call its methods using interfaces in the client application. DCOM takes care of the network transport; you just have to ask for it.

New Interface Type

OLE objects are always accessed through COM interfaces - abstract, virtual, base classes that define a group of related functions, but not their implementation. All COM interfaces are derived from the IUnknown standard interface, which defines simple reference-counting methods and a QueryInterface method to gain access to other interfaces supported by that object.

By far the most common programming error when using OLE objects is forgetting to increment or decrement the reference count of interfaces onto which you're holding. If you forget to call AddRef on an interface, the object behind that interface may delete itself if some other action causes the object's reference count to drop to zero. Subsequent use of the interface pointer you held onto will cause an access violation. If you forget to call Release on an interface when you're finished with it, the object behind that interface will remain in memory, never to be freed, because its reference count is artificially inflated.

Delphi 3 eliminates this debugging nightmare by adding a new standard type to the Object Pascal language definition: the interface type. An interface type declaration looks much like a class type declaration, but an interface type is only a declaration - it has no implementation of its own. An interface is like a standardized subset of methods that an object can implement. Regardless of what else the implementing object implements, you know that if it implements the XYZ interface, you can use the XYZ methods on it. Furthermore, you can obtain the XYZ interface from the implementing object, and use it without knowing anything about the implementor's class type.

In use, interface variables are initialized, reference-counted (through standard IUnknown methods), and released automatically by compiler-generated code, just as long strings and variants are dynamically allocated, reference counted, and released in Delphi 2. In Delphi 3, transferring values between interface variables or passing them as parameters is now as simple and reliable as transferring integer or string values. In many respects, passing interface values around is safer than passing object instances around, because interface reference-counting eliminates the question of who is responsible for freeing the object.

MI = Multiple Interfaces, not Multiple Inheritance

The flip side of interfaces is how they are implemented by an object. An OLE object may support many different interfaces, such as for streaming, printing, or drawing on the screen. Delphi 3 extends the declaration syntax of the class type, so you can declare a class as an implementor of one or more interface specifications. The Delphi compiler takes care of binding declared methods in the interface types to implemented methods in the class type. No tables of macros of pointers to functions, to crosswire with a typo - the compiler does it all. When something isn't quite right, the Delphi compiler tells you where and what you've missed in your declarations. For example, an interface-implementing class must implement all methods declared in the interface type. If you forget one of the interface methods, the compiler will remind you, just as it reminds you when you declare a method in a class type, but forget to give it a method body.

When a class implements an interface, instances of that class type are assignment-compatible with variables of the interface type. You can take an object instance and assign it to an interface variable, and the compiler will do the magic of extracting the correct interface pointer from the object instance automatically. You can also do late-bound (run-time) interface extraction using the as typecast operator, which calls the implementor's QueryInterface method to obtain the desired interface at run time.

Note that while an object may implement multiple interfaces, this is not the same as multiple inheritance; you are not inheriting any implementation details from the multiple interfaces.

What this means is that implementing OLE objects (such as ActiveX controls and custom COM servers) is now almost trivial. Delphi 3 requires none of the unintelligible tables of macros of pointers that Microsoft's ActiveX SDK heaps upon itself. Where Microsoft implements ActiveX as a system of macros and C++ template classes on top of the C/C++ language, Delphi implements ActiveX by incorporating the essential enabling technologies into the Object Pascal language and VCL classes. Why bother with macros and well-meaning source code conventions when you can have the compiler do the dirty work for you?

Data Visualization

Delphi 3's Component palette includes three new heavy-hitters: an all-new version of QuickReport, powerful charting capabilities in TeeChart, and the DecisionCube interactive crosstab (see Figure 7). You can embed TeeCharts in QuickReport reports, as well as link a TeeChart to the DecisionCube to graphically display the crosstab data on-the-fly.


Figure 7: Delphi 3's Component palette includes three new heavy-hitters, including the powerful DecisionCube.

Application Deployment: Web or Otherwise

If you've ever installed multiple Delphi applications on the same machine, you've probably wondered if there was some way to share the VCL component code between the multiple applications. Well, now there is: a Delphi package. A package is a special .DLL which contains and exports one or more units for applications or other packages to share. A package is different from a .DLL in that it's Delphi-specific (non-Delphi apps should not try to link directly to a package .DLL) and you don't have to change any Delphi source code to use it.

In compiler parlance, packaging is a code-generation option (see Figure 8), which means it should have no effect on the semantics of your source code. When your Delphi application is compiled to use the VCL core package, for example, the compiler generates code to reference the Forms unit in the VCL package .DLL instead of placing the Forms unit code in your .EXE. The result is that your .EXE size drops from around 200KB to less than 20KB. With packages, the .EXE file contains only your application logic and form resources. This also makes ActiveX control .DLLs extremely small - about 25KB - far smaller than Microsoft's 50KB minimum ActiveX template-based control library, or 800KB minimum, MFC-based ActiveX control library.


Figure 8: The new Packages page of the Project Options dialog box. A package is a special .DLL which contains and exports one or more units for applications or other packages to share.

The price for packages is that the package .DLL must contain every bit of code and data that its member units define in their interface sections. This makes the core VCL package weigh in at just over 1MB. (Because most units in the core VCL package are used by the simplest blank form application, and that minimal application produces a 150KB .EXE file, that should tell you something about the value of smart linking.)

Packages are a great way to reduce the overall size of a suite of applications, and open up interesting options for such bandwidth-sensitive applications as ActiveX controls deployed over the Internet (as objects embedded in HTML documents) or network-deployed shareware. The common packages could be bundled separately from the main application file set, so that folks who already have the packages don't have to download them again. Better yet, you could refer to the Borland Web site as the source for the Delphi core packages instead of bundling them yourself, and consuming disk space on your file server.

Code Insight

How many times have you started to write a function call statement, but forgotten what parameters that function call requires? Wouldn't it be great if you could type a function name and hit a hotkey to show the function's parameter declaration, right there in the editor? Wouldn't it be great if it showed functions you created, as well as the Borland-documented stuff? Wouldn't it be wild if it would help you fill in the parameters too?

Delphi's Code Insight provides all of this, and much more. Its Code Parameters feature uses the compiler to determine what function you're trying to use, what its parameters are, and the types of those parameters. Moreover, it's nearly instantaneous and non-intrusive. (Beware of similar-sounding features in other products, which only give you help on functions defined by the tool vendor. Delphi uses the compiler symbols to give you help on all functions in your project - Borland's, yours, and all third-party units used by your project.)

Using code templates, you can define standard code blocks (if/then/else, begin/end, for loops, while loops, etc.) with shortcut names to insert into the editor with just a keystroke or two.

The Code Completion feature helps you enter field names and parameter values by displaying a pop-up list of identifiers that are type-compatible with the source code expression to the left of the editor cursor. For example, typing:

"Caption := IntToStr(ProgressBar1."

and pressing a hotkey will show all the integer properties and functions available on the form's ProgressBar1 component. The helper knows that ProgressBar1 is a component, and that IntToStr requires an integer type parameter, so it shows you the things in ProgressBar1 that can provide an integer-compatible value.

For debugging, the Tool Tip Expression Evaluation feature shows the values of variables in a hint balloon as your mouse moves over the source code symbols in the Code Editor. I may never use the Evaluate/Modify dialog box again!

To make it easier to debug ActiveX control .DLLs and COM servers, particularly when they are used by non-Delphi applications, you can tell the Delphi IDE debugger to run a particular .EXE in order to debug the current .DLL project. So, you can compile your ActiveX control .DLL project, set a few breakpoints in the source code, tell the debugger that the host .EXE for your .DLL is VB.EXE, select Run | Run, and Visual Basic is displayed. Tell VB to load your ActiveX control .DLL - and bing! - execution stops at a breakpoint in the Delphi debugger. You can step, evaluate, watch (and so forth) items in your .DLL while it's being used by VB.

Virtualized Datasets

To enable BDE-less remote datasets (and to respond to a common customer request), Delphi 3 virtualizes all database activity through a - now abstract -TDataSet class. BDE awareness is introduced in a new TDataSet descendant, TBDEDataSet, which serves as the ancestor of TDBDataSet and the familiar TTable, TQuery, and TStoredProc classes (see Figure 9).


Figure 9: Class hierarchy of TDataSet for Delphi 2 and 3. In Delphi 3, TDataSet is now abstract and has a new ancestor, TBDEDataSet. The Delphi 3 BDE now features direct links to Access and DB2, as well as dBASE, Paradox, ODBC, Informix, InterBase, Microsoft SQL Server, Oracle, and Sybase.

Delphi 3 also implements support for "thin-client" remote datasets as a descendant of the base TDataSet class, independent of the BDE. This abstraction of the dataset will also enable third parties to implement Delphi dataset support for other data providers and file formats, without resorting to fate-tempting BDE .DLL hacks.

Breaking Up Client/Server

Seasoned SQL database folks can rattle off all sorts of weaknesses and liabilities of the industry standard two-tier SQL client/server application model. For example, the client machine and application are often intimately bound to the SQL server's network name and SQL dialect or vendor. BDE aliases allow you to re-vector server references on a client machine without recompiling the client application, but those aliases are still on the client machine. If your SQL server goes down and you have to prop up your business with a backup machine, how do you make all your clients automatically talk to the backup machine instead?

Another problem with two-tier is related to centralization of business rules and data policies. In the standard two-tier SQL model, the rules that determine data relationships and links within the database must be implemented on either the server or the client. SQL has proven itself to be an adequate tool for describing and managing data, but is terrible for implementing the programming logic required for complex business rules, such as non-tabular tax calculations or least-cost resource allocation. This means enterprise-wide business rules tend to be implemented in the client application instead of on the centralized server, inflating the size of the client application and creating a maintenance liability.

Multi-Tier Remote Data Brokers

The solution to these and many other weaknesses of the traditional two-tier SQL model is to break the direct connection between the client application and the server. Multi-tier data models make the client application talk to an intermediate machine or service (a broker), which can then process or forward the information to an appropriate server. The client never talks directly to the final SQL server that owns the actual data, so the client application doesn't need to know how to talk SQL - the client application can speak simply and frankly to the intermediate data broker, and the broker can carry the burden of speaking SQL to the data servers, and fret with maintaining connections to multiple data servers - SQL and otherwise. Because business rules and other data-handling logic can live on a middle tier broker, a significant portion of what you've been calling your client application can be moved off the client machine and onto centrally managed servers. What's more, the middle-tier broker can be implemented - and debugged - using real programming tools (such as Delphi, of course) instead of primitive SQL stored procedures (see Figures 10 and 11).


Figure 10: The two-tier SQL server model.


Figure 11: The multi-tier server model.

Delphi 3 opens the floodgates to multi-tier distributed application development with the introduction of the remote dataset. A remote dataset looks and acts like any other dataset (TTable, TQuery), serving rows of data to data-aware controls. The difference is that a remote dataset doesn't require the presence of a full database engine on the client machine - the dataset talks to a second machine (the middle-tier data broker) that contains the database engine, complete with querying, filtering, and SQL connectivity intelligence. With no BDE to install or configure on the client machine, remote datasets enable you to reduce the size and complexity of your client application's file set by an order of magnitude.

With only a handful of middle-tier machines connecting to your SQL servers, Delphi 3's remote datasets could save you an enormous amount of money in SQL server connection licenses alone. Disconnecting the client from the server also opens many options for failover and server load balancing, simply by causing the middle-tier broker to forward client requests to the least busy machine in a server farm.

Conclusion

There are far too many exciting new features in Delphi 3 to cover in one article, or even to try to absorb in one sitting. I'd love to rattle on about the Web-server dispatch and database components for building Netscape and Microsoft Internet Server extension .DLLs, or the extensive support for DIB image formats and direct pixel memory pointers in TBitmap, the new TJPEGImage class, or the new "globalization" of the Delphi RTL and VCL classes to support multi-byte character sets in Asian locales, or the all-new documentation set and online Help, but for now a tease will have to do. So many ideas, so little time.

2006. július 25., kedd

Get disk free space


Problem/Question/Abstract:

How can i get disk free space?

Answer:

function GetDiskFree(Drive: char): int64;
{ func to return the free space of a drive in bytes. }
var
{$IFDEF Delphi3Below}
  lpFreeBytesAvailableToCaller,
    lpTotalNumberOfBytes,
    lpTotalNumberOfFreeBytes: comp;
{$ELSE}
  lpFreeBytesAvailableToCaller,
    lpTotalNumberOfBytes,
    lpTotalNumberOfFreeBytes: TLargeInteger;
{$ENDIF}
begin
  if GetDiskFreeSpaceEx(PChar(Drive + ':\'), lpFreeBytesAvailableToCaller,
    lpTotalNumberOfBytes, @lpTotalNumberOfFreeBytes) then
    Result := lpTotalNumberOfFreeBytes
  else
    Result := -1;
end;

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  ShowMessage('a:' + IntToStr(GetDiskFree('a')));
  ShowMessage('c:' + IntToStr(GetDiskFree('c')));
end;

2006. július 24., hétfő

Creating a splash screen


Problem/Question/Abstract:

How do I make a splash screen for my application?

Answer:

First make a new form, this will be your SpashSreen.

Set Name to "Splash".
Set BorderStyle to "bsNone".
Put an image or whatever on it.
Make sure it is not auto-created. (Shift-Ctrl-F11)
Now edit your main program body:

program MyApp;
{... }
begin
  Application.Initialize;

  { ---------- PUT THIS IN: ------------- }
  Splash := TSplash.Create(Application);
  Splash.Show;
  Splash.Refresh;
  { ------------------------------------- }

  ..
    Application.CreateForm(...);
    Application.Run;
end;

Now edit the OnShow event of your main form:

procedure TMainForm.FormShow(Sender: TObject);
begin
  {...}
  { Free Splash screen }
  Splash.Free;
end;

You now have a splash screen!
Tip: If you place the Spash.Free in a OnTimer event, you can control how long the user sees your splash screen.

2006. július 23., vasárnap

Format a disk


Problem/Question/Abstract:

How to format a disk

Answer:

Use the SHFormatDrive- function in then Shell32.dll. You will get (Windows NT, Win95 ???) the standard format-disk- dialog. Put the following in your interface-part:

const
  SHFMT_ID_DEFAULT = $FFFF; {Default for physical format}
  SHFMT_OPT_QUICKFORMAT = $0000; {do quick formatting}
  SHFMT_OPT_FULL = $0001; {complete formatting}
  SHFMT_OPT_SYSONLY = $0002; {copy system files only}
  SHFMT_ERROR = $FFFFFFFF; {formatting error}
  SHFMT_CANCEL = $FFFFFFFE; {formatting aborted}
  SHFMT_NOFORMAT = $FFFFFFFD; {unable to format}

function SHFormatDrive(hWnd: HWND; Drive, fmtID, Options: Word): Longint; stdcall;

implementation

const
  Shell32 = 'Shell32.dll';

function SHFormatDrive; external Shell32 name 'SHFormatDrive';

And a little example for executing the function and formatting drive A:

procedure TMainFrm.DiskFormatClick(Sender: TObject);
var
  FmtRes: longint;
  DriveNo: word;
begin
  DriveNo := 0; {Drive A}
  try
    FmtRes := ShFormatDrive(Handle, DriveNo, SHFMT_ID_DEFAULT, SHFMT_OPT_QUICKFORMAT);
    if FmtRes < 0 then
      ShowMessage('Error or Abort Formatting');
  except
  end;
end;


Solve 2:

procedure FormatFloppy(Drive: byte);
{Procedure to bring up the standard Windows format dialog to format a floppy drive.
Pass 0 for A:\ or 1 for B:\ }
type
  TSHFormatDrive = function(hWnd: HWND; Drive: Word; fmtID: Word;
    Options: Word): Longint stdcall;
var
  SHFormatDrive: TSHFormatDrive;
  LibHandle: THandle;
begin
  LibHandle := LoadLibrary(PChar('Shell32.dll'));
  if LibHandle <> 0 then
    @SHFormatDrive := GetProcAddress(LibHandle, 'SHFormatDrive')
  else
  begin
    MessageDlg('Failed to load Shell32.dll.', mtError, [mbOK], 0);
    Exit;
  end;
  if @SHFormatDrive <> nil then
    SHFormatDrive(Application.Handle, Drive, { 0 = A:\, 1 = B:\ } $FFFF, 0);
  FreeLibrary(LibHandle);
  @SHFormatDrive := nil;
end;

2006. július 22., szombat

Add Menu Item To Explorer Context Menu


Problem/Question/Abstract:

How to add menu items to windows explorer / desktop context menu

Answer:

// Open Delphi select dynamic link library
// Copy / paste this into the DLL
// Then compile
// You will have to customize this code. To suite your needs.
// once the dll has been compiled you will now have to register this
// com server.
// Use regsvr32.exe sendtoweb.dll
// now open windows explorer and you will see a new menu item
// which can be accessed by the desktop also..

unit Sendtoweb;

// Author C Pringle Cjpsoftware.com

{ Implementation of the context menu shell extension COM object. This
  COM object is responsible for forwarding requests to its partner
  TPopupMenu component. The TPopupMenu component must reside on the
  MenuComponentForm, and is referred to explicitly in this example.
  You can modify this code to make it more flexible and generic in
  the future.

  The TContextMenu component registers itself as a global context menu
  handler. This is accomplished by adding a key to the
  HKEY_CLASSES_ROOT\*\ShellEx\ContextMenuHandlers key in the registry.

  jfl
}

interface

uses

  Classes, ComServ, ComObj, ActiveX, Windows, ShlObj, Interfaces, Menus,
  ShellAPI, SysUtils, registry;

type
  TContextMenuFactory = class(TComObjectFactory)
  public
    procedure UpdateRegistry(Register: Boolean); override;
  end;

  TContextMenu = class(TComObject, IShellExtInit, IContextMenu)
  private
    FFileName: string;
    function BuildSubMenu(Menu: HMENU; IndexMenu: Integer;
      var IDCmdFirst: Integer): HMENU;
  protected
    szFile: array[0..MAX_PATH] of Char;
    // Required to disambiguate TComObject.Initialize otherwise a compiler
    // warning will result.
    function IShellExtInit.Initialize = IShellExtInit_Initialize;
  public
    { IShellExtInit members }
    function IShellExtInit_Initialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
      hKeyProgID: HKEY): HResult; stdcall;
    { IContextMenu }
    function QueryContextMenu(Menu: HMENU;
      indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult; stdcall;
    function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall;
    function GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
      pszName: LPSTR; cchMax: UINT): HResult; stdcall;
  end;

var
  // Must be set prior to instantiation of TContextMenu!
  GFileExtensions: TStringList;

const
  MenuCommandStrings: array[0..3] of string = (
    '', '&STW Web Upload', '&STW FTPClient', '&STW Setup'
    );

implementation

{ TContextMenuFactory }
{ Public }

function ReadDefaultPAth: string;
var
  path: string;
  Reg: TRegistry;
begin

  Reg := TRegistry.CReate;
  try
    with Reg do
    begin
      RootKey := HKEY_LOCAL_MACHINE;
      Path := 'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths';

      if KeyExists(Path) then
      begin
        OpenKey(Path + '\sendtoweb.exe', false);
        Result := ReadString(#0);
        closekey;
      end;

      // Key Added to shell ext.

    end;
  finally
    Reg.CloseKey;
    Reg.Free;
  end;

end; // Custom registration code

procedure TContextMenuFactory.UpdateRegistry(Register: Boolean);
begin
  inherited UpdateRegistry(Register);

  // Register our global context menu handler
  if Register then
  begin
    CreateRegKey('*\ShellEx\ContextMenuHandlers\SendToWeb', '',
      GUIDToString(Class_ContextMenu));
    CreateRegKey('CLSID\' + GUIDToString(ClassID) + '\' +
      ComServer.ServerKey, 'ThreadingModel', 'Apartment');
  end
  else
  begin
    DeleteRegKey('*\ShellEx\ContextMenuHandlers\SendToWeb');
  end;
end;

{ TContextMenu }
{ Private }

{ Build a context menu using the existing Menu handle. If Menu is nil,
  we create a new menu handle and return it in the function's return
  value. Note that this function does not handle nested (recursive)
  menus. This exercise is left to the reader. }

function TContextMenu.BuildSubMenu(Menu: HMENU; IndexMenu: Integer;
  var IDCmdFirst: Integer): HMENU;
var
  i: Integer;
  menuItemInfo: TMenuItemInfo;
begin
  if Menu = 0 then
    Result := CreateMenu
  else
    Result := Menu;

  // Build the menu items here
  with menuitemInfo do
  begin
    cbSize := SizeOf(TMenuItemInfo);
    fMask := MIIM_CHECKMARKS or MIIM_DATA or MIIM_ID or
      MIIM_STATE or MIIM_SUBMENU or MIIM_TYPE or MIIM_CHECKMARKS;
    fType := MFT_STRING;
    fState := MFS_ENABLED;
    hSubMenu := 0;
    hbmpChecked := 0;
    hbmpUnchecked := 0;
  end;

  for i := 0 to High(MenuCommandStrings) do
  begin
    if i = 0 then
      menuitemInfo.fType := MFT_SEPARATOR
    else
      menuiteminfo.ftype := MFT_String;
    if i = 1 then
      menuitemInfo.fstate := MFS_ENABLED or MFS_DEFAULT
    else
      menuitemInfo.fstate := MFS_ENABLED;

    menuitemInfo.dwTypeData := PChar(MenuCommandStrings[i]);
    menuitemInfo.wID := IDCmdFirst;
    InsertMenuItem(Result, IndexMenu + i, True, menuItemInfo);
    Inc(IDCmdFirst);
  end;
end;

{ IShellExtInit }

function TContextMenu.IShellExtInit_Initialize(pidlFolder: PItemIDList;
  lpdobj: IDataObject; hKeyProgID: HKEY): HResult;
var
  medium: TStgMedium;
  fe: TFormatEtc;

begin
  with fe do
  begin
    cfFormat := CF_HDROP;
    ptd := nil;
    dwAspect := DVASPECT_CONTENT;
    lindex := -1;
    tymed := TYMED_HGLOBAL;
  end;
  // Fail the call if lpdobj is Nil.
  if lpdobj = nil then
  begin
    Result := E_FAIL;
    Exit;
  end;
  // Render the data referenced by the IDataObject pointer to an HGLOBAL
  // storage medium in CF_HDROP format.
  Result := lpdobj.GetData(fe, medium);
  if Failed(Result) then
    Exit;
  // If only one file is selected, retrieve the file name and store it in
  // szFile. Otherwise fail the call.
  if DragQueryFile(medium.hGlobal, $FFFFFFFF, nil, 0) = 1 then
  begin
    DragQueryFile(medium.hGlobal, 0, szFile, SizeOf(szFile));
    Result := NOERROR;
  end
  else
    Result := E_FAIL;
  ReleaseStgMedium(medium);
end;

{ IContextMenu }

function TContextMenu.QueryContextMenu(Menu: HMENU;
  indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult;
var
  extension: string;
  I: Integer;
  idLastCommand: Integer;
begin
  Result := E_FAIL;
  idLastCommand := idCmdFirst;

  // Extract the filename extension from the file dropped, and see if we
  // have a handler registered for it
//  extension := UpperCase( ( FFileName ) );

//for i := 0 to GFileExtensions.Count - 1 do
//   if Pos(Lowercase(GFileExtensions[ i ]),lowercase(extension))=0 then
  //  begin
  BuildSubMenu(Menu, indexMenu, idLastCommand);
  // Return value is number of items added to context menu
  Result := idLastCommand - idCmdFirst;
  //      Exit;
  //    end;
end;

function TContextMenu.InvokeCommand(var lpici:
  TCMInvokeCommandInfo): HResult;
var
  idCmd: UINT;
begin
  if HIWORD(Integer(lpici.lpVerb)) <> 0 then
    Result := E_FAIL
  else
  begin
    idCmd := LOWORD(lpici.lpVerb);
    Result := S_OK;

    // Activate the Dialog And prepare to send data to the
    // web

    case idCmd of
      1:
        begin

          ShellExecute(GetDesktopWindow, nil, Pchar(ExtractFileName(ReadDefaultPath)),
            Pchar('Direct' + '"' + szfile + '"'), nil, SW_SHOW);

        end;
      3:
        begin
          ShellExecute(GetDesktopWindow, nil, Pchar(ExtractFileName(ReadDefaultPath)),
            Pchar('Path'), nil, SW_SHOW);

        end;
      2:
        ShellExecute(GetDesktopWindow, nil, Pchar(ExtractFileName(ReadDefaultPath)),
          PChar(''), nil, SW_SHOW);
    else
      Result := E_FAIL;
    end;
  end;
end;

function TContextMenu.GetCommandString(idCmd, uType: UINT;
  pwReserved: PUINT; pszName: LPSTR; cchMax: UINT): HResult;

begin
  //  StrCopy( pszName, 'Send To The Web') ;

  Result := S_OK;
end;

initialization
  { Note that we create an instance of TContextMenuFactory here rather
    than TComObjectFactory. This is necessary so that we can add some
    custom registry entries by overriding the UpdateRegistry virtual
    function. }
  TContextMenuFactory.Create(ComServer, TContextMenu, Class_ContextMenu,
    'ContextMenu', 'Send To The Web', ciMultiInstance);

  // Initialize the file extension list
  GFileExtensions := TStringList.Create;
  // GFileExtensions.Add( 'setup msn' );

finalization
  GFileExtensions.Free;

end.

2006. július 21., péntek

Convert a string to a mathematical expression and get its result


Problem/Question/Abstract:

How to convert a string to a mathematical expression and get its result.

Answer:

unit MathComponent;

interface

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

type
  TOperandtype = (ttradians, ttdegrees, ttgradients);
  TMathtype = (mtnil, mtoperator, mtlbracket, mtrbracket, mtoperand, mtfunction);
  TMathSubtype = (msnone, mstrignometric);
  TMathOperator = (monone, moadd, mosub, modiv, momul, mopow, momod, modivint);
  TMathFunction = (mfnone, mfsinh, mfcosh, mftanh, mfcosech, mfsech, mfcoth, mfsin,
    mfcos, mftan, mfcot, mfsec, mfcosec, mflog, mfln, mfsub, mfadd);

type
  pmathchar = ^Tmathchar;
  TMathChar = record
    case mathtype: Tmathtype of
      mtoperand: (data: extended);
      mtoperator: (op: TMathOperator);
      mtfunction: (func: TMathfunction; subtype: (mstnone, msttrignometric));
  end;

type
  TMathControl = class(TComponent)
  private
    input, output, stack: array of tmathchar;
    fmathstring: string;
    ftrignometrictype: Toperandtype;
    fExpressionValid: boolean;
    procedure removespace;
    function isvalidchar(c: char): boolean;
    function getresult: extended;
    function checkbrackets: boolean;
    function calculate(operand1, operand2, operator: Tmathchar): extended; overload;
    function calculate(operand1, operator: Tmathchar): extended; overload;
    function getoperator(pos: integer; var len: integer; var amathoperator:
      TMathOperator): boolean;
    function getoperand(pos: integer; var len: integer; var value: extended): boolean;
    function getmathfunc(pos: integer; var len: integer; var amathfunc:
      TmathFunction): boolean;
    function processstring: boolean;
    procedure convertinfixtopostfix;
    function isdigit(c: char): boolean;
    function getprecedence(mop: TMathchar): integer;
  protected
    procedure loaded; override;
  published
    property MathExpression: string read fmathstring write fmathstring;
    property MathResult: extended read getresult;
    property ExpressionValid: boolean read fExpressionvalid;
    property Trignometrictype: Toperandtype read ftrignometrictype write
      ftrignometrictype;
  end;

procedure Register;

implementation

function tmathcontrol.calculate(operand1, operator: Tmathchar): extended;
begin
  result := 0;
  if (operator.subtype = msttrignometric) then
  begin
    if ftrignometrictype = ttdegrees then
      operand1.data := operand1.data * (pi / 180);
    if ftrignometrictype = ttgradients then
      operand1.data := GradToRad(operand1.data);
  end;
  case operator.func of
    mfsub: result := -operand1.data;
    mfadd: result := operand1.data;
    mfsin: result := sin(operand1.data);
    mfcos: result := cos(operand1.data);
    mfcot: result := 1 / tan(operand1.data);
    mfcosec: result := 1 / sin(operand1.data);
    mfsec: result := 1 / cos(operand1.data);
    mftan: result := tan(operand1.data);
    mflog: result := log10(operand1.data);
    mfln: result := ln(operand1.data);
  end;
end;

function tmathcontrol.getmathfunc(pos: integer; var len: integer; var amathfunc:
  TmathFunction): boolean;
var
  tmp: string;
  i: integer;
begin
  amathfunc := mfnone;
  result := false;
  tmp := '';
  if (fmathstring[pos] = '+') then
  begin
    amathfunc := mfadd;
    len := 1;
    result := true;
  end;
  if (fmathstring[pos] = '-') then
  begin
    amathfunc := mfsub;
    len := 1;
    result := true;
  end;
  if (fmathstring[pos] = 's') then
  begin
    for i := pos to pos + 3 do
      tmp := tmp + fmathstring[i];
    if strcomp(pchar(tmp), 'sin(') = 0 then
    begin
      amathfunc := mfsin;
      len := 3;
      result := true;
    end
    else if strcomp(pchar(tmp), 'sec(') = 0 then
    begin
      amathfunc := mfsec;
      len := 3;
      result := true;
    end;
  end;
  if (fmathstring[pos] = 'c') then
  begin
    for i := pos to pos + 5 do
      tmp := tmp + fmathstring[i];
    if strlcomp(pchar(tmp), 'cos(', 4) = 0 then
    begin
      amathfunc := mfcos;
      len := 3;
      result := true;
    end
    else if strlcomp(pchar(tmp), 'cot(', 4) = 0 then
    begin
      amathfunc := mfcot;
      len := 3;
      result := true;
    end
    else if strlcomp(pchar(tmp), 'cosec(', 6) = 0 then
    begin
      amathfunc := mfcosec;
      len := 3;
      result := true;
    end
  end;
  if (fmathstring[pos] = 't') then
  begin
    for i := pos to pos + 3 do
      tmp := tmp + fmathstring[i];
    if strlcomp(pchar(tmp), 'tan(', 4) = 0 then
    begin
      amathfunc := mflog;
      len := 3;
      result := true;
    end;
  end;
  if (fmathstring[pos] = 'l') then
  begin
    for i := pos to pos + 3 do
      tmp := tmp + fmathstring[i];
    if strlcomp(pchar(tmp), 'log(', 4) = 0 then
    begin
      amathfunc := mflog;
      len := 3;
      result := true;
    end
    else if strlcomp(pchar(tmp), 'ln(', 3) = 0 then
    begin
      amathfunc := mfln;
      len := 3;
      result := true;
    end
  end;
end;

procedure tmathcontrol.loaded;
begin
  inherited;
  fexpressionvalid := processstring;
end;

procedure tmathcontrol.removespace;
var
  i: integer;
  tmp: string;
begin
  tmp := '';
  for i := 1 to length(fmathstring) do
    if fmathstring[i] <> ' ' then
      tmp := tmp + fmathstring[i];
  fmathstring := tmp;
end;

function tmathcontrol.isvalidchar(c: char): boolean;
begin
  result := true;
  if (not (isdigit(c))) and (not (c in ['(', ')', 't', 'l', 'c', 'm', 'd', 's', '*',
    '/', '+', '-', '^'])) then
    result := false;
end;

function tmathcontrol.checkbrackets: boolean;
var
  i: integer;
  bracketchk: integer;
begin
  result := true;
  bracketchk := 0;
  i := 1;
  if length(fmathstring) = 0 then
    result := false;
  while i <= length(fmathstring) do
  begin
    if fmathstring[i] = '(' then
      bracketchk := bracketchk + 1
    else if fmathstring[i] = ')' then
      bracketchk := bracketchk - 1;
    i := i + 1;
  end;
  if bracketchk <> 0 then
    result := false;
end;

function Tmathcontrol.calculate(operand1, operand2, operator: Tmathchar): extended;
begin
  result := 0;
  case operator.op of
    moadd:
      result := operand1.data + operand2.data;
    mosub:
      result := operand1.data - operand2.data;
    momul:
      result := operand1.data * operand2.data;
    modiv:
      if (operand1.data <> 0) and (operand2.data <> 0) then
        result := operand1.data / operand2.data
      else
        result := 0;
    mopow: result := power(operand1.data, operand2.data);
    modivint:
      if (operand1.data <> 0) and (operand2.data <> 0) then
        result := round(operand1.data) div round(operand2.data)
      else
        result := 0;
    momod:
      if (operand1.data >= 0.5) and (operand2.data >= 0.5) then
        result := round(operand1.data) mod round(operand2.data)
      else
        result := 0;
  end;
end;

function Tmathcontrol.getresult: extended;
var
  i: integer;
  tmp1, tmp2, tmp3: tmathchar;
begin
  fExpressionValid := processstring;
  if fExpressionValid = false then
  begin
    result := 0;
    exit;
  end;
  convertinfixtopostfix;
  setlength(stack, 0);
  for i := 0 to length(output) - 1 do
  begin
    if output[i].mathtype = mtoperand then
    begin
      setlength(stack, length(stack) + 1);
      stack[length(stack) - 1] := output[i];
    end
    else if output[i].mathtype = mtoperator then
    begin
      tmp1 := stack[length(stack) - 1];
      tmp2 := stack[length(stack) - 2];
      setlength(stack, length(stack) - 2);
      tmp3.mathtype := mtoperand;
      tmp3.data := calculate(tmp2, tmp1, output[i]);
      setlength(stack, length(stack) + 1);
      stack[length(stack) - 1] := tmp3;
    end
    else if output[i].mathtype = mtfunction then
    begin
      tmp1 := stack[length(stack) - 1];
      setlength(stack, length(stack) - 1);
      tmp2.mathtype := mtoperand;
      tmp2.data := calculate(tmp1, output[i]);
      setlength(stack, length(stack) + 1);
      stack[length(stack) - 1] := tmp2;
    end;
  end;
  result := stack[0].data;
  setlength(stack, 0);
  setlength(input, 0);
  setlength(output, 0);
end;

function Tmathcontrol.getoperator(pos: integer; var len: integer; var amathoperator:
  TMathOperator): boolean;
var
  tmp: string;
  i: integer;
begin
  tmp := '';
  result := false;
  if fmathstring[pos] = '+' then
  begin
    amathoperator := moadd;
    len := 1;
    result := true;
  end
  else if fmathstring[pos] = '*' then
  begin
    amathoperator := momul;
    len := 1;
    result := true;
  end
  else if fmathstring[pos] = '/' then
  begin
    amathoperator := modiv;
    len := 1;
    result := true;
  end
  else if fmathstring[pos] = '-' then
  begin
    amathoperator := mosub;
    len := 1;
    result := true;
  end
  else if fmathstring[pos] = '^' then
  begin
    amathoperator := mopow;
    len := 1;
    result := true;
  end
  else if fmathstring[pos] = 'd' then
  begin
    for i := pos to pos + 2 do
      tmp := tmp + fmathstring[i];
    if strcomp(pchar(tmp), 'div') = 0 then
    begin
      amathoperator := modivint;
      len := 3;
      result := true;
    end;
  end
  else if fmathstring[pos] = 'm' then
  begin
    for i := pos to pos + 2 do
      tmp := tmp + fmathstring[i];
    if strcomp(pchar(tmp), 'mod') = 0 then
    begin
      amathoperator := momod;
      len := 3;
      result := true;
    end;
  end;
end;

function Tmathcontrol.getoperand(pos: integer; var len: integer; var value: extended):
  boolean;
var
  i, j: integer;
  tmpnum: string;
  dotflag: boolean;
begin
  j := 1;
  result := true;
  dotflag := false;
  for i := pos to length(fmathstring) - 1 do
  begin
    if isdigit(fmathstring[i]) then
    begin
      if (fmathstring[i] = '.') and (dotflag = true) then
      begin
        result := false;
        break;
      end
      else if (fmathstring[i] = '.') and (dotflag = false) then
        dotflag := true;
      tmpnum := tmpnum + fmathstring[i];
      j := j + 1;
    end
    else
      break;
  end;
  if result = true then
  begin
    value := strtofloat(tmpnum);
    len := j - 1;
  end;
end;

function Tmathcontrol.processstring: boolean;
var
  i: integer;
  mov: integer;
  tmpfunc: tmathfunction;
  tmpop: tmathoperator;
  numoperators: integer;
  numoperands: integer;
begin
  i := 0;
  mov := 0;
  numoperators := 0;
  numoperands := 0;
  setlength(output, 0);
  setlength(input, 0);
  setlength(stack, 0);
  removespace;
  result := true;
  if checkbrackets = false then
  begin
    result := false;
    exit;
  end;
  fmathstring := '(' + fmathstring + ')';
  while i <= length(fmathstring) - 1 do
  begin
    if not (isvalidchar(fmathstring[i + 1])) then
    begin
      result := false;
      break;
    end;
    if fmathstring[i + 1] = '(' then
    begin
      setlength(input, length(input) + 1);
      input[length(input) - 1].mathtype := mtlbracket;
      i := i + 1;
    end
    else if fmathstring[i + 1] = ')' then
    begin
      setlength(input, length(input) + 1);
      input[length(input) - 1].mathtype := mtrbracket;
      i := i + 1;
    end
    else if getoperator(i + 1, mov, tmpop) then
    begin
      if (tmpop <> moadd) and (tmpop <> mosub) then
      begin
        if i = 0 then //first character cannot be an operator
        begin // other than a '+' or '-'.
          result := false;
          break;
        end;
        setlength(input, length(input) + 1);
        input[length(input) - 1].mathtype := mtoperator;
        input[length(input) - 1].op := tmpop;
        i := i + mov;
        numoperators := numoperators + 1;
      end
      else if (tmpop = mosub) or (tmpop = moadd) then
      begin
        if (i = 0) or (input[length(input) - 1].mathtype = mtoperator) or
          (input[length(input) - 1].mathtype = mtlbracket) then
        begin //makes use of fact the if the first part of if expression is true then
          //remaining parts are not evaluated thus preventing a
          //exception from occuring.
          setlength(input, length(input) + 1);
          input[length(input) - 1].mathtype := mtfunction;
          getmathfunc(i + 1, mov, tmpfunc);
          input[length(input) - 1].func := tmpfunc;
          i := i + mov;
        end
        else
        begin
          setlength(input, length(input) + 1);
          numoperators := numoperators + 1;
          input[length(input) - 1].mathtype := mtoperator;
          input[length(input) - 1].op := tmpop;
          i := i + 1;
        end;
      end;
    end
    else if isdigit(fmathstring[i + 1]) then
    begin
      setlength(input, length(input) + 1);
      input[length(input) - 1].mathtype := mtoperand;
      if getoperand(i + 1, mov, input[length(input) - 1].data) = false then
      begin
        result := false;
        break;
      end;
      i := i + mov;
      numoperands := numoperands + 1;
    end
    else
    begin
      getmathfunc(i + 1, mov, tmpfunc);
      if tmpfunc <> mfnone then
      begin
        setlength(input, length(input) + 1);
        input[length(input) - 1].mathtype := mtfunction;
        input[length(input) - 1].func := tmpfunc;
        if tmpfunc in [mfsin, mfcos, mftan, mfcot, mfcosec, mfsec] then
          input[length(input) - 1].subtype := msttrignometric
        else
          input[length(input) - 1].subtype := mstnone;
        i := i + mov;
      end
      else
      begin
        result := false;
        break;
      end;
    end;
  end;
  if numoperands - numoperators <> 1 then
    result := false;
end;

function Tmathcontrol.isdigit(c: char): boolean;
begin
  result := false;
  if ((integer(c) > 47) and (integer(c) < 58)) or (c = '.') then
    result := true;
end;

function Tmathcontrol.getprecedence(mop: TMathchar): integer;
begin
  result := -1;
  if mop.mathtype = mtoperator then
  begin
    case mop.op of
      moadd: result := 1;
      mosub: result := 1;
      momul: result := 2;
      modiv: result := 2;
      modivint: result := 2;
      momod: result := 2;
      mopow: result := 3;
    end
  end
  else if mop.mathtype = mtfunction then
    result := 4;
end;

procedure Tmathcontrol.convertinfixtopostfix;
var
  i, j, prec: integer;
begin
  for i := 0 to length(input) - 1 do
  begin
    if input[i].mathtype = mtoperand then
    begin
      setlength(output, length(output) + 1);
      output[length(output) - 1] := input[i];
    end
    else if input[i].mathtype = mtlbracket then
    begin
      setlength(stack, length(stack) + 1);
      stack[length(stack) - 1] := input[i];
    end
    else if (input[i].mathtype = mtoperator) then
    begin
      prec := getprecedence(input[i]);
      j := length(stack) - 1;
      if j >= 0 then
      begin
        while (getprecedence(stack[j]) >= prec) and (j >= 0) do
        begin
          setlength(output, length(output) + 1);
          output[length(output) - 1] := stack[j];
          setlength(stack, length(stack) - 1);
          j := j - 1;
        end;
        setlength(stack, length(stack) + 1);
        stack[length(stack) - 1] := input[i];
      end;
    end
    else if input[i].mathtype = mtfunction then
    begin
      setlength(stack, length(stack) + 1);
      stack[length(stack) - 1] := input[i];
    end
    else if input[i].mathtype = mtrbracket then
    begin
      j := length(stack) - 1;
      if j >= 0 then
      begin
        while (stack[j].mathtype <> mtlbracket) and (j >= 0) do
        begin
          setlength(output, length(output) + 1);
          output[length(output) - 1] := stack[j];
          setlength(stack, length(stack) - 1);
          j := j - 1;
        end;
        if j >= 0 then
          setlength(stack, length(stack) - 1);
      end;
    end;
  end;
end;

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

end.

2006. július 20., csütörtök

Make Application.ExeName work in DLLs


Problem/Question/Abstract:

When you work with DLL you will discover that Application.ExeName will not return the DLL filename, but the filename of the application that loaded your library. That's a problem in some cases. You can use the GetModuleFileName to achieve this problem.

Answer:

In Forms.pas, at line 6856 you'll find:

function TApplication.GetExeName: string;
begin
  Result := ParamStr(0);
end;

for DLLs you can use this:

function GetRealExeName: string;
var
  ExeName: array[0..MAX_PATH] of char;
begin
  fillchar(ExeName, SizeOf(ExeName), #0);
  GetModuleFileName(HInstance, ExeName, MAX_PATH);
  Result := ExeName;
end;

now, this will return also DLL file names.

2006. július 19., szerda

Kill an application without any confirmation


Problem/Question/Abstract:

How to kill an application without any confirmation

Answer:

The following code searches for Notepad, and kills it - no questions asked. Use

WinHwnd := FindWindow(classname, nil);

or

WinHwnd := FindWindow(nil, 'window-caption');

with your apps details.

procedure TForm1.ButtonClick(Sender: TObject);
var
  ProcessHandle: THandle;
  WinHwnd: HWND;
  ProcessID, ExitCode: Integer;
begin
  ProcessID := 0;
  ExitCode := 0;
  WinHwnd := FindWindow('NotePad', nil);
  if not (IsWindow(WinHwnd)) then
  begin
    ShowMessage('NotePad not found');
    exit;
  end;
  GetWindowThreadProcessID(WinHwnd, @ProcessID);
  ProcessHandle := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_VM_OPERATION
    or PROCESS_VM_WRITE or PROCESS_VM_READ or
    PROCESS_TERMINATE, False, ProcessID);
  if (ProcessHandle > 0) then
  begin
    GetExitCodeProcess(ProcessHandle, ExitCode);
    { or  GetExitCodeProcess(ProcessHandle, DWORD(ExitCode)); }
    TerminateProcess(ProcessHandle, ExitCode);
    CloseHandle(ProcessHandle);
  end
  else
    ShowMessage('Unable to get proccess Handle');
end;

2006. július 18., kedd

How to save and retrieve font information to / from a TIniFile


Problem/Question/Abstract:

How to save and retrieve font information to / from a TIniFile

Answer:

Solve 1:

uses
  Inifiles;

procedure SaveFont(FName: string; Section: string; smFont: TFont);
var
  FStream: TIniFile;
begin
  FStream := TIniFile.Create(FName);
  try
    FStream.WriteString(Section, 'Name', smFont.Name);
    FStream.WriteInteger(Section, 'CharSet', smFont.CharSet);
    FStream.WriteInteger(Section, 'Color', smFont.Color);
    FStream.WriteInteger(Section, 'Size', smFont.Size);
    FStream.WriteInteger(Section, 'Style', Byte(smFont.Style));
  finally
    FStream.free;
  end;
end;

procedure LoadFont(FName: string; Section: string; smFont: TFont);
var
  FStream: TIniFile;
begin
  FStream := TIniFile.Create(Fname);
  try
    smFont.Name := FStream.ReadString(Section, 'Name', smFont.Name);
    smFont.CharSet := TFontCharSet(FStream.ReadInteger(Section, 'CharSet',
      smFont.CharSet));
    smFont.Color := TColor(FStream.ReadInteger(Section, 'Color', smFont.Color));
    smFont.Size := FStream.ReadInteger(Section, 'Size', smFont.Size);
    smFont.Style := TFontStyles(Byte(FStream.ReadInteger(Section, 'Style',
      Byte(smFont.Style))));
  finally
    FStream.free;
  end;
end;

Here 's how to use the procedures:

{Save Font}

procedure TForm1.Button1Click(Sender: TObject);
begin
  SaveFont('font.ini', 'label', Label1.Font);
end;

procedure TForm1.Label1DblClick(Sender: TObject);
begin
  if FontDialog1.Execute then
    Label1.Font := FontDialog1.Font
end;

{Load Font}

procedure TForm1.Button2Click(Sender: TObject);
begin
  LoadFont('font.ini', 'label', Label1.Font);
end;


Solve 2:

This code converts an instance of TFont to/ from a string. Just store that string whereever you want:

function GetAnyFontAsStr(Font: TFont): string;
begin
  Result := 'DEFAULT';
  if Assigned(Font) then
  begin
    Result := QuotedStr('Name=' + Font.Name);
    Result := Result + ';
      Size = ' + IntToStr(Font.Size);
      Result := Result + ';
      Color = ' + ColorToString(Font.Color);
      Result := Result + ';
      Pitch = ' + GetEnumName(TypeInfo(TFontPitch), Ord(Font.Pitch));
      Result := Result + ';
      Style = ' + IntToStr(Byte(Font.Style));
  end;
end;

procedure SetAnyFontAsStr(var Font: TFont; aFontStr: string);
var
  FontStrList: TStringList;
begin
  if Assigned(Font) then
  begin
    FontStrList := TStringList.Create;
    FontStrList.QuoteChar := '''';
    FontStrList.Delimiter := ';';
    FontStrList.DelimitedText := aFontStr;
    Font.Name := FontStrList.Values['Name'];
    Font.Size := StrToInt(FontStrList.Values['Size']);
    Font.Color := StringToColor(FontStrList.Values['Color']);
    Font.Pitch := TFontPitch(GetEnumValue(TypeInfo(TFontPitch),
      FontStrList.Values['Pitch']));
    Font.Style := TFontStyles(Byte(StrToIntDef(FontStrList.Values['Style'], 0)));
    FontStrList.Free;
  end;
end;


Solve 3:

uses
  IniFiles;

procedure SaveInfo;
var
  t: TIniFile;
  s: string;
begin
  s := ExtractFilePath(ParamStr(0)) + 'fontinfo.ini';
  t := TIniFile.Create(s);
  {load previous values into Font dialog if there are any}
  if FileExists(s) then
    with FontDialog1.Font do
    begin
      Name := t.ReadString('Font', 'Name', Name);
      Size := t.ReadInteger('Font', 'Size', Size);
      Color := t.ReadInteger('Font', 'Color', Color);
      if t.ReadBool('Font', 'Bold', False) then
        Style := Style + [fsBold];
      if t.ReadBool('Font', 'Italic', False) then
        Style := Style + [fsItalic];
      if t.ReadBool('Font', 'Underline', False) then
        Style := Style + [fsUnderline];
      if t.ReadBool('Font', 'Strikeout', False) then
        Style := Style + [fsStrikeOut];
    end;
  if FontDialog1.Execute then
  begin
    {write new values to INI file for next time}
    with FontDialog1.Font do
    begin
      t.WriteString('Font', 'Name', Name);
      t.WriteInteger('Font', 'Size', Size);
      t.WriteInteger('Font', 'Color', Color);
      t.WriteBool('Font', 'Bold', fsBold in Style);
      t.WriteBool('Font', 'Italic', fsItalic in Style);
      t.WriteBool('Font', 'Underline', fsUnderline in Style);
      t.WriteBool('Font', 'Strikeout', fsStrikeout in Style);
    end;
  end;
  t.Free;
end;

As you can see, the Color property is stored as an integer. Therefore, if you need to also store the ColorDialog.Color property, use the ReadInteger and WriteInteger methods.

2006. július 17., hétfő

Write the checked state of stand-alone TRadioButtons to a TIniFile


Problem/Question/Abstract:

I have three single TRadioButtons sitting on a TPanel (on Form1). How can I save the state of the checked TRadioButton to a TIniFile and read it back afterwards? I know how to do it with a TRadioGroup, but not with stand-alone radiobuttons.

Answer:

uses
  IniFiles;

procedure SaveRadioButtonState;
var
  data: TIniFile;
  K: Integer;
begin
  data := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'test.ini');
  try
    for K := 0 to form1.ComponentCount - 1 do
      if Form1.Components[K] is TRadioButton then
        {We only look for the checked radiobutton and save its state, of course}
        data.WriteBool('Options', IntToStr(K),
          TRadioButton(Form1.Components[K]).Checked = True);
  finally
    data.Free;
  end;
end;

procedure ReadRadioButtonState;
var
  data: TIniFile;
  K: Integer;
begin
  data := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'test.ini');
  try
    for K := 0 to Form1.ComponentCount - 1 do
      if Form1.Components[K] is TRadioButton then
        TRadioButton(form1.Components[K]).Checked := data.ReadBool('Options',
          IntToStr(K), True);
  finally
    data.Free;
  end;
end;

2006. július 16., vasárnap

Add formatted line (with TAGs) to TRichEdit - ready function


Problem/Question/Abstract:

Add formatted line (with TAGs) to TRichEdit - ready function

Answer:

procedure AddRichLine(RichEdit: TRichEdit; const StrToAdd: string);
var
  StrLeft: string;
  TempStyle: TFontStyles;
  TempStr: string;

  function FromLeftUntilStr(var OriginalStr: string; const UntilStr: string; const
    ToEndIfNotFound, Trim: Boolean): string;
  var
    TempPos: Integer;
  begin
    TempPos := Pos(UntilStr, OriginalStr);
    if TempPos > 0 then
    begin
      Result := Copy(OriginalStr, 1, TempPos - 1);
      if Trim then
        Delete(OriginalStr, 1, TempPos - 1);
    end
    else
    begin
      if ToEndIfNotFound then
      begin
        Result := OriginalStr;
        if Trim then
          OriginalStr := '';
      end
      else
        Result := '';
    end;
  end;

  function StrStartsWith(var OriginalStr: string; const StartsWith: string; const
    IgnoreCase, Trim: Boolean): Boolean;
  var
    PartOfOriginalStr: string;
    NewStartsWith: string;
  begin
    PartOfOriginalStr := Copy(OriginalStr, 1, Length(StartsWith));
    NewStartsWith := StartsWith;

    if IgnoreCase then
    begin
      PartOfOriginalStr := LowerCase(PartOfOriginalStr);
      NewStartsWith := LowerCase(NewStartsWith);
    end;

    Result := PartOfOriginalStr = NewStartsWith;

    if (Result = True) and (Trim = True) then
      Delete(OriginalStr, 1, Length(NewStartsWith));
  end;

  procedure AddToStyle(var Style: TFontStyles; AStyle: TFontStyle);
  begin
    if not (AStyle in Style) then
      Style := Style + [AStyle];
  end;

  procedure RemoveFromStyle(var Style: TFontStyles; AStyle: TFontStyle);
  begin
    if AStyle in Style then
      Style := Style - [AStyle];
  end;
begin
  TempStyle := RichEdit.Font.Style;
  StrLeft := StrToAdd;
  RichEdit.SelStart := Length(RichEdit.Text);
  while StrLeft <> '' do
  begin
    if StrStartsWith(StrLeft, '<', True, False) then
    begin
      // Bold Style
      if StrStartsWith(StrLeft, '', True, True) then
        AddToStyle(TempStyle, fsBold);
      if StrStartsWith(StrLeft, '', True, True) then
        RemoveFromStyle(TempStyle, fsBold);

      // Italic Style
      if StrStartsWith(StrLeft, '', True, True) then
        AddToStyle(TempStyle, fsItalic);
      if StrStartsWith(StrLeft, '', True, True) then
        RemoveFromStyle(TempStyle, fsItalic);

      // Underline Style
      if StrStartsWith(StrLeft, '', True, True) then
        AddToStyle(TempStyle, fsUnderline);
      if StrStartsWith(StrLeft, '', True, True) then
        RemoveFromStyle(TempStyle, fsUnderline);

      // Color
      if StrStartsWith(StrLeft, '', True, True) then
        RichEdit.SelAttributes.Color := RichEdit.Font.Color;
      if StrStartsWith(StrLeft, '', False, True);
      try
        RichEdit.SelAttributes.Color := StringToColor(TempStr);
      except
        RichEdit.SelAttributes.Color := RichEdit.Font.Color;
      end;
      Delete(StrLeft, 1, 1);
    end;
  end
else
  begin
    RichEdit.SelAttributes.Style := TempStyle;
    RichEdit.SelText := FromLeftUntilStr(StrLeft, '<', True, True);
  end;

  RichEdit.SelStart := Length(RichEdit.Text);
end;
RichEdit.SelText := #13#10;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  AddRichLine(RichEdit1,
    'Test: This is a bold test line which is written in blue. Nice?');
end;

2006. július 15., szombat

OpenGL II: moving and rotating 2D shapes

Problem/Question/Abstract:

On this article I'll show you some basic movement and rotating, I also try to explain how openGL works

Answer:

After showing you how to setup an openGL window and draw a simple quad (OpenGL I: Hello World) plus doing all the message handling and correctly shutting down your openGL application on this article we will add some movement and rotating to our shapes This article is based on the first article, so if you didn't read it you may do so now, so it makes more sense

After reading this article you should be able to move shapes around the screen and rotate them in all the different axis, what can you do with that? well, some basic 2D games... of course moving a quad on the screen is not so exciting, but at least we're moving now =o) later we will see how to add textures to our shapes so they look way better.

Drawing in openGL is relatively easy, because when you draw, say a quad and you want to rotate it, you don't have to do any calculations, openGL works using a turtle graphics kinda of thing, where the pen that is drawing just follows instructions like "go forward", "turn left 90 degrees", etc... and so, as I say the advantage on this is that that many paths are more simply described in relative than in absolute terms.
For example, it's easy to indicate the absolute coordinates of the corners of a square with vertical and horizontal sides, but it's not so easy to find the corners of an inclined square.

A good way to describe this is if you hold a map (is the screen) and then walk following the directions, then you find a square that is facing you, you see it with vertical and horizontal sides to you (then pen), but when you first saw the map that same square was inclined, make sense?

those instructions in OpenGL look like this:

glTranslatef(-1.5, 0.0, PosZ);
//move to position 3.0, 0.0, PosZ (coming and going to/from view)
glRotatef(Angle, 0.0, 0.0, 1.0); // Rotate The quad On The Z axis
glBegin(GL_QUADS); // Draw A Quad
glVertex3f(-1.0, 1.0, 0.0); // Top Left
glVertex3f(1.0, 1.0, 0.0); // Top Right
glVertex3f(1.0, -1.0, 0.0); // Bottom Right
glVertex3f(-1.0, -1.0, 0.0); // Bottom Left
glEnd(); // end of the Quad

The glTranslatef is the "go -1.5 in the X axis", "go O in the Y" and "go PosZ in the Z axis", as you can see there's only one variable, and that's the one that is going to allow my shape to move (only in the Z axis for now, which is going into the screen depth or coming out)

Then the glRotatef is "rotate Angle degrees in the Z axis", if I had put 1.0 in the second parameter is would rotate in the X axis, the third parameter in the Y axis and the last parameter in the Z axis.

Then I tell openGL, glBegin(GL_QUAD): "I'm going to draw a square", and then you specify the four points of the Quad, because you already told openGL that you're going to draw a square is expecting four points if you give it five or six points it will discard them, it will only take four by four (which make a quad) If I put 8 points it will create 2 quads, and so on the same applies if you tell openGL that you are going to draw triangles, you have to give 3 by 3 points

Ok, with no more here's the main code of our article:

Since we are going to do some movement, we now need some variables

var
Angle: glfloat; //angle of the shapes
PosZ: glfloat; //Position in the Z axis
DForward: Boolean; //going forward? or backwards if false

That's all we need, now we initialize our variables on the InitGL part of our program:

Angle := 0;
PosZ := -20.0;
DForward := True;

And that's all we need, we can draw now:

function DrawGLScene(): Bool; { All Rendering Done Here }
begin
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); //Clear Screen and Depth Buffer
glLoadIdentity(); //Reset The View (move to 0, 0, 0)
glColor3f(0.0, 0.0, 1.0); //set the color (1.0=totally blue)
glTranslatef(-1.5, 0.0, -15.0); //Draw triangle always at same position
glRotatef(Angle, 0.0, 1.0, 0.0); // Rotate The Triangle On The Y axis ( NEW )
glBegin(GL_TRIANGLES); // Drawing Using Triangles
glVertex3f(0.0, 1.0, 0.0); // Top
glVertex3f(-1.0, -1.0, 0.0); // Bottom Left
glVertex3f(1.0, -1.0, 0.0); // Bottom Right
glEnd(); // end of the triangle

glLoadIdentity(); //move to position 0, 0, 0
glColor3f(0.5, 0.0, 0.5); //set the color (0.5 red and 0.5 blue)
glTranslatef(-1.5, 0.0, PosZ);
//move to position 3.0, 0.0, PosZ (coming and going to/from view)
glRotatef(Angle, 0.0, 0.0, 1.0); // Rotate The quad On The Z axis
glBegin(GL_QUADS); // Draw A Quad
glVertex3f(-1.0, 1.0, 0.0); // Top Left
glVertex3f(1.0, 1.0, 0.0); // Top Right
glVertex3f(1.0, -1.0, 0.0); // Bottom Right
glVertex3f(-1.0, -1.0, 0.0); // Bottom Left
glEnd(); // end of the Quad

if (DForward) then
//control the position of the quad, it just goes forward or backward
begin
PosZ := PosZ + 0.05; //go Forward
if (PosZ > -10.5) then
//have I gone too far? go backwards now (towards screen depth)
DForward := False
end
else
begin //go backward
PosZ := PosZ - 0.05;
if (PosZ < -20.0) then //have I gone too far into depth? go forward (towards user)
DForward := True
end;
Angle := Angle + 0.4; //change the angle
DrawGLScene := True
end;

Simple right? once you understand how the drawing is done "internally" it's easier to figure out what we can do with openGL.

That's all for this article, let me know what you think about these openGL articles I try to make them as easy to understand as posible, because I know that is not that easy at the begining but let me know if you are interested in more advanced openGL articles and I will try to post some more topics soon.

2006. július 14., péntek

Disable the mouse wheel

Problem/Question/Abstract:

Is there any way to disable the mouse wheel for a particular application or form?

Answer:

You can use a handler for the Application.OnMessage event to filter out messages (e.g WM_MOUSEWHEEL) before any control in your application sees them. Note that this will not work with all mouse drivers. There are some that can be configured to not post the standard wheel messages but to send WM_VSCROLL messages instead if the control under the mouse has scrollbars. This "compatibility mode" is there to make the wheel usable with applications that do not have wheel support build in.

procedure TMainForm.FormCreate(Sender: TObject);
begin
Application.OnMessage := AppMessage;
end;

procedure TMainform.Appmessage(var Msg: TMsg; var Handled: Boolean);
begin
Handled := msg.Message = WM_MOUSEWHEEL;
end;

If you only want to do this for a specific form class you would modify this method to

procedure TMainform.Appmessage(var Msg: TMsg; var Handled: Boolean);
begin
Handled := (msg.Message = WM_MOUSEWHEEL) and
(Screen.Activeform is TMySpecialFormclass);
end;


2006. július 13., csütörtök

Random Number Engine with UNIQUE property

Problem/Question/Abstract:

This class can be used to generate random numbers from MinNumber to MaxNumber returning a number only once if Unique is set. A check is also available to see if the numbers has been used. A dynamic TBit array is used to ensure numbers are returned only once and to see if ALL the numbers have been used.

Useful particularly for card games eg. random numbers from 1 to 52, and once drawn do not draw same card again until pack is empty.

properties

MinNumber Random Number from.
MaxNumber Random Number to.
NumbersTotal Total available numbers.
NumbersUsed  Unique numbers used of total.
NumbersFree Unique numbers free of total.
Unique If true then a number will not be repeated. If false then numbers may be repeated.

methods

IsUsed(Index) Returns true if UNIQUE used and number has been used.

Reset Resets the NumbersUsed and NumbersFree (Used by UNIQUE)

GetRandom Returns a random number from MinNumber  to MaxNumber
(will not repeat a number if UNIQUE is set)

// =========================
// Simple Example
// =========================

procedure TForm1.Button2Click(Sender: TObject);
var
R: TRandomEngine;
begin
R := TRandomEngine.Create;
R.MinNumber := 1;
R.MaxNumber := 52;
R.Unique := true;

repeat
Memo1.Lines.Add(inttostr(R.GetRandom) + '   ' +
inttostr(R.NumbersTotal) + '  ' +
inttostr(R.NumbersUsed) + '  ' +
inttostr(R.NumbersFree));
until R.NumbersFree = 0;

R.Free;
end;

Answer:

unit REngine;
interface

uses Windows, Classes;

type
TRandomEngine = class(TObject)
private
FSelected: TBits;
FArrSize, FNumbersUsed: longint;
FMinNumber, FMaxNumber: longint;
FUnique: boolean;
procedure SizeSelArray;
procedure SetFMinNumber(NewValue: longint);
procedure SetFMaxNumber(NewValue: longint);
function GetFNumbersFree: longint;
public
constructor Create;
destructor Destroy; override;
procedure Reset;
function GetRandom: longint;
function IsUsed(Index: longint): boolean;
property MinNumber: longint read FMinNumber write SetFMinNumber;
property MaxNumber: longint read FMinNumber write SetFMaxNumber;
property Unique: boolean read FUnique write FUnique;
property NumbersUsed: longint read FNumbersUsed;
property NumbersTotal: longint read FArrSize;
property NumbersFree: longint read GetFNumbersFree;
end;

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

// ===================================
// Create and Free the Object
// ===================================

constructor TRandomEngine.Create;
begin
FSelected := TBits.Create;
FNumbersUsed := 0;
FMinNumber := 0;
FMaxNumber := 0;
FArrSize := 0;
FUnique := false;
Randomize;
end;

destructor TRandomEngine.Destroy;
begin
inherited Destroy;
FSelected.Free;
end;

// ===========================
// Property Get/Set methods
// ===========================

procedure TRandomEngine.SetFMinNumber(NewValue: longint);
begin
if (NewValue <> FMinNumber) then
begin
FMinNumber := NewValue;
if FMinNumber > FMaxNumber then
FMaxNumber := FMinNumber;
SizeSelArray;
end;
end;

procedure TRandomEngine.SetFMaxNumber(NewValue: longint);
begin
if (NewValue <> FMaxNumber) then
begin
FMaxNumber := NewValue;
if FMaxNumber < FMinNumber then
FMinNumber := FMaxNumber;
SizeSelArray;
end;
end;

function TRandomEngine.GetFNumbersFree: longint;
begin
Result := FArrSize - FNumbersUsed;
end;

// =======================================
// Resize the boolean array (FSelected)
// =======================================

procedure TRandomEngine.SizeSelArray;
var
i: longint;
begin
FArrSize := FMaxNumber - FMinNumber + 1;

if FArrSize > 0 then
begin
FSelected.Size := FArrSize;
for i := 0 to FArrSize - 1 do
FSelected[i] := false;
end;

FNumbersUsed := 0;
end;

// =======================================
// Reset avail,used and free numbers.
// Reset FSelected array to false for
// IsUsed()
// =======================================

procedure TRandomEngine.Reset;
begin
SizeSelArray;
end;

// ===================================================
// Return true/false if numbers has been used if
// Unique is set
// ===================================================

function TRandomEngine.IsUsed(Index: longint): boolean;
var
Retvar: boolean;
begin
if (Index < FMinNumber) or (Index > FMaxNumber) then
Retvar := false
else
RetVar := FSelected[Index - FMinNumber];

Result := RetVar;
end;

// ===================================================
// Return a random number based on Min - Max
// If Unique then generate based on FSelected
// array (ie. make sure number has not been used
// ===================================================

function TRandomEngine.GetRandom: longint;
var
V: longint;
NumSelected: boolean;
begin
if FUnique and (FNumbersUsed = FArrSize) then
V := 0
else
begin
repeat
V := Random(FMaxNumber - FMinNumber + 1) + FMinNumber;
if not FUnique then
NumSelected := true
else
begin
if FSelected[V - FMinNumber] then
NumSelected := false
else
begin
NumSelected := true;
FSelected[V - FMinNumber] := true;
inc(FNumbersUsed);
end;
end;
until NumSelected;
end;

Result := V;
end;

end.