Borland Delphi Informant Magazine Home InfoPower 4


Member Services
FREE Trial Issue
New Subscription
Renew Subscription
Delphi CD-ROM
Report Problems
Change of Address


Delphi Informant
Features
Case Studies
News
New Products
Book Reviews
Product Reviews
Opinion
Back Issues

Downloads
Article files
Third-party files
Upload A File

Informant
ICG News
Contact Us
Advertise With Us
Write For Us
Delphi Informant Magazine Complete Works
 
Delphi Informant Magazine Complete Works
 •

Dropping Hints: Part II : A Hint Window Application


 •

Dropping Hints: Part I : Taking Control of Fly-over Help



 •

Dropping Hints: Part II


 •

Multiple Inheritance: In Delphi?


 •

Embedded Forms


 •

A Multimedia Assembly Line: Part I


 •

An Automation Server





Tell a friend
about this article!




OP Tech

TRect / Windows API

Rectangles

A Closer Look at the TRect Windows Data Type

Rectangles are a major part of Windows life. Indeed, all visible VCL components have Top, Left, Right, and Height properties that form a rectangle. All window objects in Windows are described as rectangles. Thus, the Windows API has a large group of functions that use and manipulate rectangles. The Delphi VCL adds many new ways to use rectangles.

The basic RECT is described by Windows as a structure of four integers: top, left, bottom, and right. The following is the C declaration found in the API Help:

typedef struct _RECT

{  

  LONG left;

  LONG top;

  LONG right;

  LONG bottom;

} RECT;

In Delphi, this structure is defined as a record of type TRect. This is its declaration:

TRect = record

  case Integer of

// This is the standard part of the record.

    0: (Left, Top, Right, Bottom: Integer);

// We'll talk about this later.

    1: (TopLeft, BottomRight: TPoint);     

end;

This article attempts to provide a comprehensive look at this important Windows data type. It will detail how and when it's used. It will also take a short spin through some API functions - some not widely known - to show how you can fully utilize TRect.

In its default form, TRect contains the same four integers: left, top, right, and bottom. It's generally similar to RECT, and can be used in one way or another whenever the API calls for a RECT.

A TRect defines an area bounded by left, top and right, bottom. This area does not have to refer to actual window real estate, but can represent a virtual rectangle as well. The values in the rectangle are offsets of the top, left. The top, left of the coordinate system is 0,0, and that increases as the distance moves down or right. The size of the rectangle is the difference between the furthest edge and the closest edge. For example, if the rectangle is X=25,Y=25 to X=50,Y=50, then the rectangle is 25 high by 25 wide.

Basic TRect Manipulation

Because rectangles are so important, Windows has many functions to manipulate and analyze them. To use a TRect, you declare it in the var section of a unit or procedure. As with all Delphi variables, the members of the TRect will already have random values assigned to them. If you want to set all the values to zero, you can set all the members manually, or you can use an API function named SetRectEmpty, that "empties" the rectangle. A TRect is considered empty when there can be no space in it. If the bottom value is less or equal to the top value, or the right is less or equal to the left, the rectangle is considered empty.

SetRectEmpty takes the TRect as a parameter and changes all its member values to zero. You would call SetRectEmpty like this:

var

  Rec: TRect;

...

begin

  SetRectEmpty(Rec);

end;

Most API functions will not handle an empty rectangle and will just ignore it. To determine if a TRect is empty, you can use the API's IsRectEmpty function. IsRectEmpty takes the TRect as a parameter and returns a Boolean True if the TRect is empty, and a False if it isn't.

You can fill the TRect's members by assigning them directly. For example:

Rec.Left := 30;

However, the API provides a quicker way to fill the TRect's members. It's called SetRect, and takes as parameters the TRect and four signed integers, for top, left, bottom, and right. This frees the programmer from having to manually fill all the members. Delphi has an even better way to do this: the Rect function. The Rect function also takes the four integer parameters, but instead of modifying an existing TRect, it returns a TRect as its result. Thus, to assign a value to a TRect, you can use code like this:

Rec := Rect(30,23,173,142);

Because Rect returns a 16-byte array, it has an advantage over the API function. In the API approach, if you need to use a TRect as a parameter to a function, you have to declare it, assign the value using SetRect, then pass the TRect to the function:

function DoSomeTRectThing;

var

 Rec:TRect; // We have to define a TRect;

begin

  SetRect(Rec, 30, 23, 173, 142);

  ProcessTRect(Rec);

end;

The Delphi approach allows you to use the Rect function as a parameter to a procedure:

function DoSomeTRectThing;

begin

  ProcessTRect(Rect(0, 23, 173, 142));

end;

Note, however, that this will not work with a function that wants to modify the value of the TRect, or with a function that requires a PRect (we'll discuss this in more detail later).

Copying the values of one TRect to another can be done using the CopyRect API function. CopyRect takes the source and the destination rectangles as parameters. However, because TRect is a standard Delphi type, all you have to do is assign one TRect to another, using the assignment operator ( := ).

When it comes to comparing TRect objects, Delphi won't allow a direct comparison, i.e. with the = operator. So instead of making a long-winded "if ladder" (if top = top then if left = ... etc.), you could use the API EqualRect function, which takes the two Rect objects and returns a Boolean. A call to EqualRect looks like this:

if EqualRect(Rect1, Rect2) then

  DoSomthing // They are equal.

else

  DoSomeThingElse;

To change the width and length of a TRect, you can use the API InflateRect function. You pass the TRect and the x and y amount of pixels you want changed. If the value is positive, the rectangle is enlarged; if it's negative, the rectangle is made smaller by that amount.

InflateRect inflates the rectangle from all sides. If the TRect you passed is 10,10 - 20,20, and you passed 5 for x and 10 for y, the resulting rectangle will be 5, 0, 25, 30 (see Figure 1).


Figure 1: InflateRect inflates the rectangle from all sides.


Moving a whole TRect can be done by using the API OffsetRect function; pass an x and a y to move the TRect by that amount. OffsetRect adds that amount to each member value so that if you pass a positive, the rectangle moves to the right and down; if you pass a negative, it moves left and up.

Advanced TRect Manipulation

The Windows API provides functions for more advanced manipulation of rectangles. The PtInRect function will test if a certain position is in a rectangle. PtInRect takes a TRect and a TPoint (also a Delphi record type, similar to a TRect, and describes only one point as x and y). If the TPoint is in the TRect, then the PtInRect returns True; otherwise, it returns False.

Rectangles don't have to be alone in a coordinate system. You can have many rectangles floating around in the same virtual space. Some rectangles may overlap and intersect with others. The Windows API provides functions that analyze the positions of two TRect objects in relation to each other, and returns a rectangle that holds the result.

The following API functions take three parameters. The first is the rectangle to fill with the result; the next two are the rectangles to be analyzed. Overlapping rectangles share a rectangular area, and IntersectRect fills a TRect with the coordinates of this rectangle (see Figure 2).


Figure 2: Overlapping rectangles share a rectangular area, and IntersectRect fills a TRect with the coordinates of this rectangle.


You can use the UnionRect function to retrieve a rectangle that fully encompasses both rectangles - even if they don't overlap (see Figure 3).


Figure 3: You can use the UnionRect function to retrieve a rectangle that fully encompasses both rectangles - even if they don't overlap.


The SubtractRect function will return the part of the second rectangle not covered by the first. This can only be a rectangular area if the second rectangle totally covers the first in either width or height, as illustrated in Figure 4. If this is not True, then SubtractRect ignores the actual subtraction and simply returns the coordinates of the first TRect. This is something you have to watch out for because it can mess up your program's logic. It's recommended you test the results using EqualRect (unless that's the result you want).


Figure 4: The SubtractRect function will return the part of the second rectangle that is not covered by the first. This can only be a rectangular area if the second rectangle totally covers the first in either width or height.


All these functions are demonstrated in the included sample application (see the end of this article for download details). Figure 5 is a table with all the rectangle manipulation functions and their tasks.

Function

Use

CopyRect

Copies the values of one TRect to another.

InflateRect

Enlarges the rectangle from all sides.

IntersectRect

Returns the overlapping area of two rectangles.

IsRectEmpty

Determines if a rectangle does not contain space.

OffsetRect

Moves the entire rectangle.

Rect

VCL - Sets all the member values at once.

SetRect

API - Sets all the member values at once.

SetRectEmpty

Sets all the rectangles members to 0.

SubtractRect

Returns the area of the first rectangle that is not covered by the second.

UnionRect

Returns the smallest rectangle that can hold both rectangles.

Figure 5: TRect manipulation functions.

Extra TRect Goodies

A TRect is represented in memory as a block of data, 16 bytes long. This is divided into four integers of 4 bytes each. The Delphi TPoint is a block of memory 8 bytes long and divided into two integers describing an x, y position. Two TPoints can fit into one TRect.

Because a TRect specifies two points, the left, top and the right, bottom, it's useful that Delphi allows variant parts in a record. Variant parts allow you to access parts of a record in different ways. Here are the variant parts, and what they equal:

Rect.Topleft.x = Rect.Left

Rect.TopLeft.y = Rect.Top

Rect.BottomRight.x = Rect.Right

Rect.BottomRight.y = Rect.Bottom

This is helpful in a case where you want a point of the TRect to get its value from a function that returns a TPoint. For example, GetCursorPos is an API function that fills a TPoint with the position of the mouse cursor in screen coordinates. To use the mouse position as the bottom-right part of a TRect, you could use this code:

GetCursorPos(MyRect.BottomRight);

Converting Screen Coordinates to Client Coordinates

API functions such as GetCursorPos return coordinates in offsets from the top left of the screen. To convert a TRect from screen coordinates to the client coordinates of a window, we can take advantage of the TRect's variant parts (TopLeft and BottomRight), and the TControl.ClientToScreen method. The listing in Figure 6 shows functions that convert screen coordinates to client coordinates, and vice versa.

function ScreenToClientRect(Rec: TRect;

  Control: TControl): TRect;

begin

  Result.TopLeft := Control.ScreenToClient(Rec.TopLeft);

  Result.BottomRight :=

    Control.ScreenToClient(Rec.BottomRight);

end;

function ClientToScreenRect(Rec: TRect;

  Control: TControl); TRect;

begin

  Result.TopLeft := Control.ClientToScreen(Rec.TopLeft);

  Result.BottomRight :=

    Control.ClientToScreen(Rec.BottomRight);

end;

Figure 6: Converting Screen and Client coordinates.

What Is a PRect?

When you pass a TRect to a function as a parameter, you are passing a full 16 bytes containing the TRect to the function. This is only a copy of the original value. The function can inspect the values and do some action based on it, but it can't change the original TRect. This is what the Rect function does. It returns a 16-byte block of memory containing the values.

However, the calling function cannot change the values (as many of the API functions want to do). For a function to change the original TRect, you must pass it as the memory address of the record. The function can then look at the original TRect and change its values.

Delphi defines some of the API functions as expecting var TRect. Internally, whenever the compiler sees a function parameter declared as a var, it does not pass the actual record, but passes a memory address to the original record. This is why you can use a standard TRect to call API functions; all of which want the address of the TRect. However, some API definitions expect a variable type named PRect, which is a pointer to a TRect. Delphi 3 or 4 can show you which type is required with its Code Parameters tool hint window. To pass a TRect to a function requesting a PRect, you can use the @ (at) operator before the TRect's name. The @ operator causes the variable to which it is prepended to act as a pointer, and have it passed as an address.

So what is the difference between var TRect and PRect, and why did Inprise define some parameters as var TRect objects and others as PRect objects? Well, according to Peter Below of TeamB, it seems that whenever an API function might require a nil to be passed to it, the function was defined as a PRect because a var TRect cannot accept a nil.

TRect in Everyday Life

So why is TRect so important? TRect describes a rectangular area, and many objects in Windows can be described as rectangles. Let's look at some Delphi/API functions that accept TRect objects as a parameter.

A window is a rectangle, and so are all VCL controls. A TRect can adequately describe them. Delphi provides the BoundsRect method for the TControl and its descendants. BoundsRect returns a TRect describing the position of the control within its parent window. To get the rectangular area of non-VCL Windows, you can use the API GetWindowRect, which fills a TRect with the description.

To set the size and position of a VCL control, you can change the BoundsRect property, or, for a non-VCL window, you can use the API SetWindowRect function.

The client rectangle of a window is the area not covered by the Windows border, the menu bar, or any other of the standard interface objects. To retrieve this rectangle, you can use the ClientRect property for VCL controls, or the GetWindowRect API function for others.

Clipping the cursor means that you force the cursor to stay within a certain part of the screen. This is a rectangle that is passed to the ClipCursor API function. The coordinates for the TRect are in offsets from the top, left of the screen. For any mouse movement, Windows ensures the mouse pointer does not move out of the rectangle. Because there is only one shared mouse for the whole system, the clipping rectangle affects all applications. To cancel the clipping rectangle, call ClipCursor again, this time passing it a nil. Because ClipCursor does at times take a nil, it's defined as a PRect, so to pass a TRect to ClipCursor, you'll have to use the @ operator.

It's important to make sure that the rectangle you pass to the ClipCursor is not empty. If it's empty, the cursor will be stuck in its original position and will remain stuck there until it is freed. Therefore, it's recommended that you test for IsRectEmpty before calling ClipCursor.

MDI (Multiple Document Interface) Windows have a mechanism to cascade all open child Windows. There isn't an easy way to cascade all the top-level windows. You can, however, use the function that MDI Windows use internally to cascade its client windows: the CascadeWindow function. It takes a handle to the parent window. But if the handle is nil, the desktop Windows is assumed. You can pass an open-ended array of window handles to the function, and the function will only cascade those windows. The interesting attribute of this function for our discussion is that you can specify the rectangle in which the windows will be cascaded to.

TCanvas

Delphi provides the TCanvas property for many of its components. TCanvas is an encapsulation of a Windows device context. Device contexts are objects in Windows that represent the surface of a window or a bitmap. You draw to a window by drawing to its device context. The device context is essentially a structure (record) with information about the drawing state of the window. Pen, brush, and font information are stored in the device context. All Windows use device contexts to draw themselves, including controls such as list boxes and buttons. Windows provides a number of functions to draw text and other shapes to a device context. Delphi encompasses many of these functions into properties and methods of the TCanvas object. Some components (such as the TForm and TImage) automatically surface the TCanvas property.

Whenever it's time for a window to redraw itself, Windows sends a message. Delphi converts this message to the OnPaint event and the control repaints itself. The graphical drawing routines can take a long time to execute, and there might be no need to repaint the whole control simply because a small corner got uncovered. Indeed, the whole window doesn't get repainted. Windows defines something called a Clipping Rectangle. This is the rectangle to which paint requests will be drawn. Any drawing out of this rectangle will be ignored.

On a TCanvas, this rectangle can be retrieved using the ClipRect property. You can also set this property. To determine if a certain rectangle is part of the clipping region, you can use the RectVisible function. The TCanvas encapsulates some of the Windows GDI (Graphics Device Interface) functions that make them easier to use. I will run through some that are somewhat related to TRect.

You can copy a whole image onto a TCanvas by using the Draw method. However, Draw copies the original image in its original height and width. You can use the StretchDraw method to specify the destination rectangle. The drawn graphics will be enlarged or shrunk to fit into the TRect you provided. Even better is the CopyRect method, which allows you to specify the source rectangle as well.

The TextRect function is like the TextOut function in that it draws text on to the TCanvas in the specified x and y. But, TextRect also allows you to specify a clipping rectangle to draw the text in. Any text that would output outside the bounds of this rectangle will not be drawn.

It's easier to handle a rectangle as a TRect than as separate x, y, x, y values. Most of the shape-drawing functions in Delphi - even the Rectangle method - take separate x, y, x, y variables. You can still use the TRect to draw a rectangle by using the FrameRect method. FrameRect draws a rectangle with one-pixel walls using the brush. To fill a rectangular area using the brush, you can call the FillRect method. The code listing in Figure 7 shows a quick way to draw a three-dimensional bevel frame using FrameRect.

procedure Draw3DBevelFrame(rct:TRect);

begin

with canvas do begin

    brush.color := clBtnShadow;

    FrameRect(rct);

    brush.color := clBtnHighlight;

    OffsetRect(rct,-1,-1);

    FrameRect(rct);

  end;

end;

Figure 7: Drawing a bevel using FrameRect.

When a control has the focus, it's expected to have some kind of visual indicator showing this. Most controls draw a dotted rectangle around them. The API has a function DrawFocusRect that does this, and Delphi encapsulates this as the DrawFocusRect method. This method draws the dotted line in an XOR pen; every second pixel is inverted. This allows you to erase the line by redrawing it again. The listing in Figure 8 shows the procedures you can use to add a rubber-band-style box to your application.

// Form-level variables.

var

rec: TRect;

 

procedure TForm2.FormMouseDown(Sender: TObject;

Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

begin

rct.TopLeft.x := x;   // These will be the origin

rct.TopLeft.y := y;  // of the rectangle.

  // Erase any BottomRights left from last time.

rct.BottomRight := rct.TopLeft;

end;

 

procedure TForm2.FormMouseMove(Sender: TObject;

Shift: TShiftState; X, Y: Integer);

begin

if not (ssLeft in Shift) then

    Exit;   // Mouse is not down.

  // Erase the last rectangle by drawing over it.

Canvas.DrawFocusRect(rct);        

  // Set the new bottom-right position.

rct.Bottom := y;                  

rct.Right := x;

Canvas.DrawFocusRect(rct);   // Draw the new rectangle.

end;

 

procedure TForm2.FormMouseUp(Sender: TObject;

Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

begin

Canvas.DrawFocusRect(rct);   // Erase for last time.

end;

Figure 8: Implementing a rubber-band box.

Drawing with the API

There are many API drawing functions that TCanvas does not encapsulate. Many of these functions are the ones Windows uses to draw standard window elements. Because most of these elements are rectangular, they take TRects as parameters. These functions are hidden gems that most developers don't know about.

We'll start off with the function that Windows uses to draw borders around windows. The DrawEdge function can draw either a full window border, or only one of the corner edges. The first parameter is the device context to draw on. The device context of a VCL window is contained in Canvas.Handle. The second parameter is a TRect defining the border rectangle. The third parameter is a group of flags that specify the type of border to draw, e.g. BDR_RAISEDINNER, EDGE_BUMP, etc. The fourth parameter is a group of flags that specify what the border is, e.g. an edge, a full border, etc. Check the API documentation for DrawEdge.

The DrawFrameControl function is used by Windows to build almost all the standard controls. This function can draw all the caption buttons (close, maximize, help, minimize, restore, etc.) or check boxes and radio buttons. It can also draw scrollbar buttons in all directions. It can draw the buttons in normal, disabled, or down states. In other words, this is the Windows-control-drawing workhorse. It takes a drawing TRect and other parameters that specify the type of button to draw.

ExtTextOut is the big brother of the VCL's TCanvas.TextRect, and allows for many more formatting options. It allows you to justify the text as right, left, center, top, or bottom. You can also word-wrap the text in the formatting rectangle.

To draw a line of text surrounded by a three-dimensional box (raised or lowered), you can use the DrawStausText API function. This function was originally intended to allow programs to draw text segments on status bars, but can draw to any other device context.

Conclusion

Some beginning developers tend to shy away from a procedure that takes a record, and would rather stick to procedures that take verbose parameters. They would rather labor over the rectangle method, which takes four integers, than pass a simple TRect to the FrameRect parameter. In this article, I've tried to show beginners and advanced developers alike that the TRect isn't really so frightening, and can even be downright friendly. Knowing how to use the TRect is the key to using many API functions.

The file referenced in this article is available for download.

Motty Adler is a freelance programmer/consultant and President of WAISS Systems. He has been developing software for over five years, and has used Delphi for over two years. He can be reached at mailto:aisssoft@aol.com. WAISS Systems' Web site is http://www.waiss.com/.

Microsoft Internet Explorer

Top of page
 
InfoPower 4

Informant Communications Group

Informant Communications Group, Inc.
10519 E. Stockton Blvd., Suite 100
Elk Grove, CA 95624-9703
Phone: (916) 686-6610 • Fax: (916) 686-8497

Copyright © 1999 Informant Communications Group. All Rights Reserved. • Site Use Agreement • Send feedback to the Webmaster • Important information about privacy