HomePage Delphi Library Shopping

Creating non-rectangular windows

version markers: 2.0 3.0 4.0

By Kent Reisdorph

--------------------------------------------------------------------------------

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.

Do you get the point(s)?

Our example application for this article will create a main window in the shape of a five-pointed star. Before you can create an irregularly shaped window, you must determine the points (X and Y coordinates) that will make up the outline of the window. This can be a somewhat tedious exercise, depending on the complexity of your outline.

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.

Creating a Windows region from the points

Windows allows you to create several types of regions. Table A lists the Windows region functions and a description of each.

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.

Set the window region

You're ready to tell Windows to use your new polygon region as the main form's window region. You do that in the form's OnCreate event handler with the SetWindowRgn function. SetWindowRgn simply tells Windows what part of the window it should paint. Anything that falls outside of the specified region isn't painted.

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. The application code

Listing A shows our example application's main unit. We've added some painting code to outline the window in black so it's easier to see. We've also added code that lets you drag the window by clicking anywhere in it. Note that the title bar of the window is still there--it's just invisible. You can still close the application by pressing [Alt][F4].

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.

Conclusion

You probably shouldn't use non-rectangular windows often. However, if you have a specialty application that can benefit from an irregularly shaped window, the technique we've presented in this article is for you.

 

--------------------------------------------------------------------------------

Hosted by www.Geocities.ws

1