Delphi programming tips
by Kent Reisdorph
You can download� sample files from� www.zdjournals.com/ddj as part of the file dec98.zip. Click on the Source Code hyperlink.
Have you ever seen a Windows program that doesn't have a rectangular main window? Sometimes the main window of this type of application is in the shape of a company logo or made to fit some bitmap. This technique is typically reserved for a very unique type of Windows application, for a splash screen, or a fancy About dialog box.
Fortunately, creating a non-rectangular window is fairly easy to achieve, once you know what's required. In this article, we'll show you how to create the irregularly shaped window shown in Figure A.
Figure A: Our example program displays a star-shaped window.
We'll use the Win32 API functions CreatePolygonRgn and SetWindowRgn. You'll probably be surprised at how easy it is to create this type of window in a 32-bit Windows application.
Using a bitmap as a pattern works fairly well. You can load the bitmap into Windows' Paint program and then place the mouse pointer over each primary point in your outline. The status bar of the Paint program will show you the X and Y coordinates of the pixel under the pointer. Write down the coordinates of each point--you'll use the coordinates later to create an array of points. Figure B shows the star we'll use for our example program and the coordinates of each point.
Figure B: Our star outline's points have these coordinates.
Once you have the points, you can put them into an array of TPoints. Here's how the code looks for our star:
const
Points : array[1..10] of TPoint =
((X:203;Y:22), (X:157;Y:168),
(X:3;Y:168), (X:128;Y:257),
(X:81;Y:402), (X:203;Y:334),
(X:325;Y:422), (X:278;Y:257),
(X:402;Y:168), (X:249;Y:168));
As you can see, the array of points exactly matches the points on the star in
Figure B. Now that you have an array of points, you can move on to the next
step.
Table A: Windows region functions
| Hook | Description |
|---|---|
| CreateEllipticalRgn | Creates an elliptical region (circle or oval). |
| CreatePolygonRgn | Creates a polygon region from a series of points. |
| CreatePolyPolygonRgn | Creates a region made up of several polygons. |
| CreateRectRgn | Creates a rectangular region. |
| CreateRoundRectRgn | Creates a rectangular region with rounded corners. |
For this example, you'll use the CreatePolygonRgn function. Creating a polygon region is as simple as one call:
var
Rgn : HRGN;
...
Rgn := CreatePolygonRgn(
Points, High(Points), ALTERNATE);
CreatePolygonRgn takes three parameters. The first parameter specifies the array
of points that make up the polygon. The second parameter specifies the number of
points in the array. Since your array is declared with elements from 1 to 10,
you can use the High function to pass the number of points. Doing so allows you
to change the point array later without having to change the code that creates
the region. Finally, the last parameter of CreatePolygonRgn specifies the
polygon fill mode; this parameter can be set to ALTERNATE or WINDING. Since your
polygon is a simple outline (none of the lines intersect), you don't care what
the fill mode is. (For more information on polygon fill modes, see the Windows
API Help on SetPolyFillMode.) Now you have a region that Windows can use to
create your main form's window region.
It's important to realize that the window (the main form, in this case) retains its original size, but only the area inside the polygon is painted. In other words, if your form is 400x400 pixels before setting the region, it will still be 400x400 after setting the region. Setting the region doesn't modify the form in any way--it only describes the area that Windows should paint.
Setting the window region is the simplest step of all:
SetWindowRgn(Handle, Rgn, True);SetWindowRgn also takes three parameters. The first parameter is the window handle of the window that will be modified to use the new region. The second parameter is the handle of the region itself. The last parameter is the Windows repaint flag. If this parameter is True, Windows will repaint the window after setting the region; if it's False, Windows won't repaint the window after setting the region. In this case, it doesn't matter what value you specify for this parameter, because you're setting the window region in the form's OnCreate event handler. Windows will repaint the window when the form is shown, so the value of the last parameter is immaterial.
Listing A: StarRgnU.pas
unit StarRgnU;
interface
uses
Windows, Forms, Controls,
StdCtrls, Classes, Messages;
type
TForm1 = class(TForm)
CloseBtn: TButton;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure CloseBtnClick(Sender: TObject);
private
{ Private declarations }
procedure WmNCHitTest(var Msg :
TWMNCHitTest); message WM_NCHITTEST;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
const
{ An array of points for the star region. }
RgnPoints : array[1..10] of TPoint =
((X:203;Y:22), (X:157;Y:168), (X:3;Y:168),
(X:128;Y:257), (X:81;Y:402), (X:203;Y:334),
(X:325;Y:422), (X:278;Y:257), (X:402;Y:168),
(X:249;Y:168));
{ An array of points used to draw a line }
{ around the region in the OnPaint handler. }
LinePoints : array[1..11] of TPoint =
((X:199;Y:0), (X:154;Y:146), (X:2;Y:146),
(X:127;Y:235), (X:79;Y:377), (X:198;Y:308),
(X:320;Y:396), (X:272;Y:234),(X:396;Y:146),
(X:244;Y:146), (X:199;Y:0));
procedure TForm1.FormCreate(Sender: TObject);
var
Rgn : HRGN;
begin
{ Create a polygon region from our points. }
Rgn := CreatePolygonRgn(
RgnPoints, High(RgnPoints), ALTERNATE);
{ Set the window region. }
SetWindowRgn(Handle, Rgn, True);
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
{ Draw a line around the star. }
Canvas.Pen.Width := 3;
Canvas.Polyline(LinePoints);
end;
procedure TForm1.CloseBtnClick(Sender: TObject);
begin
Close();
end;
{ Catch the WM_NCHITTEST message so the user }
{ can drag the window around the screen. }
procedure TForm1.
WmNCHitTest(var Msg: TWMNCHitTest);
begin
DefaultHandler(Msg);
if Msg.Result = HTCLIENT then
Msg.Result := HTCAPTION;
end;
end.