Delphi Informant 95 2001
Delphi Informant 95 2001
19 Algorithms DEPARTMENTS
Map Coloring — Rod Stephens
Mr Stephens presents two algorithms — four- and five-color — for col- 2 Delphi Tools
oring maps, or any surface with discrete areas that must be visually 4 Newsline
unique. Demonstration programs are available for download, of course. 36 File | New by Alan C. Moore, Ph.D.
HyperAct, Inc.
Price: US$250
Phone: (402) 891-8827
Web Site: http://www.hyperact.com
Davis Business Systems and Psi Computer Consultants Offer BS/1 Small Business
Davis Business Systems Ltd. programs designed for use by to include a fully functioning
and Psi Computer small- to medium-sized busi- accounting system in their
Consultants Pty Ltd. have nesses. BS/1 is an ActiveX con- own custom application.
developed BS/1 Small trol that provides software BS/1 Small Business ActiveX
Business, a suite of accounting developers a cost-effective way can be applied in a wider
range of languages than a
native VCL. A developer can
use it for one client’s VB appli-
cation and for another client’s
Delphi application, without
purchasing two versions of the
same software.
By Xavier Pacheco
I n the March, 1999 issue of Delphi Informant, we discussed the Singleton pattern. This
month, we’ll create a simple application framework using a commonly used pattern,
the Template Method. This article will present two concepts: designing a framework
around which you develop a user interface, and using patterns to accomplish this.
In the following section, I’ll explain the concept of So how do patterns and frameworks relate to one
frameworks, what they are, and how they help us another? A developer uses patterns to build frame-
in designing applications. I’ll also explain what works for software. Patterns and frameworks aren’t
frameworks have to do with patterns. In this and the same; the GoF have defined three major dif-
future articles, I’ll make reference to the book ferences. Design patterns are:
Design Patterns: Elements of Reusable Object- more abstract than frameworks;
Oriented Software [Addison-Wesley, 1994] by smaller architectural elements than frame-
Erich Gamma, Richard Helm, Ralph Johnson, works; and
and John Vlissides, otherwise known as the Gang less specialized than frameworks.
of Four (GoF).
With a tool like Delphi, it’s easy to fall into the The second difference emphasizes how patterns are
trap of designing the UI without careful fore- smaller in size. A framework may be composed of
thought as to how it’s supposed to work. A typi- many patterns, whereas a pattern would never be
cal UI may start out as a main form with a composed of frameworks. A pattern is more like an
menu. Later, a page control is added to separate algorithm used in a framework, and the framework
logical functionality. As the UI develops, more is what defines the architecture of a system.
functionality is added, removed, and moved into
separate forms. Typically, the layout and flow of The third difference emphasizes how frame-
the UI isn’t decided upon until much later in works are written to a specific application
the development process. By first designing a domain. For example, you might define a
framework, one can reduce the amount of re- framework for a graphics manipulation applica-
coding and redesign that occurs during UI tion. There isn’t a pattern that specifically
development. A UI framework acts as a plug-in addresses this domain, although you might use
mechanism to facilitate a more structured a pattern or combination thereof to construct
approach to UI development. this specific framework. The key is that the
A Template Method accomplishes the same at the method/algorith- The example in Figure 3 depicts the modularity of a framework.
mic level. A Template Method is defined and implemented in an The Shell Application owns a View Manager. The View Manager
abstract class to provide structure for an action performed by that
class. All descendant classes (concrete classes) override and imple- AbstractClass
ment smaller pieces — abstract methods — of the class to help give Primitive Method1
it identity. Therefore, a Template Method is actually part of an TemplateMethod PrimitiveMethod2
abstract class even though it’s usually fully defined and implement- PrimitiveMethod1
ed. Look at it as a way to extend the control in which you attempt PrimitiveMethod2
to define an abstract class. Ideally, the Template Method isn’t
declared as virtual or dynamic. The purpose of this method is to
establish a consistent process among the descendant classes. For
example, a Template Method might look something like this:
ConcreteClassA ConcreteClassB
procedure TSomeObject.TemplateMethod;
var
PrimitiveMethod1 PrimitiveMethod1
i: Integer; PrimitiveMethod2 PrimitiveMethod2
begin
PrimitiveMethod1; Figure 1: The Template Method pattern structure.
for I := 0 to 10 do
PrimitiveMethod2;
Type Description
PrimitiveMethod1;
end; Template A combination of the remaining four methods forming
an algorithm. Subclasses typically don’t change this
method and simply override the methods they call.
In this example, the TemplateMethod method defines a simple tem-
Concrete A method hard-coded by the abstract class that
plate by which two primitive methods are invoked. The implemen-
doesn’t get overridden by the subclasses. In Delphi,
tations of these primitive methods may vary so that they perform
this is a static method.
entirely different actions. For this to work, we must use object
Abstract A method declared by, and implemented by, each
inheritance to allow for the varying behaviors for the primitive
subclass. In Delphi, the keyword abstract is used to
methods. Therefore, it can be assumed that the Template Method
define this type of method.
pattern is dependent on abstract classes, i.e. they go hand in hand.
Hook A method that provides default functionality, and
that may be overridden by subclasses. In Delphi,
Method Types
this is the virtual or dynamic method.
We’ve nailed down the issue of Template Methods being routines
Event A pointer to a method. This method may be provided
with behaviors that change internally without the client knowing
by the client of the abstract class. It will be called if it
of this change. Because this method is contained in a class, it fol-
exists by the abstract class. When a method is provid-
lows we must provide an inheritance model to allow us to create
ed to the method pointer (e.g. OnClick event proper-
different subclasses to which we provide the differing method
ty) it’s referred to as an “event handler.” Component
behaviors. I’ll focus less on the Template Method itself, and more
writers will be familiar with this method type.
on the abstract class used to implement a Template Method. In
fact, because this is a technique so common in OOP development, Figure 2: Method types in an abstract class.
is the intermediary between the Shell Application and each TWinControl = class(TControl)
TViewForm. TViewForm is an abstract class that defines the inter- procedure CreateParams(
var Params: TCreateParams); virtual;
face with which the View Manager and Shell Application interact.
procedure CreateWindowHandle(
Developers create implementations of TViewForm that adhere to const Params: TCreateParams); virtual;
the interface defined by TViewForm. Because each TViewForm end;
descendant is an independent form, it can be developed apart from
the Shell Application. When the functionality for a TViewForm is
complete, it’s incorporated with the main application. Both methods are used in the TWinControl.CreateWnd method, which
looks similar to Figure 4. I removed much of the code to conserve
Defining the Abstract TViewForm space. You’ll see that both the CreateParams and CreateWindowHandle
Listing One (on page 9) shows the source for the abstract TViewForm methods are called in the context of the CreateWnd method. It’s possi-
(this and all other source is available for download; see end of article ble for descendant classes to override both these methods to provide
for details). This class declares the methods and properties that enable different behaviors described by the Template Method pattern. In fact,
it to be embedded as a child window. Specifically, it declares two con- I override the CreateParams method to ensure that TViewForm can be
structors. The constructor that takes a TWinControl parameter, embedded as a child window. Other than its name, this Template
AParent, assigns the value passed in as AParent to a temporary variable. Method pattern is nothing new to most Delphi developers. The same
This will be used in the overridden Loaded method that sets up the is true for an event method as shown here:
appropriate property values required by this form to be embedded.
procedure TCustomForm.DoClose(var Action: TCloseAction);
TViewForm also declares three hook methods that may be overridden begin
if Assigned(FOnClose) then FOnClose(Self, Action);
by its descendants. These are “reader” methods for the properties
end;
ViewDescription, ViewMenu, and ViewToolbar. Whenever you declare a
virtual class containing reader methods for properties, it’s a good idea to
not keep these methods as abstract, because the descendants of the class This procedure shows the TCustomForm.DoClose method.
may not need to implement them. For example, not all TViewForm TCustomForm is another TViewForm ancestor. This method checks
descendants will return a TToolBar, TPopupMenu, or string description. to see that its field, FOnClose, is referring to a method. FOnClose is
Instead of forcing the descendant class to implement these methods, a method pointer that, if valid, gets called. Again, this technique
we’ll just implement them ourselves to provide default nil/empty values, isn’t new to component developers.
which is what the descendant class is going to have to do anyway.
Creating a TViewForm Subclass
TViewForm declares one abstract method, CanChange. Because Listing Two (on page 10) shows TViewForm2, one of three
TViewForm descendants are to be embedded into a TTabControl, you TViewForm descendants accompanying this article’s example pro-
may want to prevent the user from switching tabbed pages until a gram. I’m illustrating this form because it’s more complete. This
certain operation is complete. The Shell Application doesn’t know form is a simple database form containing a database connection, a
what functionality the TViewForm provides and, therefore, can’t pre- TDBGrid to display the data, a TToolbar for navigation, and a pop-
vent the tab switch from occurring. However, the Shell Application up menu. This form can function independently from the main
can call the CanSwitch method to determine if the tab switch is valid. application. It can also be integrated with the main application,
because it adheres to the interface defined by TViewForm.
TViewForm provides only hook and abstract methods, although it
carries along concrete, template, and event methods from its own Notice how I’ve overridden the methods declared by TViewForm, and
ancestors. For example, one of TViewForm’s ancestors is provided the proper results for the Shell Application. For example,
TWinControl, which defines two virtual methods that may be over- GetViewDescription returns a valid string, and GetViewMenu returns a
ridden by its descendants, CreateWindowHandle and CreateParams. valid TPopupMenu for integration with the Shell Application. Also,
The declarations of these methods are shown here: examine the CanChange method. This method passes False if Table1 is
Figure 5: The Shell Application main form. Figure 7: TViewForm2 at run time.
in a mode other than dsBrowse, thus forcing the user to save his or her When the user attempts to change a tab on the TTabControl, the
work. There are similar forms in the example you can study. OnChanging event handler for the TTabControl is invoked, and
the AllowChange parameter is set to the value of the currently
Creating the Shell Application loaded TViewForm.CanChange method. You can see how you
The Shell Application for this framework is shown in Listing Three might prevent a user from changing tabs and the current
(beginning on page 10). Figure 5 shows the main form for the Shell TViewForm. If the change is valid, tctrlMainChange,
Application. Notice that the primary pieces to the main form are the TTabControl.OnChange, is invoked. This method performs several
TTabControl, TCoolBar, and TMainMenu. The TTabControl contains a steps. First, it un-merges the current TViewForm’s menu. Then, it
TPanel, pnlContainer, that will serve as the container to the TViewForm retrieves the next TViewForm and merges its menu and toolbar
descendants. When the user selects a new tabbed page, the TViewForm with the main form. The reason I don’t have an UnMergeToolBar
corresponding to that tab will be loaded, and the previous form will be method is because the TViewForm’s toolbar is still owned by
unloaded. TCoolBar contains two bands. The first band is an applica- TViewForm. In the process of freeing the TViewForm, its
tion level band. The TToolButton objects on this band each correspond TToolBar is also freed. Figures 6, 7, and 8 show the main form
to one of the three TViewForm descendants and invokes them as nor- with the three TViewForm descendants contained in pnlContainer.
mal modal forms. The second band, which appears empty, contains a
TPanel, pnlViewTB, that will contain the TToolBar for the TViewForm The main form retrieves the TViewForm descendants using one
that’s returned by the TViewForm.ViewToolBar property. Finally, the of two methods. One is illustrated in the tctrlMainChange
pop-up menu returned by the TViewForm.ViewMenu property is method in the call to the TViewManager.GetViewForm methods
merged into the main form’s main menu. I don’t use the standard merg- shown below:
ing capabilities of TMainMenu, because I’m going to have multiple lev-
els of merging when I extend this framework in my next article. FViewManager.GetViewForm(tctrlMain.TabIndex, pnlContainer);
As you examine Listing Three, you’ll notice another class, TViewManager.GetViewForm is an overloaded method that creates a
TViewManager. This class represents another pattern, which I’ll discuss TViewForm instance and assigns the second parameter, in this case
in my next article. For now, just know that this is the class with which pnlContainer, as the parent. TViewManager does this by calling the
the main form interacts to reference the TViewForm descendants. appropriate constructor of TViewForm. The TViewManager
TViewManager is responsible for creating, freeing, and managing the method is called in TMainForm.ShowViewFormModal:
TViewForm descendants. I’ll discuss TViewManager’s functionality in
the context of the main form interacting with TViewForms. ViewForm := FViewManager.GetViewForm(ViewIndex);
{$R *.DFM}
This method returns a reference to a TViewForm. ShowViewFormModal
demonstrates how you can show the same form previously embedded in { TviewForm. }
a TPanel as a separate modal form (see Figure 9). Finally, notice that the constructor TViewForm.Create(AOwner: TComponent);
main form’s OnCreate event handler invokes the tctrlMainChange begin
method to load the first TViewForm. FAsChild := False;
inherited Create(AOwner);
end;
Conclusion
The Template Method pattern is really an “OOP Patterns” term for constructor TViewForm.Create(AOwner: TComponent;
something most Delphi programmers have been doing since Delphi AParent: TWinControl);
begin
1. In this article, I discussed the Template Method pattern and started
FAsChild := True;
on the initial design of an application framework. I’ll use this frame- FTempParent := aParent;
work in my next article to discuss another useful and commonly used inherited Create(AOwner);
pattern, the Builder pattern. I’ll also illustrate how to extend this end;
framework to embed fully functional, run-time modules by using
procedure TViewForm.Loaded;
add-in packages. In closing, I’d like to thank David Streever and Anne begin
Pacheco for their technical and grammatical review of this article. ∆ inherited;
if FAsChild then begin
The files referenced in this article are available on the Delphi Informant align := alClient;
BorderStyle := bsNone;
Works CD located in INFORM\99\MAY \DI9905XP.
BorderIcons := [];
Parent := FTempParent;
Position := poDefault;
end;
Xavier Pacheco is the president and chief consultant of Xapware Technologies Inc., end;
where he provides consulting services and training. He is also the co-author of
procedure TViewForm.CreateParams(
Delphi 4 Developer’s Guide [SAMS Publishing, 1998]. You can write Xavier at
var Params: TCreateParams);
[email protected], or visit http://www.xapware.com. begin
Inherited CreateParams(Params);
if FAsChild then
Params.Style := Params.Style or WS_CHILD;
end;
end.
L ast month, we looked at several useful dialog boxes whose functions are not provided
in Comdlg.dll, nor are they clearly documented. Instead of trying to duplicate interfaces
by building a dialog box manually, we showed how to access several commonplace system
dialog boxes you may need, but are difficult to find. To close out this two-part series, we’ll
show you several more functions and how to use them.
A sample program accompanies this series and The SearchRoot parameter allows you to begin a
demonstrates each dialog box function (see Figure 1). search in a particular folder. This is the same effect
The first article covered five dialog boxes: Browse for you get if you select Find on the context menu of a
Folder, About Windows, Format, Change Icon, and folder you’ve right-clicked. You may pass this para-
Run. This month we tackle the rest. meter as nil to allow the user to begin searching at
the Desktop. The SavedSearchFile parameter
Finding Files allows you to specify a file saved from a previous
Many people want the Find dialog box. It’s the search (a .FND file) that will initialize the dialog
handy utility provided by the shell to find files box to match the saved state. This is the effect you
based on a variety of criteria (see Figure 2). The would get from opening a .FND file in the
ability to spawn this dialog box from your own Explorer. You may also pass this parameter as nil.
application is provided by the SHFindFiles func- Passing both parameters as nil produces the dialog
tion. It’s exported from Shell32.dll, and the box you would get from selecting Find | Files or
ordinal value is 90: Folders from the Start menu.
function SHFindFiles(SearchRoot: PItemIDList; If you specify a non-nil SearchRoot PIDL, it’s your
SavedSearchFile: PItemIDList): LongBool;
responsibility to free that PIDL after calling
stdcall;
SHFindFiles. However, if you pass a non-nil
SavedSearchFile PIDL, you mustn’t try to free that
PIDL if the function succeeds, as an error will
occur if you do. We can only hope the shell will
free it when it’s done doing whatever it does with
it. If the function fails, however, you must free the
PIDL yourself. [For a description of PIDLs and
their use, see “Shell Notifications” by Kevin J.
Bluck and James Holderness in the March, 1999
Delphi Informant.]
port OLE drag-and-drop, so the user can drag found files from the
dialog box into your application.
Finding Computers
Another function closely related to SHFindFiles is SHFindComputer. This
function shows the same dialog box you would get if you clicked the Start
menu and selected Find | Compute. Its interface is identical to
SHFindFiles, except it completely ignores the parameters you send it.
Apparently, they have been reserved for future expansion. Just pass nil to
both parameters. The return values are identical to SHFindFiles, and like
that function, the dialog box is non-modal. So the function returns Figure 3: The Properties dialog box — in this case for a drive.
immediately while the dialog box remains open, and there is no direct
means of telling what the user did with the dialog box. SHFindComputer ignored, and the path from the FileName parameter is used instead.
is exported from Shell32.dll by the ordinal value of 91: The DefaultExtension parameter points to a buffer containing the
default extension the dialog box will search for. The Filter parameter
function SHFindComputer(Reserved1: PItemIDList; points to a buffer containing pairs of null-terminated filter strings
Reserved2: PItemIDList): LongBool; stdcall; that will be shown in the Files of Type drop-down list. The Caption
parameter points to a string to be shown in the title bar of the dialog
Browsing for Files box. For further details on all these parameters, see the documenta-
There really isn’t any compelling reason why you need to use this tion on the Windows OPENFILENAME data structure.
next function. GetFileNameFromBrowse is nothing more than a sim-
plified wrapper around the GetOpenFileName function, which is the If the user selects a file to open, the return value is True. It’s False if
function you call when you want to display the standard Open dia- an error occurs, the user chooses the Cancel button, or the user
log box. Obviously, you already have access to everything this func- chooses the Close command from the System menu.
tion can do by using the standard VCL TOpenDialog component or
the GetOpenFileName API function. However, for some applica- Displaying Object Properties
tions, it might be nice to be able to browse for a file with a single Another handy undocumented dialog box function is
function call without the tedious process of filling in all the mem- SHObjectProperties. This function can be used to display the
bers of the OPENFILENAME structure, or instantiating an Properties dialog box for a drive, folder, or file (see Figure 3). It
instance of OpenDialog. The function is exported from Shell32.dll can also be used to display the properties for a printer object.
by ordinal value 63: The function is exported from Shell32.dll by ordinal value 178:
The Owner parameter identifies the window that owns the dialog box.
Most of the parameters to this function correspond directly with The Flags parameter specifies the type of object whose name is passed
members of the OPENFILENAME structure. The Owner parameter in the ObjectName parameter. These are the possible flag values:
identifies the window that owns the dialog box. The FileName para-
meter points to a buffer that contains a file name used to initialize the OPF_PRINTERNAME = $01;
dialog box’s Edit control. When the function returns, this buffer con- OPF_PATHNAME = $02;
tains the full path of the selected file. It’s advisable to provide a buffer
capable of storing MAX_PATH characters plus a null terminator. The ObjectName parameter points to a string containing the path
The MaxFileNameChars parameter specifies the size, in characters, of name, or the printer name whose properties will be displayed. If a
the buffer pointed to by the FileName parameter. The InitialDirectory printer is local, you may use only the actual printer name. If a print-
parameter points to a string that specifies the initial file directory er is from the network, you need to use the entire UNC-style name,
when the dialog box appears. If the FileName parameter contains a in the form \\COMPUTERNAME\PRINTERNAME. The
fully qualified file name with path, the InitialDirectory parameter is InitialTabName parameter points to a string containing the name of
the tabbed page that will initially be shown in the dialog box. If the procedure ExitWindowsDialog(Owner: HWND); stdcall;
InitialTabName parameter is nil, or the string doesn’t match the
name of any tab, the first tab on the property sheet will be selected. and:
If the function succeeds, the return value is True. If the function function RestartDialog(Owner: HWND; Reason: Pointer;
fails, the return value is False. To get extended error information, call ExitType: UINT): DWORD; stdcall;
the API function GetLastError. Note that this dialog box is non-
modal, similar to the SHFindFiles dialog box, so when the function ExitWindowsDialog is probably the less useful of the two. It’s the
returns, the dialog box will almost certainly still be open. There is dialog box displayed when you select Shut Down from the Start
no way of knowing when the user has closed the dialog box. menu. The dialog box doesn’t always seem to actually use Owner as
a parent. On Windows 95, the owner window will receive a
Networking WM_QUIT message if the operation is successful. On Windows
The next two functions allow your user to connect to network NT, the owner window doesn’t appear to be used at all. There is
resources. SHNetConnectionDialog (see Figure 4) is available on no return value for the function, so you have no way of knowing
Windows 95 and NT, and is exported from Shell32.dll by ordinal 160: what the user selected, or whether the operation was canceled.
Presumably, your application will be receiving shutdown messages
function SHNetConnectionDialog(Owner: HWND; from Windows fairly soon if the user decided to quit, but there is
ResourceName: Pointer; ResourceType: DWORD): no way to know for sure at the time of the function call.
DWORD; stdcall;
EWX_LOGOFF = $00;
The parameter lists are basically identical. The Owner parameter EWX_SHUTDOWN = $01;
EWX_REBOOT = $02;
takes the handle of the window that will own the dialog box. The
EW_RESTARTWINDOWS = $42;
ResourceName parameter points to a null-terminated string specify- EW_REBOOTSYSTEM = $43;
ing the fully qualified UNC path of the network resource to connect EW_EXITANDEXECAPP = $44;
to. Specifying this parameter results in a dialog box that is “pre-set”
to the named resource and doesn’t allow the user to change the
resource. If you pass nil to this parameter, the dialog box allows the The return value is IDYES if the user chose to perform the shut-
user to specify the resource. down. It is IDNO if the operation was canceled.
The ResourceType parameter can be set to one of two values: There are a couple of other points about RestartDialog you should
RESOURCETYPE_DISK or RESOURCETYPE_PRINT. These val- note. The reason displayed in the dialog box always has some default
ues will produce different dialog boxes. The first allows you to assign a text appended to it asking the user to confirm the operation. It’s
drive letter to a network disk resource, while the second allows you to therefore advisable you always end your reason text string with a
map a parallel port name, such as LPT2, to a network printer. space, or a CR/LF. The title of the dialog box is always set to
However, for some reason, RESOURCETYPE_PRINT doesn’t work “System Settings Change.” Finally, the return value cannot be used
on NT. If you pass this value on NT, the function fails. There are also to determine the success of the operation. It only signifies the choice
some other constants in the RESOURCETYPE_XXX family, but made by the user. If the restart operation failed for some reason, the
none of the others work for this function on any platform. return value will still be IDYES.
If the function succeeds, the return value is NO_ERROR. If the Note that for either of these functions to work correctly, users must
user cancels the dialog box, it returns –1 ($FFFFFFFF). If the func- have the SE_SHUTDOWN_NAME privilege enabled in their profile.
tion fails, the return value is some other error code. To get more This is usually not an issue in Windows 95, but some installations of
detailed error information, call the GetLastError API function. NT are set up to prevent certain users from shutting down the system.
By Rod Stephens
Map Coloring
A Look at Four- and Five-color Algorithms
A map can be an incredibly useful tool, particularly if you don’t like to stop and ask
for directions. A complicated map can be quite confusing, however. For hundreds of
years, cartographers have been making maps easier to read by displaying different
countries or regions in different colors. If no two adjacent regions have the same color,
it’s much easier to see where one ends and the next begins.
In 1853, F. Guthrie speculated that it was possi- large, this can be a huge number of combina-
ble to color any map this way using, at most, tions. For example, if R is only 10, R 4 is 20,000.
four colors. It wasn’t until 1976, 123 years later, Because the program searches all these combina-
that this fact was proven by Appel and Haken. tions in a rather simple-minded manner, this is
Unfortunately, the proof is computer assisted. It called an exhaustive search.
uses a program to exhaustively examine a huge
number of graphs, and doesn’t provide an effi- The first step in any map-coloring algorithm is
cient algorithm for four-coloring a map. to convert the map into an adjacency graph. Each
node in the graph corresponds to a region on the
This article describes two algorithms for color- map (the two terms are used interchangeably in
ing maps (both are available for download; see the rest of this article). Two nodes in the graph
end of article for details). The first is a some- are connected with a link if their regions share a
what inefficient algorithm that four-colors a border in the map. Figure 1 shows a small map
map. While the algorithm is theoretically ineffi- and its corresponding adjacency graph.
cient, in practice, it is quite fast for maps with a
reasonably large number of regions. The example program Color4 uses an exhaus-
tive search to four-color maps (see Figure 2).
The second alternative is an efficient algorithm that Much of the program’s code is dedicated to
colors a map using five colors. Whether you color a loading, editing, drawing, and otherwise manip-
map with four or five colors usually doesn’t matter. ulating maps. This code is long and irrelevant
The only reason you might need to use exactly four to the map-coloring algorithm, so it’s not
colors is if you have a somewhat unusual computer described here.
that can display only four colors, or if you are try-
ing to impress your friends with your algorithmic Color4 uses the TRegion class to store informa-
prowess. In these cases, use the inefficient four- tion about regions. The code for this class is
coloring algorithm, and be patient. shown in Figure 3, with region loading, editing,
and other irrelevant routines omitted. TRegion’s
Exhausting Work points array stores the points that define the
One way to four-color a map is to simply exam- region’s borders. The program uses this array to
ine all the possible combi- draw the region. It also compares the points in
nations of colors for two regions’ borders to see if the regions are adja-
the regions until cent. The neighbors TList contains a list of the
you find a com- adjacent regions. This list gives the links between
bination that the nodes in the adjacency graph. The color_number
works. If there variable holds the index of the color for the region.
are R regions on When the algorithm is finished, this will be a num-
the map, there ber between 1 and 4.
are R 4 possible
combinations of As far as the map-coloring algorithm is con-
Figure 1: A map and its adjacency graph. colors. If R is cerned, TRegion provides only two interesting
When the user invokes the Color Nodes command from the Color
menu, the program invokes the AssignColors procedure, shown in
Figure 4. AssignColors resets the regions’ colors and clears their
neighbor lists. It then calls FindNeighbors to find the regions’ cur-
rent neighbors. AssignColors then assigns an arbitrary color to node
0 in the graph. It doesn’t matter which color the program uses for
this first node. The color helps determine the colors of the other
nodes, but a solution is possible no matter what color is used first.
Figure 2: The example program Color4 uses an exhaustive The procedure then calls function AssignOneColor, passing it the
search to four-color maps. parameter 1. That tells AssignOneColor to begin assigning colors
type
// Array of points. x1 := points^[1].X;
TPointArray = array [1..1000000] of TPoint; y1 := points^[1].Y;
// Pointer to array of points. for i := 2 to num_points do begin
PPointArray = ^TPointArray; x2 := points^[i].X;
y2 := points^[i].Y;
// Map region class. if ((x1=a1) and (y1=b1) and (x2=a2) and (y2=b2)) then
TRegion = class Exit;
public if ((x2=a1) and (y2=b1) and (x1=a2) and (y1=b2)) then
// # points on the region's border. Exit;
num_points : Integer;
// The points on the region's border. x1 := x2;
points : PPointArray; y1 := y2;
// List of neighboring TRegions. end;
neighbors : TList;
// Region number starting with 0. // We didn't find the line.
number : Integer; Result := False;
// Color number for the region. end;
color_number : Integer;
// See if this region is adjacent to the indicated one.
constructor Create; virtual; // If so, add the regions to each other's adjacency lists.
... procedure TRegion.CheckNeighbor(rgn : TRegion);
// Region loading, editing, etc. declarations omitted. var
i, x1, y1, x2, y2 : Integer;
function HasLine(a1, b1, a2, b2: Integer): Boolean; begin
procedure CheckNeighbor(rgn: TRegion); x1 := points^[1].X;
procedure AddNeighbor(nbr: TRegion); y1 := points^[1].Y;
end; for i := 2 to num_points do begin
x2 := points^[i].X;
// Set some defaults. y2 := points^[i].Y;
constructor TRegion.Create; if (rgn.HasLine(x1, y1, x2, y2)) then
begin inherited Create; begin
num_points := 0; // They are neighbors. Add them to each other's
neighbors := TList.Create; // adjacency lists.
number := -1; neighbors.Add(rgn);
color_number := 0; rgn.neighbors.Add(Self);
end; Exit;
end;
// Return True if the region contains this line segment. x1 := x2;
function TRegion.HasLine(a1, b1, a2, b2: Integer): Boolean; y1 := y2;
var end;
i, x1, y1, x2, y2 : Integer; end;
begin
// Assume we will find it. // Region loading, editing, etc. routines omitted.
Result := True; ...
The FindNeighbors procedure, shown in Figure 5, examines every If the color isn’t already used by any of the region’s neighbors,
pair of regions. It invokes one of the pair’s CheckNeighbor proce- AssignOneColor assigns that color to this region. It then recursively calls
dures to see if the two regions are neighbors. If they are, itself to assign a color to the region with the next index. If that call to
CheckNeighbor adds them to each other’s neighbor lists. the function returns True, the program has found a valid four-coloring,
so this call to AssignOneColor sets its return value to True and exits.
The heart of the exhaustive search is the AssignOneColor function,
shown in Figure 6. This function takes as a parameter the index of If the recursive call returns False, the program cannot find a valid color-
the next region it should consider. If that index is greater than the ing with this region having the assigned color. The function continues to
largest index of any region, all the nodes have been assigned colors try the other colors. If the function cannot find a valid coloring using
successfully. That means the current assignment of colors is a valid any of the four colors for this region, it resets the region’s color, sets its
four-coloring. Function AssignOneColor sets its return value to True return value to False, and exits. As the calls to AssignOneColor return
to indicate that it found a valid coloring, then exits. back up the call stack, they will make new color assignments for the pre-
viously colored regions, and try again to color this region. The recursion
If it has not yet found a valid coloring, the function tries to give the will continue to try color combinations until it finds one that works.
region it is considering each of the four colors in turn. For each
color, the function determines whether one of the region’s neighbors The Color4 program uses this code to four-color maps. Use the File
menu to open a map file, or draw your own map. Click the left
// Four-color the map.
procedure TMapForm.AssignColors; mouse button to start a new region or add to the current region.
var Click the right button to finish the region. To make two regions
rgn_i : Integer; neighbors, use the same end points for at least one of their edges.
rgn : TRegion;
begin // Assign a color for the node with the indicated index.
// Reset all the colors and clear the neighbor lists. // Recursively assign colors for the other nodes. Return
for rgn_i := 0 to regions.Count - 1 do begin // True if we find a valid assignment.
rgn := regions.Items[rgn_i]; function TMapForm.AssignOneColor(rgn_i: Integer): Boolean;
rgn.neighbors.Clear; var
rgn.color_number := 0; color_num, nbr_i : Integer;
end; rgn, nbr : TRegion;
// Make the adjacency lists. begin
FindNeighbors; // If rgn_i >= regions.Count, then all regions have been
// Only continue if there are regions. // assigned and we have a valid solution.
if (regions.Count > 0) then if (rgn_i >= regions.Count) then
begin begin
// Assign the first region a color. Result := True;
rgn := regions.Items[0]; Exit;
rgn.color_number := 1; end;
// Assign the other colors using an // Try each possible color for this region.
// exhaustive search. rgn := regions.Items[rgn_i];
if (not AssignOneColor(1)) then for color_num := 1 to 4 do begin
ShowMessage( // See if this color is available for this region.
'Error: Could not find a valid coloring.'); for nbr_i := 0 to rgn.neighbors.Count - 1 do begin
// See if this neighbor has used the color already.
end;
nbr := rgn.neighbors.Items[nbr_i];
end;
if (nbr.color_number = color_num) then Break;
Figure 4: The AssignColors procedure four-colors the map. end;
// See if the color is usable.
if (nbr_i >= rgn.neighbors.Count) then
// Create the adjacency lists for the regions.
begin
procedure TMapForm.FindNeighbors;
// Assign this color to the region.
var
rgn.color_number := color_num;
i1, i2 : Integer;
// Recursively assign colors to the other regions.
rgn1, rgn2 : TRegion;
if (AssignOneColor(rgn_i + 1)) then
begin
begin
// Compare each region to every other and see if they // We found a valid assignment.
// are adjacent. Result := True;
for i1 := 0 to regions.Count - 2 do begin Exit;
rgn1 := regions.Items[i1]; end;
for i2 := i1 + 1 to regions.Count - 1 do begin end;
rgn2 := regions.Items[i2]; // If everything worked and we found a complete
rgn1.CheckNeighbor(rgn2); // assignment, we have already exited. Otherwise
end; // we continue trying other colors.
end; end;
// Blank our color so we can try again later.
// Display the neighbor lists if desired. rgn.color_number := 0;
if (mnuShowNeighborLists.Checked) then // We found no valid assignments.
ShowNeighborLists; Result := False;
end; end;
Figure 5: The FindNeighbors procedure finds the neighbors for Figure 6: The AssignOneColor procedure recursively assigns
each region. colors to regions until it finds a valid four-coloring.
A
X AB
B
shown that there is a node X with exactly five neighbors, where two
neighbors, A and B, are not neighbors of each other, and each has at
most seven neighbors of its own. For example, the graph on the left
Figure 7: The example program Color5 five-colors maps. in Figure 8 shows such nodes A, X, and B. Node X has five neigh-
bors. Its neighbors A and B are not neighbors of each other, and
they each have no more than seven neighbors.
When you have loaded or created a map, select the Color Nodes
command from the Color menu, or press 9. Because nodes A and B are not neighbors, the program can assign
them the same color. To continue processing the graph, the program
Planar Postulates removes node X and combines nodes A and B into a single node. It
The four-coloring algorithm used by Color4 exhaustively examines color then continues coloring the remaining nodes. Figure 8 shows this
combinations until it finds one that works. For reasonably small prob- combination process. Notice that nodes that were neighbors of both
lems, this algorithm is fast, and the program has no trouble. If a map nodes X and node A or B have fewer neighbors than they did before.
contains many regions, however, exhaustive search can be impractical. Now there are some nodes with fewer than five neighbors, so the pro-
gram can use the first observation to continue processing the graph.
If the map contains R regions, there are R 4 possible color combinations.
If R is 1000, R 4 is one trillion. If your computer can examine one mil- When it has finished processing the smaller graph, the program
lion combinations per second, it will take more than 11 days to search restores nodes X, A, and B. It gives nodes A and B the color it gave
them all. The program will probably find a valid coloring before it the combined node in the smaller graph. Because node X has exactly
searches all the combinations, but there is no guarantee of quick success. five neighbors, and nodes A and B have the same color, X’s neigh-
In cases like this, you can use the five-coloring algorithm demonstrated bors can have used at most four of the five colors available. Pick one
by the Color5 program, shown in Figure 7. This program doesn’t always of the remaining colors and assign it to node X.
find a four-coloring, but it’s faster for very large maps.
Five-coloring
In many ways, the example program Color5 is similar to the example The example program Color5 uses these two facts to five-color maps.
program Color4; both use the same TRegion class and the same code As it removes nodes from the graph, it places information about the
to load and manipulate maps. Their differences are in how they find nodes on a stack. When the graph is empty, the program removes the
map colorings. Color5 uses two key facts about planar graphs to pro- items from the stack in last-in-first-out (LIFO) order, using the infor-
duce the five-coloring. A graph is planar if it can be drawn in a flat mation to assign colors to the nodes. In English, the algorithm is:
plane without any links crossing each other. Adjacency graphs, such
as the one shown in Figure 1, are always planar. 1) While the graph is not empty, do:
a. If there is a node N with fewer than 5 neighbors, then:
Fact 1 i) Add node N and its current neighbor list to the stack.
The first useful fact about planar graphs is that nodes with fewer ii) Remove node N from the graph.
than five neighbors are easy to color. First, remove the node from b. Else:
the graph and color the remaining nodes. When they are colored, i) Find a node X with exactly five neighbors, two of which (A
restore the removed node and look at its neighbors. Because the and B) are not adjacent and have at most seven neighbors.
node has fewer than five neighbors, the neighbors cannot have used ii) Add node N and its current neighbor list to the stack.
up all five colors. Pick an unused color and assign it to the node. iii) Remove node N from the graph.
iv) Add nodes A and B to the stack.
This fact alone would be enough to color the entire graph if every node v) Combine nodes A and B by adding node B’s neighbors to
had fewer than five neighbors. In fact, removing a node reduces the node A’s neighbor list.
number of neighbors of each of its neighbors, so some of them may vi) Remove node B from the graph.
now have fewer than five neighbors. The program can continue like this 2) While the stack is not empty, do:
indefinitely unless it eventually reaches a state where every node has at a. If the next pair of objects on the stack is a node and its neigh-
least five neighbors. For example, every node in the graph shown in bor list saved in step 1.a.i or step 1.b.ii, then:
Figure 1 has exactly five neighbors. i) Examine the node’s neighbors and assign the node a color
that is not used by its neighbors.
Fact 2 b. Else (the next pair contains two nodes A and B saved in step
The second fact about planar graphs is useful when every node in 1.b.iv):
the graph has at least five neighbors. When that is the case, it can be i) Assign node B the same color already assigned to node A.
By Keith Wood
An HTML Generator
Part I: Putting the Delphi Interface Construct to Work
In the May, 1996 issue of Delphi Informant, I introduced the THTMLWriter component in
the article “An HTML Generator.” This component allowed us to generate HTML from a
Delphi program, with the full power and flexibility that a Delphi program provides. The
approach used a single component to encapsulate the required behavior, and defined
methods to generate the HTML.
Since the release of Delphi 3, we’ve had an alter- ition of a set of methods that an object can
nate way to implement the generation of HTML express. In this article, we’ll redesign the HTML
— a more object-oriented approach. Delphi 3 generator as a set of objects, the IHTML collec-
introduced us to the interface, an abstract defin- tion, that encapsulates one or more HTML tags
(this article assumes a basic knowledge of
{ Base class that implements the IHTMLProducer interface. }
HTML). We’ll also update these objects to take
THTMLBase = class(TObject, IHTMLProducer) into account changes for HTML 4.
private
FStyle: string; Interfaces
FId: string;
An interface defines a set of methods that deter-
FTagClass: string;
FLanguage: string; mines the interactions expected of an object.
FDirection: THTMLDirection; The methods are not implemented in the inter-
FTitle: string; face (in this way, it’s similar to an abstract class),
FAccessKey: Char; but must be coded for each object that expresses
FTabIndex: THTMLNumber;
FOtherAttributes: string;
that interface. Other differences from abstract
protected classes are that interfaces can only have method
function QueryInterface(const IID: TGUID; out Obj): and property declarations, and all properties
HResult; stdcall; must be accessed through functions or proce-
function _AddRef: Integer; stdcall;
dures. Also, all attributes must be public, and
function _Release: Integer; stdcall;
property Style: string read FStyle write FStyle; interfaces can have no constructor or destructor.
property Id: string read FId write FId;
property TagClass: string read FTagClass write FTagClass; Delphi has a single inheritance model, which
property Language: string read FLanguage write FLanguage; means that each class can be derived from only
property Direction: THTMLDirection
read FDirection write FDirection;
one other class, inheriting the latter’s properties
property Title: string read FTitle write FTitle; and methods. Interfaces allow us to simulate
property AccessKey: Char multiple inheritance through an object, express-
read FAccessKey write FAccessKey; ing one or more of these sets of definitions.
property TabIndex: THTMLNumber
Like the normal class hierarchy, interfaces form
read FTabIndex write FTabIndex;
property OtherAttributes: string their own hierarchies and are all ultimately
read FOtherAttributes write FOtherAttributes; derived from IUnknown. This interface defines
function BaseAttributesAsHTML: string; the basic functionality required to discover what
public interfaces are available in an object, and to ref-
function AsHTML: string; virtual; stdcall;
end;
erence count accesses to them.
Figure 1: The THTMLBase class declaration, which is the basis of Our interface, IHTMLProducer, consists of a
the HTML-generating hierarchy. single method, AsHTML, that returns the con-
var
htm: THTMLDocument; it might appear as:
begin
try 6 November, 1998 08:23 PM
htm := THTMLDocument.Create(
'IHTML Objects Demonstration');
As an extension to this scheme, we can include another file into the
htm.AddHeaderTag(THTMLMetadata.Create(
'Keywords', 'IHTML;Demonstration')); first one by following the pound sign with ^ (caret) and the name of
htm.AddHeaderTag(THTMLComment.Create( that file. The new file is also parsed in the same manner, allowing
'This page demonstrates the IHTML objects')); for further substitutions.
htm.Add(THTMLHeading.Create(
1, 'IHTML Objects Demonstration'));
htm.Add(THTMLParagraph.Create(
Data Tables
'Welcome to the demonstration of the IHTML objects.'));
To facilitate the generation of HTML tables from information in
memHTML.Text := htm.AsHTML; a database table, we have the THTMLDataTable object. This is
finally declared in the IHTMLDB unit, because it doesn’t encapsulate
htm.Free; one of the basic HTML tags.
end;
end;
It derives from the THTMLTable object, and uses its inherited
Figure 4: Generating a simple HTML document. abilities to generate the actual table HTML. The main new prop-
To make the code even shorter, we could subclass the original class and Conclusion
replace its methods with abbreviated versions as well. For example, we The interfaces introduced in Delphi 3 allow us to employ a more
could subclass THTMLParagraph, as shown in Figure 6. The imple- object-oriented approach to the generation of HTML from a Delphi
mentation of these constructors is just a call to the longer original ver- program. Instead of the monolithic component created in the previous
sions. Now we can have the following: effort, we now have a set of smaller, integrated objects that encapsulate
specific tags within an HTML document. Properties allow us to cus-
htm.Add(TP.New('An even shorter paragraph call')); tomize the tags without forcing us to fill in lots of unnecessary parame-
ters in a method call. Furthermore, if the objects presented here don’t
Feel free to apply this technique to whichever of the IHTML work the way you would prefer, the object-oriented approach allows you
objects you desire. to extend or replace them without affecting the rest of the hierarchy.
Demonstration Using interfaces means we can add the HTML generating abilities to
The demonstration program that accompanies this article allows any object we desire, and have it interact seamlessly with the objects
you to generate five documents. (The demonstration program is previously described. These new objects can appear anywhere within
available for download; see end of article for details.) In each case, the class hierarchy and have any sort of inherited abilities. Next
the HTML page is displayed as source on the screen, and is saved month, we’ll look at more applications for this approach, including
to a file that can be opened in your browser. The name of the file programs for generating Pascal source code to HTML, converting a
is given each time. directory structure to HTML, and producing a frameset definition
document in a more visual way. ∆
The first example illustrates many of the common tags used in
HTML documents. These include headings, formatted text, lists, The files referenced in this article are available on the Delphi Informant
images, and forms. The second demonstrates the use of tables and Works CD located in INFORM\99\MAY \DI9905KW.
draws a chessboard complete with pieces (assuming that the sup-
plied graphics are located in a subdirectory called images). Next,
there is an example of a frameset document. It is based on the
Keith Wood is an analyst/programmer with CCSC, based in Atlanta. He started
example in the HTML specification and displays three frames, two
using Borland’s products with Turbo Pascal on a CP/M machine. Often working
with images and the last with the source code.
with Delphi, he has enjoyed exploring it since it first appeared. You can reach him
via e-mail at [email protected].
HTML tables can also be generated from database tables, as the
fourth example demonstrates. The contents of the Biolife table
(minus the graphic) are displayed within the browser. Note that
the generating object automatically handles memo fields. Two
events are attached to the table creation to color every second row
light blue and the length columns red.
F or most of the past year, this column has taken a systematic look at Delphi
database development. This month’s installment continues this series with a
look at client-side data validation.
Data validation, as defined here, involves con- data is coming from client applications in a
firming that data entered by the user is acceptable client/server environment, being inserted from
before permitting it to be posted to a database, an application server, or being imported directly
e.g. the data entered into a field (column) is with- to the database, these rules are respected. The
in an acceptable range of values, and that data has drawback to this approach is that any change to
been supplied for all required fields. It also the back end requires all data-based triggers and
involves verifying that data within a given record constraints to be redefined for the new server,
is consistent, e.g. when a payment type field indi- e.g. when your company replaces Microsoft
cates that a credit card was used, the correspond- SQL Server with Oracle.
ing credit card number field has been entered.
The advantage of storing the rules on the appli-
Overview of Data Validation cation server is that all thin-client applications
There are a number of ways that data validation using the application server will use the rules.
can be implemented. If a database server is being The drawback is that the rules are not enforced
used (e.g. InterBase, Oracle, or Microsoft SQL until the client application sends the user’s data
Server), you can define constraints and triggers updates. This may occur after the user has made
on the server. Server-based validation ensures numerous inserts, updates, or deletions. If the
that all applications storing information on the user has repeatedly violated the same business
server must abide by the rules defined there. If rule, this may not be discovered until after the
the data is being accessed in an n-tier environ- user has made extensive invalid edits.
ment, such as that supported by Inprise’s
MIDAS (Multi-tier Distributed Application Client-side validation can be used in any situa-
Services) technology, these business rules can be tion, including those stand-alone applications
defined on the application server. Finally, it’s that don’t involve a database server or an appli-
possible to define the data validation rules on cation server. Furthermore, client-side validation
the individual client applications. can be applied without involving a network
round trip. The drawback to client-side valida-
Each of these approaches has its advantages and tion is that it must be repeated for each client
disadvantages. For example, placing business application. This introduces the potential for
rules on the database (whether it’s a remote inconsistent application of business rules.
database or a local one) has the advantage that Likewise, client-side validation involves binding
the rules are applied regardless of how the data the user interface to business logic, an associa-
is being added to the database. Whether the tion many developers want to avoid.
procedure TForm1.Table1CustNoValidate(Sender:
TField);
begin
if CustNo.AsInteger < 1000 then
raise EInvalidCustNoException.Create(
'Customer numbers must be greater than
1000');
end;
ECustomException = class(Exception); There are two ways to place constraints. You can use the
EInvalidCustNoException = class(ECustomException); CustomConstraint string property of individual TField compo-
nents, or the Constraints TCheckConstraints property of your
In general, whenever you raise an exception in code, it’s consid- TTable or TQuery components. (TStoredProc components don’t
ered good form to raise a custom exception, i.e. one defined by have a Constraints property. Furthermore, do not confuse this
you. Doing so permits any future exception handling code you Constraints property with the TControl.Constraints TSizeConstraints
add to distinguish between exceptions raised by you and those property, which controls the size of visible components.)
generated by Delphi’s components. In this case, a class named
ECustomException is defined, and all explicitly raised exceptions The example project VALID employs the CustomConstraint prop-
are declared to descend from it. erty to ensure the Contact field isn’t left blank. The value of the
CustomConstraint property for the Table1Contact TField is
Record-level Validation “Contact IS NOT NULL.” When a record is being posted, and
Record-level validation is used to prevent a record from being one of the TField CustomConstraint SQL expressions evaluates to
posted when it contains invalid data. Unlike keystroke-level and False, an exception is raised, and the string associated with the
field-level validation, the user is not prevented from entering TField ’s ConstraintErrorMessage is displayed to the user. In this
invalid data. Indeed, a user may move freely throughout a record, example, the ConstraintErrorMessage property contains the string
entering invalid data all over the place. Before the user can post “You must enter a contact name.”
the record, however, this invalid data must be corrected. While
some may argue this is not efficient, imagine how difficult it Using the CustomConstraint property requires you either instanti-
would be for you to complete a written form, such as your ate TFields at design time, or assign this property to TFields at run
income taxes, one field at a time, with each field needing to be time. By comparison, the Constraints property for TTable and
correct before you could continue to the next. TQuery components permits you to define one or more constraints
without working with individual TFields. The Constraints property
Delphi provides three ways to create record-level validation. Two contains one or more Constraint objects, each of which defines a
involve properties, and one makes use of an event handler. Each of CustomConstraint and an ErrorMessage. (The Constraints property
these is discussed in the following sections. also permits you to import constraints defined by a remote server.
This is a useful feature for propagating server-side constraints to
Before doing so, however, a comment is in order. The first two the client side, which can reduce network traffic. Using imported
techniques, constraints and the Required property, can be applied constraints is outside the scope of this article.)
at the field level. This might lead you to treat them as field-level
validation, but doing so is incorrect. Field-level validation is You add a constraint to the Constraints property by displaying the
applied on a field-by-field basis, which occurs during navigation, Constraints property editor, shown in Figure 2. After the
but does not necessarily involve the current record being posted. Constraints property editor is displayed, click the Add New
The files referenced in this article are available on the Delphi Informant
Works CD located in INFORM\99\MAY \DI9905CJ.
Cary Jensen is president of Jensen Data Systems, Inc., a Houston-based database development
company. He is co-author of 17 books, including Oracle JDeveloper [Oracle Press, 1998],
JBuilder Essentials [Osborne/McGraw-Hill, 1998], and Delphi in Depth [Osborne/McGraw-Hill,
1996]. He is a Contributing Editor of Delphi Informant, and is an internationally respected
trainer of Delphi and Java. For information about Jensen Data Systems consulting or training
services, visit http://idt.net/~jdsi, or e-mail Cary at [email protected].
L ast month we examined some general sites of interest to Delphi developers. This month, we’ll examine some
Delphi sites you may not know about: sites devoted to components, tools, techniques, and/or code; news sites;
and special interest sites.
Developer’s Corner Journal (http://www.dcjournal.com) is similar to Would you like to be an advocate for Delphi? Check out The
some of the Pascal sites we examined last month. However, this site Delphi Advocacy Group at http://www.tdag.org. They define them-
focuses on two Inprise tools: Delphi and C++Builder. Particularly selves as Delphi users and professionals who promote the most
rich in content, DCJ includes sections on beginners’ issues, the advanced Windows development tool.
Windows GUI, Internet programming, experts, database develop-
ment, and much more. You may recall my partner in the TAPI articles, Major Ken Kyler (see
the July, August, and September 1998 issues of Delphi Informant).
Brad Stowers’ Delphi Free Stuff (http://www.delphifreestuff.com) Ken is sponsoring new lists: a Delphi moderated list, a moderated
is just that. It includes over a dozen of Brad’s components, as well Delphi Database list, Delphi Talk, and more. You can find out how to
as those of other developers. It also includes links, experts, tips, subscribe at http://www.kyler.com. His page includes information on
and examples demonstrating advanced techniques, including work- Web page authoring, technical writing, and other topics.
ing with the Windows API. There’s an open invitation and willing-
ness to provide a home for other “freeware components that need a The Tomes of Delphi Support Site at http://www.cyberramp.
distribution point.” net/~jayres/ is a lot more than its title suggests. It does, of course,
provide a good deal of updates and information related to the
Conrad Herrmann’s DAX FAQs at http://pweb.netcom.com/ important series of books of which John Ayers (the owner of the
~cherrman/daxfaqs.htm is devoted to Delphi’s ActiveX capabilities. It site) is the principle author. But the most impressive aspect is an
includes information and code examples related to Delphi’s ActiveX exhaustive page of links to third-party tools.
Class Framework, covering Delphi 3 and 4. It covers bugs in IE4.01
and Delphi 3.02, as well as work-arounds. Advanced Delphi Programming at http://members.tripod.com/
~delphipower/index.htm is also worth visiting. It has information on
The Delphi Pages at http://www.delphipages.com is an attractive several 32-bit Delphi programming topics, such as working with shell
site with a wealth of tools. The Delphi News portion, central to extensions in Delphi; displaying the Properties page for a file, folder, or
the site, is excellent. It includes pages devoted to applications, drive; and using SHFileOperation to copy files in Delphi.
tips, components, chat, a Delphi forum, links, and more.
Last year, I discussed Project Jedi, a project to develop and make avail-
Delphi user groups can be an effective means of sharing informa- able conversions of Windows APIs otherwise unavailable to Delphi
tion and programming techniques. But what about those who don’t developers. Of course, the Project Jedi site at http://www.delphi-jedi.org
live close enough to such a group to participate? Enter the Virtual has information on this important endeavor. But the site also includes a
Delphi User Group, located at http://balticsolutions.com/vdug. good deal of additional information and links. There is an excellent
VDUG provides a number of useful services. In addition to its tutorial written by Andreas Prucha on converting C headers; it provides
monthly newsletters, it provides information on Delphi compo- a wealth of information on this difficult topic. This site also includes
nents and Internet sites. links to user groups and an interview with John Ayers.
The Search Site for Software Developers at http://developers.href.com I hope to revisit this important topic. In the meantime, I continue
is indispensable for Delphi developers. It provides a powerful means to invite your helpful input. ∆
to search many Usenet and vendor newsgroups. You can find informa-
tion on obscure topics, assessments of programming tools, and — Alan C. Moore, Ph.D.
answers to tough questions. It also provides access to files on the
Delphi Super Page and links to other top sites. Alan Moore is a Professor of Music at Kentucky State University, specializing
in music composition and music theory. He has been developing education-
Richey’s Delphi-Box at http://inner-smile.com/delphi4.htm con- related applications with the Borland languages for more than 10 years. He
tains a wealth of information and links. In addition to the has published a number of articles in various technical journals. Using
expected links to Web sites, FTP sites, and Inprise sites, there are Delphi, he specializes in writing custom components and implementing
sections devoted to less-usual topics: Delphi user groups and multimedia capabilities in applications, particularly sound and music. You
Delphi job offers. can reach Alan on the Internet at [email protected].