Help & Manual authoring tool
Delphi: Non-rectangular shapes - Part I

Non-rectangular shapes - Part I

Copyright © 2000 Alirio A. Gavidia B.. Para ver más de
mis artículos visite Programación Orientada en Delphi

EurekaLog - Catch and log every exception!

Introduction

The present essay intends to show the possibilities of use of non-rectangular shapes under Windows. The use is simple, but the possibilities look interesting. Additionally, in particular in this first part, we will make use of two important elements: Canvas and Timer.

The first example

In the previous article (see "Post-it". Sizable windows without border or title) I made reference to the possibility of creating an analog round clock like the one of Windows "PowerToys". Making a round form is simple and it is limited to calling a couple of procedures. The clock must also draw the hands (which requires certain mathematic calculation) and update about 60 times per minute (this is what the TTimer is for).

Note: Analog Clock in opposition to Digital. The term is NOT correct because the clock we create here -even if it has hands- is digital. For the clock to really imitate an actual analog behavior it would have to have a continuos movement. The one we create here moves from second to second and therefore it has finite and defined states and that makes it qualify as digital. It seems a more adequate term is "hands clock".

Without title

The clock is based on a simple borderless form, eliminating the title bar, which results in a bother when we have a round form. This generates the additional problem of moving the form. For this reason the drag operation should be attended from the client area of the form. To solve this inconvenience there are various ways. I choose the one applied in the previous article (use of wm_Syscommand $f012):

procedure TForm1.FormMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  ReleaseCapture;
  Perform(wm_Syscommand, $f012, 0)
end;

Another technique is responding to Wm_NcHitTest and returning htCaption as the result. This is simply telling Windows that the click was made in the title area. It seems to me that there is at least another technique, but it isn't the objective of this essay.

The "timer"

In the form we included a TTimer control. This control fires an event approximately at intervals (defined by the Interval property given in milliseconds). Its use is well documented in Delphi, but I must add something: it isn't exact.

The event associated with the timer executes three actions: eliminates the hands from the previous position, takes the system time, and puts the hands in a new position.

The "Canvas"

The Canvas provides graphical capacities for graphic controls and forms. I offers possibilities to draw lines, ellipses, polygons, text and others. Its usage is simple and practical.

We made use of the following elements of the Canvas:

  • Pen, to set the color and mode of the line draw.
  • MoveTo, to set the initial point of a line draw.
  • LineTo, to draw a line from the last position to the new one.
  • Rectangle, to draw a rectangle according to the given parameters.

Particularly we have to note the property Canvas.Pen.Mode that allows the use of values like pmXor or pmCopy (among many others).

Note: The use of Xor as a logical operator has an interesting property which is that applied twice you get the original value, i.e.: x xor y results in z, and if you apply xor y again to z it results in x. This is useful for simple encrypting where we want to apply a key to encrypt and the same key and the same algorithm to decrypt. It is also useful when we want to draw something and then erase it because it is only required to draw it again applying a "xor" between the background and the pixel to draw.

The Paint event of the form certainly makes use of the Mode and Color properties of the Pen obtaining a curious effect. The result, at least for me, wasn't the expected, however it was nice. All this seems to be the consequence of the way in which a rectangle is created. It is filled and then the upper and left border are drawn, that under the Xor operator generate a white color and the resulting image generates a 3D effect. I suggest the reader to comment out the lines Mode and Color to draw his own conclusions.

As an additional comment, Pen.Color only affects the border of the rectangle. The filling is defined by the Brush property of the Canvas which in this case remains with its default value.

procedure TForm1.FormPaint(Sender: TObject);
Var
  X1, Y1, i : integer;
  Angle     : double;
begin
  Canvas.Pen.Mode := pmXor;
  Canvas.Pen.Color := clBtnFace;
  for i:=1 to 12 do
  begin
    Angle := i * 2 * Pi / 12;
    X1 := trunc(X0 + Len * Sin(Angle));
    Y1 := trunc(Y0 - Len * Cos(Angle));
    Canvas.Rectangle(X1-4,Y1-4,X1+4,Y1+4)
  end;
  Canvas.Pen.Mode := pmCopy;
  Canvas.Pen.Color := clBlack;
end;

Arithmetic

The coordinates system has the point zero in the upper-left corner. To determine the position of the hand we need the center and the ending point. The center is the half of the width and the height. For the length of the hands we take a fraction of the minimum between the width and the height.

Knowing the center and length of the hand, we only need to know the position of its ending point and for this we determine the angle. Here we end up turning to trigonometry. The angle is determined with the zero in 12 o'clock, 90 degrees at 3 o'clock, 180 degrees at 6 and 360 at 12. The sine of the angle gives us the opening in the "X" coordinate, while the cosine gives us the opening of the "Y" coordinate. As an additional remark, the coordinates system in the "Y" axis is inverted with respect of the usual trigonometric systems. All angles must be expressed in radians to be used with the sine and cosine functions. The procedure DrawWatchHand makes the calculations (also in FormPaint)

procedure Tform1.DrawWatchHand;
  :
  :

  // Now draw Second
  Canvas.MoveTo(X0, Y0);
  Angle := S * 2 * Pi / 60;
  X1 := trunc(X0 + 9*Len * Sin(Angle)/10);
  Y1 := trunc(Y0 - 9*Len * Cos(Angle)/10);
  Canvas.LineTo(X1, Y1);
  :
  :

Notice that each hand hand uses a proportion of the Len value. For example the seconds are 9/10 of Len, minutes are 3/4 and hours are 1/2.

Round

Until now we have used the Canvas to draw, eliminated the title bar and substituted its functionality, we have done some arithmetic and we have made use of a timer. The central point that gives title to this article is the creation of a non-rectangular shape. Well, this is tremendously easy and is solved with two calls to Windows in the FormCreate of the main form:

  hRegion := CreateEllipticRgnIndirect(R);
  SetWindowRgn(handle, hRegion, true);

CreateEllipticRgnIndirect is a version of CreateEllipticRgn with the parameters (a rectangle) packed in only one structure. This routine returns a "handle" to an elliptic region circumscribed to the given rectangle. If you want you can define non circular regions using CreatePolygonRgn for polygons, CreateRectRgn for rectangles (without sense in our case, but it is possible), CreateRoundRectRgn for rectangles with round corners, or CombineRgn to combine the results of other regions.

SetWindowRgn defines the area in which a window is visible. It is interesting that under this concept the rectangular window still exists and we only defined something like a hole from where we can see it.

A "Bug", a second example

My first attempt of a non rectangular shape was a couple of years ago with Delphi 3. It was a mischief motivated in that I know people who terribly hate cockroaches (and here in the tropic there are spectacular ones). So I created a cockroach that wanders on the Windows desktop. Its form is elliptic and I fundamentally created some images for different angles (four: two verticals and two horizontals). The result is a bit rustic, but I enjoyed creating this "bug". I very well know there are better ways to do this, but it comes to the case because of the subject of this article.

The next

Well, this opens interesting possibilities of use (as I mentioned before). I think the most adequate seems to be the one of "Skins" like some applications. For reference you have "Winamp", "NeoPlanet", "Windows Media Player" (new version). The technique that allows non rectangular shapes is applicable to buttons and other controls.

Bibliography:

  • Delphi Developer's Handbook,
    Marco Cantů, Tim Gooch, John F. Lam
    SYBEX
    ISBN: 0-7821-1987-5
    1.998

  • La Biblia de Delphi 5
    Marco Cantů
    ANAYA
    ISBN: 84-415-0994-8
    2.000

The full source code of the examples are available for download.

JfControls Library - for Delphi and C++ Builder
Copyright © 2000/2006 Ernesto De Spirito.   All rights reserved.