0% found this document useful (0 votes)
120 views35 pages

Delphi Informant Magazine (1995-2001)

This issue of the Delphi Informant magazine from February 2000 features articles on the Mediator pattern, building a CORBA server with Delphi, modifying VCL behavior without creating new classes, visual form inheritance, and generating XML documents from a database. It also announces new releases of InstallConstruct for creating installers, Raize Components with additional controls and design-time editors, and Oracle Data Access Components for direct Oracle database access from Delphi.

Uploaded by

reader-647470
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
120 views35 pages

Delphi Informant Magazine (1995-2001)

This issue of the Delphi Informant magazine from February 2000 features articles on the Mediator pattern, building a CORBA server with Delphi, modifying VCL behavior without creating new classes, visual form inheritance, and generating XML documents from a database. It also announces new releases of InstallConstruct for creating installers, Raize Components with additional controls and design-time editors, and Oracle Data Access Components for direct Oracle database access from Delphi.

Uploaded by

reader-647470
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 35

February 2000, Volume 6, Number 2

Cover Art By: Darryl Dennis

ON THE COVER 23 Delphi at Work


5 Patterns in Practice Time Travels — T. Wesley Erickson
Mediator Pattern — Xavier Pacheco Mr Erickson asks pesky questions, e.g. “What will happen when your
The Mediator pattern simplifies complex communication between app is deployed in a different time zone?” and “Have you considered
objects, while maintaining loose coupling, making it ideal for use in a Daylight Savings Time?” then — thankfully — answers them.
distributed process control system, as Mr Pacheco explains.
27 On the ’Net
FEATURES Generating XML — Keith Wood
9 Greater Delphi He’s already introduced us to XML scripting. Now Mr Wood demonstrates
CORBA: Part I — Dennis P. Butler how to generate XML documents from a database, then send the docu-
Mr Butler begins a two-part exploration of CORBA and CORBA develop- ments over the Internet.
ment with Delphi with an introduction to the technology, and a step-
by-step description of how to build a CORBA server.

14 Inside OP REVIEWS
Modifying VCL Behavior — Jeremy Merrill 32 TSyntaxMemo
Mr Merrill shows us how to dynamically change the behavior of a Product Review by Alan C. Moore, Ph.D.
native Delphi visual component without creating a new class. The secret
is to intercept Windows messages sent to the control.

18 Visual Programming DEPARTMENTS


Visual Form Inheritance: Part I — Rick Spence
Decreased development time, fewer errors, a more-consistent UI. The 2 Delphi Tools
benefits of VFI sound terrific, but Delphi’s implementation of the tech- 4 Newsline
nology is woefully underdocumented. Enter Mr Spence. 35 File | New by Alan C. Moore, Ph.D.
1 February 2000 Delphi Informant Magazine
Delphi FileStream.com Ships InstallConstruct 3.2.1
FileStream.com, Inc./Pacific Wizard with their own product of icons to be added to the com-
T O O L S Gold Coast Corp. announced the graphics and logo and display mon group under Windows NT
release of InstallConstruct 3.2.1, formatted text, for Internet and and Windows 2000 so all users
New Products designed for creation of Installer, Intranet distribution of program, of the computer can have access
and Solutions Setup Wizard, Uninstaller, and single and multi-volume CD- to it; creation of installers for
HTML-based Internet ROM, and disk distributions, as Font Delivery; and the option of
Component Download installers. well as create and customize keeping backup copies of existing
InstallConstruct is a compact Uninstaller. InstallConstruct uses files. In addition, this new release
suite of wizards and tools that the expanding wizard system, also expands the total path
makes creating Windows 3.1, 95, which walks users through the length limitation. International
98, NT, and Windows 2000 entire creation process to choose language scripts are supported.
Installers easier, using step-by-step from available options, without
procedures. These installer files are the need for a programming FileStream.com, Inc./
ideal for efficient and profession- background or having to write Pacific Gold Coast Corp.
al distribution of groups of pro- application-specific program- Price: US$199
gram and data files, which are ming codes. Phone: (800) 732-3002
compressed for economy and This latest release adds support Web Site: http://www.installconstruct.com
ready to be installed at the users’
convenience.
InstallConstruct automatically
records the selected package
options of a project as a package
script file (*.adx). These script files
not only save you from the repeti-
tive task of creating other similar
packages and in updating the
existing ones manually, they also
support command-line batch pro-
cessing in unattended operation
so they can be created automati-
cally, without any user prompts.
Users of InstallConstruct can
create and customize a Setup

Raize Releases Raize Components 2.5


Raize Software Solutions, Inc. nent has been changed. It no in a DataModule to control com- ty editors by providing new
announced Raize Components longer controls all the compo- ponents on multiple forms. editors that show a preview of
2.5, the latest release of the com- nents on the form that support Raize Components 2.5 also the available items.
pany’s library of native VCL con- Custom Framing. Instead, each introduces several features specifi- Raize Components 2.5 sup-
trols for Delphi and C++Builder. component that supports Custom cally for Delphi 5 developers. For ports Delphi 1, 3, 4, and 5, and
Raize Components 2.5 has more Framing now has a new example, several new property C++Builder 3 and 4. Raize
than 90 components with the FrameController property. As a categories are registered with Components 2.5 comes with
addition of nine new controls. result, a RzFrameController will Delphi 5, making it easier to complete source code for all
Version 2.5 adds more design- only control those components locate properties related to specif- components, packages, and
time editors and streamlines some that reference itself through the ic features in Raize Components. design-time editors at no addi-
existing editors to make the com- FrameController property. This lets For example, there are categories tional charge.
ponents easier to use. developers use multiple frame for Custom Framing, Text Style,
Most of the components have controllers to control different sets and Border Style. Raize Software Solutions, Inc.
been enhanced in the new ver- of components on the same form. Raize Components 2.5 takes Price: US$249
sion. For example, the behavior of Likewise, a single advantage of the owner-draw Phone: (630) 717-7217
the TRzFrameController compo- TRzFrameController can be used list support in Delphi 5 proper- Web Site: http://www.raize.com

CoRe Lab Announces ODAC 2.0


CoRe Lab Co. announced allows developers to refuse using types; support of native Oracle8 7.3, 8, and Oracle 8i, including
Oracle Data Access Components the BDE for applications work- call interface; embedded SQL Personal Oracle.
(ODAC) 2.0, a library of native ing with Oracle only. Designer to build queries; and
Delphi components for direct Features in ODAC 2.0 include more. CoRe Lab Co.
access to Oracle. flexible automatic updating; CoRe Lab distributes versions Price: US$99 for a single-developer
ODAC 2.0 is an easier, more advanced locking and refresh of ODAC for Delphi 3, 4, and license; US$249 for source code (in addition
flexible, more powerful, and rows; advanced support of 5 and C++Builder 3 and 4, to developer license).
faster way of developing database Oracle objects, arrays, nested Professional and Enterprise edi- Phone: (800) 903-4152 (US orders only).
applications with Oracle. ODAC tables, BLOB, and CLOB data tions. ODAC supports Oracle Web Site: http://www.crlab.com

2 February 2000 Delphi Informant Magazine


Delphi 20/20 Offers PC-Install 7
20/20 Software, Inc. intro- drop editing; an expanded list of wizard-style uninstall interface
T O O L S duced two new versions of its PC-Install system variables that and automatic support for the
PC-Install program for building makes locating and using system Add/Remove Programs control
New Products software installations in resources to control installations panel. Users installing software
and Solutions Windows environments: PC- easier, more efficient, and trans- over the Internet using PC-
Install 7 and PC-Install 7 with parent to the user; broader sup- Install 7 with Internet Extensions
Internet Extensions. port for Visual Basic (VB) pro- no longer need to restart their
Both versions of PC-Install jects; automatic file collection for installation from the beginning
include unlimited distribution VB 4 through VB 6; added sup- when their connection fails.
licenses and one year of free tech- port for automatic file collection Version 7 restarts automatically
nical support. Significant new in ODBC and Delphi projects; at the point in the installation
benefits for software developers developer-defined local variables where the connection was lost.
in PC-Install 7 include a refined, that allow collecting nearly
more flexible, and intuitive inter- unlimited amounts of data from 20/20 Software, Inc.
face that makes building installa- an end user; and a “Smart” unin- Price: US$249, or US$199 if downloaded
devSoft Releases ICK
Version 2.0
tions quicker; several additional stall feature that supports incre- from 20/20 Software or its distribution part-
devSoft Inc. released version editing tools, including global mental removal of installed com- ners; PC-Install 7 with Internet Extensions,
2.0 of Internet Commerce Kit search and replace, template, and ponents. US$449, or US$399 if downloaded.
(ICK), a developer’s toolkit for direct command editing, multi- For the end user, PC-Install 7’s Phone: (800) 735-2020
secure access and manipulation ple undo/redo, and drag-and- added features include a new Web Site: http://www.twenty.com
of Internet data. The toolkit
includes native Internet and
intranet development compo- RightWare, Inc. Announces ARMS 2.0
nents for development environ- RightWare, Inc. announced project risks with the ARMS the identification of risks by
ments such as Delphi,
the Active Risk Management Discussion Group feature; foster selecting from over 200 risks and
C++Builder, Visual Basic, Visual
C++, and others. System 2.0 (ARMS), a two-com- a project environment that does over 14 categories; define project
The new release introduces ponent suite of easy-to-use, not exclude risk identifiers; create risk attributes, such as time
vGrid (‘virtual’ Grid), used to easy-to-deploy, enterprise-class, a consistent understanding of the frame, project phase found, and
dynamically exchange relational team-based, risk-management project risks; import resources status; link risks and track the
(tabular) data over the Web. The
component may be used in both software. from Microsoft Project into impact of change on related risks;
server and client applications to ARMS allows users to: unify ARMS Team Manager as users; and more.
serve and access data. their project team by improving create and assign action items to
XML is used as the interchange risk communication and have team members; get a better pic- RightWare, Inc.
format. This approach makes it
team members stay in sync with ture of how mitigation affects Price: ARMS Team Manager, US$1,599
possible to link together a variety
of applications from a variety of changing project risks and get project schedules; print reports, (Enterprise Edition) and US$999 (Standard
platforms, built with different up-to-date information with the such as the Risk Management Edition); ARMS Team Member, US$1,299
development tools. The new ARMS Team Member compo- Plan, Mitigation Plan, and Top (Enterprise Edition) and US$799 (Standard
release brings a number of sig- nent; facilitate communication 10 Risk List; export reports to Edition).
nificant improvements in the
other components of ICK as well, by allowing the team members to DOC, RTF, PDF, and TXT for Phone: (877) 717-ARMS
including HTTP, HTTPS, FTP, engage in online dialog about further project visibility; optimize Web Site: http://www.right-ware.com
XMLp, and NetDial.
Improvements include better pro- Excel Software Announces WinTranslator 2.0.2
grammatic access to interactive
Excel Software announced neering. WinTranslator is used class models or structure charts
features and security, as well as
support for the latest versions of WinTranslator 2.0.2 for soft- in conjunction with Excel’s from source code. It can also
development environments, such ware design and code re-engi- WinA&D product to create create CRC cards for the
as Delphi 5. QuickCRC design tool from
ICK 2.0 costs US$245. For more Delphi, C++, or Java.
information call (919) 493-5805
or visit http://www.dev-soft.com. The WinTranslator 2.0.2
update makes several improve-
ments to version 2.0, including
additional customization options
to the re-engineering wizard that
steps the user through the
process of generating design from
code, and additional optimiza-
tions for Java re-engineering.

Excel Software
Price: US$495 for a single license; site
licenses are available.
Phone: (515) 752-5359
Web Site: http://www.excelsoftware.com

3 February 2000 Delphi Informant Magazine


News Inprise Announces New Agreement for VisiBroker
Scotts Valley, CA — Inprise
Corp. announced that its
standards to transform network
management. Inprise’s VisiBroker
plicated with numerous sub-
applications, this combination
L I N E VisiBroker CORBA Object facilitates the development and becomes more important for
Request Broker will be utilized deployment of distributed enter- companies thriving in today’s
in Cisco Systems’ prise applications that are scal- Internet economy.
February 2000 CiscoWorks2000 family of able, flexible, and easily main- For more information on Cisco,
enterprise network manage- tained. As systems get more com- visit http://www.cisco.com.
ment products.
As part of the agreement,
Inprise Announces Embedded Database Solution
Inprise’s VisiBroker will be with InterBase Version 5.6
embedded in Cisco’s Common Scotts Valley, CA — Inprise as performance enhancements.
Management Foundation, a Corp. announced the availability InterBase 5.6 is certified on
software infrastructure used by of InterBase version 5.6, the latest NetWare 4.2 and 5.0, Microsoft
CiscoWorks2000 network man- version of Inprise’s embedded Windows NT 4.0 SP4,
agement applications. database solution. This new ver- Windows 95, and Windows 98.
CiscoWorks2000 is a family of sion is now available on the InterBase 5.6 Server software
network management solutions Novell NetWare and Windows for Windows or NetWare is
combining Cisco’s switch and platforms and includes updates to priced at US$200 for a one-
router management with Internet SQL functions and roles, as well user server license. Additional
users are US$150 each,
Dale Fuller Outlines Strategy in Support of US$1,200 for 10 users, or
Application Service Providers US$2,100 for 20 users. Local
San Diego, CA — Inprise Finally, a “messaging layer” InterBase 5.6 software for
Corp. interim President and allows different applications from Windows is available on CD-
Chief Executive Officer Dale various ASPs to communicate ROM for US$50. Activation
Fuller unveiled the company’s with one another. keys for Local InterBase 5.6 for
new strategy in support of appli- According to International Data Windows are US$60 each,
cation service providers (ASPs). Corporation, worldwide spending US$800 for a package of 20, or
In addition, Fuller announced for ASPs will increase from US$2,000 for a package of
plans to create Inprise US$150 million in 1999 to over 100. There is no Local
AppServices, a new service to US$2 billion by 2003. InterBase for NetWare.
integrate software and services For more information, visit For more information, visit
from many application service http://www.inprise.com. http://www.inprise.com.
providers into a single suite.
AppServices will allow cus- ICG Announces the Launch of
tomers to access business applica-
tion sources through a Web-based ComputerBookstore.com
portal that includes a unified
Elk Grove, CA — Informant Communications Group,
suite of communication/
Inc. announced the launch of ComputerBookstore.com,
collaboration/productivity tools,
an online bookstore featuring a wide variety of com-
such as calendaring, messaging,
puting, gaming, certification, training, science, and
and discussion forums.
business books, in addition to training materials,
Inprise and its partners plan to
videos, and documentation.
build and host AppServices.
ComputerBookstore.com offers a wide variety of
AppServices will enable end users
titles from all the major publishers, including
to access their applications and
Addison-Wesley, IDG, Macmillan, Microsoft Press,
desktop via any networked
O’Reilly & Associates, Osborne/McGraw-Hill, SYBEX,
device, operating system, or pro-
Wiley, and WROX, as well as smaller independent
tocol, using a standard browser
publishers.
interface.
In addition, ComputerBookstore.com features profes-
Inprise’s strategy for ASPs con-
sional reviews, e-mail alerts, and other various interac-
sists of three layers.
tive activities.
The first, a “user layer,” provides
ComputerBookstore.com guarantees a savings of up
users with a single point of entry
to 41 percent off the suggested retail price for all on-
and universal registration system
sale items. January 2000 will feature various sales
from which to access applications
throughout the month, including sales on gaming,
from various ASPs being used
Osborne/McGraw-Hill, and Microsoft Press books.
within a company.
Additional information regarding this new online
The second, a “transport
bookstore is available at http://www.
layer,” allows a user to access
ComputerBookstore.com.
ASP-hosted applications on dif-
ferent types of devices.

4 February 2000 Delphi Informant Magazine


Patterns in Practice
Design Patterns / Mediator / Delphi 4, 5

By Xavier Pacheco

Mediator Pattern
Part I: Introduction to Process Control Frameworks

I n March of last year (1999), I began a series of articles on design patterns and how to
use such patterns within the Delphi VCL framework. In those articles, I discussed the
Singleton, Template Method, and Builder patterns.

This month, we’ll use the Mediator pattern to The Colleague is an abstract class or interface
illustrate how to solve the problem of process that defines the class to be managed by the
flow control in a batch processing system. I ini- Mediator class.
tially planned for this entire example to appear in ConcreteColleague classes are self-contained
one article — until I realized that there was too classes that communicate with a Mediator
much information. Therefore, the next three arti- class. Knowledge of the Mediator class is typ-
cles will focus on this pattern and how to use it in ical but not necessary.
a distributed process control system.
Figure 2 shows how the Mediator pattern works
Let’s begin by defining the pattern in detail. As when implemented. The ConcreteMediator serves
stated in the classic, Design Patterns [Addison- as “traffic cop,” controlling the execution of, and
Wesley, 1995], by Erich Gamma, et al., the communication between, the ConcreteColleague
Mediator pattern is used to “Define an object that objects. In this scenario, ConcreteColleague
encapsulates how a set of objects interact. objects don’t communicate directly with each
Mediator promotes loose coupling by keeping other; rather, they rely on the Mediator to enforce
objects from referring to each other explicitly, and inter-object communication. This requires a stan-
it lets you vary their interaction independently.” dard form of communication as defined by the
abstract class definitions or interfaces.
The Mediator pattern is used when objects must
communicate with one another in well-defined ways, Uses and Motivation
but the communication is complex. The Mediator The Mediator pattern removes the complexities
pattern can simplify the communication between and dependencies of inter-object communication.
objects. Additionally, the Mediator pattern can also This objective is especially applicable to process
unbind the dependencies between objects. This facil- control. Processes by definition are subject to
itates object reuse and customizations. There are four change because of constant improvements in the
participants to the Mediator pattern (see Figure 1): way we design and handle information. Planning
Mediator defines an abstract class or interface for and designing for changes in a process reduce the
communication between the Colleague classes. pain of making modifications.
A ConcreteMediator implements the abstract
mediator. Each ConcreteMediator coordinates Any repetitive process we see in business today can
the Colleague communication. benefit from a system design that incorporates the
Mediator pattern. Before we delve into the techni-
Mediator
mediator
Colleague calities of an example implementation, however,
let’s address the motivation of including the
Mediator pattern in the design.

Applications focused on managing processes


involve the systematic execution of tasks and
reporting of task status. If each task were responsi-
ConcreteMediator ConcreteColleague1 ConcreteColleague2
ble for knowing subsequent tasks and passing
behavioral statistics to those tasks, it’s easy to see
Figure 1: The Mediator pattern (from Design Patterns, Gamma, et al.). how the resulting application would be constrained

5 February 2000 Delphi Informant Magazine


Patterns in Practice
sion, “task” refers to an encapsulated unit of work, and “process” refers
ConcreteColleague to a series of tasks coordinated to achieve a defined purpose.
-Mediator : TMediator ConcreteColleague

-Mediator : TMediator
The example we’ll present is generic for now; we’ll expand on its
capabilities in later articles. The intent is simply to illustrate how the
Mediator controls the invocation of tasks, and how tasks can be
ConcreteMediator invoked non-sequentially.
ConcreteColleague

-Mediator : TMediator ConcreteColleague Defining the ITask Interface


-Mediator : TMediator
ITask defines an interface with a single function, ExecuteTask (see
Figure 5).
ConcreteColleague

-Mediator : TMediator This function takes an OleVariant as a parameter and returns an


Figure 2: Mediator pattern implementation. OleVariant. The reason is due largely to how the interface will be used
in a distributed environment. In my initial design of the process control
to the initial definition of the process, i.e. the sequence of tasks nec- system, I had hard-coded parameters to exactly those needed by the spe-
essary to complete the process. Changes in the sequence of tasks cific tasks. This led to problems when a task’s parameters required modi-
and/or the passing of behavioral parameters between tasks, in this fication, especially when that task had already been deployed on other
instance, would require updates to all objects involved — even if the machines. I needed a way to re-implement a task and to re-deploy it
individual task behavior had not changed (see Figure 3). without having to unregister/register the task on a given machine. Also,
I did not want to have to re-compile the calling module that also passed
In Figure 3, you see that if we were to modify the input and/or out- in hard-coded parameters. In a later article, we’ll see how to use a
put parameters of any given task, the changes to the system would TClientDataset to implement parameters for each task.
impact more than just the changed task. Additionally, this design
restricts the execution of tasks to the specified sequence. It becomes Defining the Mediator Interface
difficult for the results of one task to determine the next task to be IProcess defines two methods, ExecuteProcess and MessageToProcess
implemented. A task would have to know about all tasks that it (see Figure 6). Implementations of IProcess will serve as the
might invoke. This violates the intent to loosely couple each task. Mediator objects.
The Mediator pattern addresses these issues.
MessageToProcess is a method that will be used by each imple-
Adding a Mediator between the task objects removes the task-to-task mentation of ITask to allow a message to be passed back to
dependencies. The application is now free to alter the sequence of the Mediator class of a given ITask implementation.
tasks without modifying any of the individual tasks. Additionally, this ExecuteProcess is similar to ExecuteTask in that it’s invoked by the
design can allow generic parameters (process modifiers, performance client of the process.
statistics, error conditions, etc.) of one task to be passed to tasks that
aren’t necessarily next in sequence. Figure 4 illustrates how this might TTask5
look; notice the similarities with Figure 2.
+ExecuteTask

Task Control Example


This article’s example illustrates a simplified architecture by which you
can control a series of tasks/processes. To reduce any possible confu-
TTask1 TTask4
TProcess
+ExecuteTask +ExecuteTask
TProcess

TTask1
TTask2 TTask3
+ExecuteTask
+ExecuteTask +ExecuteTask

Figure 4: Design with Mediator is very flexible.


TTask2

+ExecuteTask unit IntfTask;

interface

TTask3
type
+ExecuteTask ITask = interface
['{712185C1-810F-11D3-8117-00008638E5EA}']
function ExecuteTask(AInParams: OleVariant):
TTask4 OleVariant;
end;
+ExecuteTask

implementation

TTask5 end.
Figure 3: Design without
+ExecuteTask
Mediator is inflexible. Figure 5: The ITask interface.

6 February 2000 Delphi Informant Magazine


Patterns in Practice
Implementing ITask You’ll also notice that we’ve made TTask a descendant of
Figure 7 illustrates the implementation of the ITask interface. TInterfacedObject so the IUnknown methods are implemented.
IUnknown is the root definition from which all interfaces descend.
As you can see, TTask is implemented as an abstract class. This has TInterfacedObject is a class that implements IUnknown’s reference
two primary purposes. First, we want to propagate the requirement counting methods, so your classes don’t have to.
for descendant classes to implement the ExecuteTask method.
Second, we want to provide a mechanism by which the TTask The IProcess Class
descendants would know about, or have a reference to, their Figure 8 illustrates the implementation of IProcess as an abstract class.
Mediator object. This is done through the TTask’s constructor.
unit IntfProcess;
TProcess descends from TInterfacedObject for the same reasons
mentioned for TTask. TProcess implements the IProcess interface,
interface and defines TProcess as an abstract class. Again, we want to force
implementations of ExecuteProcess and MessageToProcess.
type
IProcess = interface
['{ 712185C3-810F-11D3-8117-00008638E5EA }'] unit DemoProcess;
procedure MessageToProcess(AMessage: string);
function ExecuteProcess(AInParams: OleVariant): interface
OleVariant;
end; uses
Classes, ProcessClass;
implementation
type
end.
TDemoProcess = class(TProcess)
Figure 6: The IProcess interface. private
FMessageStrings: TStrings;
unit TaskClass; public
function ExecuteProcess(AInParams: OleVariant):
interface OleVariant; override;
procedure MessageToProcess(AMessage: string); override;
uses constructor Create(AMessageStrings: TStrings);
IntfProcess, IntfTask; end;

type
implementation
TTask = class(TInterfacedObject, ITask)
protected
uses
FMediator: IProcess;
IntfTask, Task1, Task2, Task3, Task4;
public
function ExecuteTask(AInParams: OleVariant):
constructor TDemoProcess.Create(AMessageStrings: TStrings);
OleVariant; virtual; abstract;
begin
constructor Create(AMediator: IProcess);
FMessageStrings := AMessageStrings;
end;
end;
implementation
function TDemoProcess.ExecuteProcess(
AInParams: OleVariant): OleVariant;
constructor TTask.Create(AMediator: IProcess);
begin var
inherited Create; Task: ITask;
FMediator := AMediator; i: Integer;
end; InParam: Integer;
begin
end. Randomize;
InParam := AInParams;
Figure 7: ITask implementation TTask. for i := 1 to 10 do begin
case InParam of
unit ProcessClass; 1: Task := TTask1.Create(Self);
2: Task := TTask2.Create(Self);
interface
3: Task := TTask3.Create(Self);
4: Task := TTask4.Create(Self);
uses IntfProcess;
else
Task := TTask3.Create(Self);
type
end;
TProcess = class(TInterfacedObject, IProcess)
InParam := Task.ExecuteTask(InParam);
public
end;
function ExecuteProcess(AInParams: OleVariant):
end;
OleVariant; virtual; abstract;
procedure MessageToProcess(AMessage: string);
procedure TDemoProcess.MessageToProcess(AMessage: string);
virtual; abstract;
begin
end;
FMessageStrings.Add(AMessage);
implementation end;

end. end.

Figure 8: Implementing IProcess with TProcess. Figure 9: TDemoProcess, the concrete TProcess implementation.

7 February 2000 Delphi Informant Magazine


Patterns in Practice

unit Task1; simple set-up where a process (TDemoProcess) can create and invoke
tasks in a non-sequential fashion by using the resulting values from
interface each task to determine the next task to invoke. This is a very simple
implementation of a much more complex set-up, where the process
uses TaskClass;
determines which task to implement based on status values con-
type tained in a database server.
TTask1 = class(TTask)
function ExecuteTask(AInParams: OleVariant):
OleVariant; override;
Conclusion
end;
The Mediator pattern is an ideal approach to any system that
requires some form of process control, and where extensibility
implementation and loosely coupled classes are essential. In the next article in this
series, we’ll show how to use another pattern to enhance the
{ Process will get executed here. This would consist of
Mediator capabilities shown here. We’ll illustrate how to allow a
reading the parameters from AInParams, using them and
then creating the output params which are passed back variable number of parameters to be passed to each task and still
as Result. } maintain loose coupling between each task and between tasks and
function TTask1.ExecuteTask(AInParams: OleVariant): their Mediator object.
OleVariant;
begin
FMediator.MessageToProcess(
Many thanks to John Wilcher for his feedback and help with this
'Executing TTask1.ExecuteTask'); article. A Consulting Manager for Inprise Corp., John provided
Result := Random(5); his experience and some of the initial content for this article. He
end; also provides architectural and design consulting services as a
Principal Consultant for Inprise PSO. You can write John at
end.
[email protected], and visit Inprise’s PSO Web site at
Figure 10: TTask1, an implementation of the TTask abstract class. http://www.inprise.com/services. ∆

The Concrete TProcess Implementation References


Figure 9 illustrates a concrete implementation of the TProcess I use these books whenever considering applying a design pattern to
abstract class. a given problem. They are a must for any developer serious about
learning and using design patterns:
As mentioned earlier, we want to illustrate how the Mediator pattern Design Patterns: Elements of Reusable Object-Oriented Software by
can be used to invoke tasks and how it can do so in a non-sequential Erich Gamma, et al. [Addison-Wesley, 1995].
fashion. This simulates a scenario where a task can specify the next task The Design Patterns Smalltalk Companion by Sherman R. Alpert,
to get executed in a sequence. As this series progresses, we’ll expand on et al. [Addison-Wesley, 1998].
this design to allow for asynchronous invocation of tasks by the Patterns in Java, Volume 1 by Mark Grand [John Wiley &
Mediator object. Sons, 1998].

TDemoProcess is a simple Mediator class that contains a reference


to a TStrings object and adds strings to those objects in the
MessageToProcess method. Therefore, TTask objects can communi-
cate to the TDemoProcess through the MessageToProcess method.
The reference to FMessageStrings, the TStrings instance, is set up Xavier Pacheco is the president and chief consultant of Xapware Technologies Inc.,
in the constructor for TDemoProcess. where he provides consulting services and training. He is also the co-author of
Delphi 5 Developer’s Guide published by SAMS Publishing. You can write Xavier
at [email protected], or visit http://www.xapware.com.
TDemoProcess.ExecuteProcess creates and executes four different
implementations of the TTask class. We’ll use a randomly generated
return value from each TTask implementation to determine the next
TTask implementation to invoke in the sequence. We do this 10
times before leaving the procedure. This effectively illustrates non-
sequential invocation of TTask objects.

The Concrete TTask Implementation


Figure 10 illustrates one of five implementations of the TTask
abstract class.

The implementation of TTask is simple; it first passes a message


back to its Mediator through the MessageToProcess method, then
passes back a random number from 0 to 4. As shown in Figure 9,
TDemoProcess uses this randomly generated result to determine the
next TTask implementation to invoke.

This implementation of the Mediator pattern is simple, yet illustra-


tive as an expandable model for a Mediator pattern. We’ve created a

8 February 2000 Delphi Informant Magazine


Greater Delphi
CORBA / Delphi 4, 5 / VisiBroker / JBuilder

By Dennis P. Butler

CORBA
Part I: Creating a Server

F rom the stand-alone personal computer, to heterogeneous distributed clients and


servers, and everywhere in between, the computing industry has evolved dramatically
over the past several decades. Computer professionals are constantly required to use
their skills to the utmost in their current environment, and then be able to migrate to the
next level when the current framework becomes too complicated or restricting. The evo-
lution of computing continues to point toward a common goal: simplify the work process
by better planning, faster development, and sharing of resources.

The CORBA architecture — short for Common The starting point for CORBA applications is the
Object Request Broker Architecture — was interface that applications share when passing infor-
designed to accomplish this goal. The CORBA mation. This common interface that defines what
architecture defines and implements the frame- information is going to be passed is called IDL, short
work for applications to communicate across for Interface Definition Language. IDL is its own
such previously unbreakable boundaries as multi- language, although the syntax is similar to that of
ple operating systems and programming lan- Java and C++. As its name implies, the only purpose
guages. This capability is achieved through the of this language is to define the interface for objects
use of a common interface and information pass- that will be passed between CORBA applications.
ing mechanism implemented in different pro- The implementation and use of these objects is done
gramming languages. in the specific target language chosen. The only stip-
ulation here is that the target language has facilities
There are several factors that set CORBA apart to map to the CORBA architecture.
from competitive proprietary information sharing
technologies. First of all, CORBA is an open stan- This is where Delphi comes in. CORBA develop-
dard; that is, the specification is constantly being ment is typically associated with C++ or Java devel-
reviewed and updated by the OMG, or Object opment. However, even non-object-oriented lan-
Management Group. This group is made up of guages, such as C and COBOL, have mappings to
hundreds of companies worldwide that decide CORBA, and thus can take advantage of the open
how to evolve the CORBA specification. This architecture. As we’ll see later in this article, Delphi
process of evolution has been occurring since employs several methods to use CORBA in an
1991, when CORBA 1.0 was released. Another application. CORBA can be implemented through
feature that sets CORBA apart from other tech- the use of the Type Library editor for easily creating
nologies is that the interface is common among IDL interfaces, through MIDAS to connect to
languages, not the implementation of it. Other CORBA data, and soon Delphi will gain direct
information-sharing methods rely on operating- facilities to compile IDL code into Pascal source,
system-specific implementations to pass informa- which can be used to implement and use the
tion. While this may be useful in LAN/WAN or CORBA objects. Much like the IDL2JAVA utility,
intranet environments, where operating systems this IDL2PAS utility will be available soon to
can be standardized, true distributed applications Delphi developers to give complete control and
that need to operate over any OS require a more flexibility in creating CORBA applications.
complete solution, such as CORBA. With
CORBA, the client doesn’t need to know any VisiBroker CORBA
details of how the object that it will obtain from a VisiBroker is the ORB (Object Request Broker)
server was implemented. used throughout this two-article series and its exam-

9 February 2000 Delphi Informant Magazine


Greater Delphi
osagent will find the object implementation that the client is
Internet Client Web Server VisiBroker
for C++ ORB looking for, thus allowing a connection to be established
VisiBroker VisiBroker
for Java ORB for Java ORB Naming Services between the client and server. The client can now access the
VisiBroker server object directly.
Java Applet Gatekeeper C++ Objects
for C++ ORB
Smart Agent
C++
Application
Now that we know the steps that take place in a basic CORBA
Internet Enterprise Client
application, we’ll apply them to Delphi to see how they’re
Intranet/
Enterprise accomplished. For this first example, we’ll create an online auc-
Firewall tion demonstration, where the server will keep track of a partic-
Intranet Client ular product, and clients will bid against each other to try to buy
Java the product. For each successful bid, the client application will
Application
VisiBroker notify that the bid was successful, and will update the screen to
for Java ORB VisiBroker show the high-bid amount. Further bids by other clients will
for Java ORB
Java Object now have to outbid that new highest amount to win the product
Event Service
Smart Agent
(which, of course, is a copy of Delphi 5 Enterprise Edition).

Figure 1: Sample high-level CORBA implementation. Example 1: The Online Auction


Our first step in this example is to create the CORBA object for
ples. VisiBroker is the Inprise implementation of the CORBA standard our server, and create the server that will implement this object. As
that adds many additional features to assist developers when creating I mentioned earlier, the CORBA objects are defined by IDL.
applications. Support for thread management and connection manage- Delphi developers don’t need to know IDL to create their object;
ment is included, as well as many libraries and other utilities that are instead, this can be done through the use of the Type Library edi-
created to assist developers when developing CORBA applications. tor. This handy utility allows visual creation of objects and their
Knowledge of the intricacies of VisiBroker isn’t required for this series. interfaces. This utility can also be used later to export to IDL for
For the examples that are used, VisiBroker additions and standard use in other implementations of the object.
CORBA features are used together, much as they would be for actual
production situations. To create the server and its object, start a new Delphi application
and save the form and project. You may want to shrink the dimen-
Let’s take a quick look at a high-level diagram of how CORBA fits sions of the form, as this will be your server application running on
into distributed applications (see Figure 1). your machine. For this example, I have named the files Cserver.dpr
and Cmain.pas. From the main menu of Delphi, select File | New,
As you can see in this standard Inprise diagram, there can be many then select the CORBA Object item on the Multitier page. The
levels of connection across boundaries, such as the Internet, an CORBA Object Wizard will be displayed (see Figure 2).
intranet, or other internal networks. This diagram introduces many
VisiBroker-specific items, such as the Gatekeeper, Smart Agent, As you can see, the object to be defined is named OnlineAuction,
Naming Services, and Event Service. All we need to grasp from the will be a shared instance, and will be single-threaded. The informa-
diagram at this point is that one or more IDL interfaces for tion required by this dialog box is described in more detail here.
CORBA objects has been generated, and instances of those server
object implementations are being passed to various clients. As Class Name. Enter the base name of the object that implements the
shown in the diagram, clients can include the Internet Client run- CORBA interface for your object. Filling in the class name will do two
ning a Java applet, or the C++ application running on the corporate things; it will create a class of this name with a “T” prepended, and cre-
intranet. ate an interface for the class using this name with an “I” prepended.

This two-article series will show how Delphi can be used in this net- Instancing. Use the Instancing combo box to indicate how your
work to also take advantage of the CORBA architecture. In this arti- CORBA server application creates instances of the CORBA object.
cle, we create a CORBA server. Next month, in Part II, we’ll address There are two possible values:
CORBA clients. Instance-per-client — A new CORBA object instance is created
for each client connection. The instance persists as long as the
Before we get started with Delphi, let’s take a quick look at how connection is open. When the client connection closes, the
applications communicate with CORBA. This information will be instance is freed.
relevant later in the article when using Delphi to implement this
technology. We will start here with a very simple example of how a
basic CORBA application can be started between two machines:
First, the ORB Smart Agent (osagent) must be run on a machine
on the network. The osagent will keep track of all server object
implementations that have been registered with it, as well as
keeping track of other osagents.
Next, the server application must be run. This will register its
object(s) with the osagent to let it know that it has object imple-
mentation(s) available for client applications.
A client application is started. When the client application
requires a server object, it will issue a UDP broadcast to find
the closest osagent to search for that implementation. The Figure 2: Creating the CORBA object interface.

10 February 2000 Delphi Informant Magazine


Greater Delphi
to specify all the information we need to define the
interface of our CORBA object. For this example, we
want to create a server object for our online auction
that will hold information about the latest high-bid
amount and person. We will also add a property for the
product that is being bid upon. We also want to add
methods to place a new bid, and check information
about the current bid. In true object-oriented fashion,
properties cannot be modified directly. They must use
accessor methods to change their values. As we’ll see,
the Type Library editor takes care of this as well.

The Type Library editor is also used to define interfaces


to COM objects; in fact, this was the original purpose of
the Type Library editor. Because of this, it has some fea-
tures that aren’t used for CORBA objects. An example
of this is the “Help” information shown in Figure 3.
Figure 3: Delphi’s Type Library editor.
Also, some of the data types that are available in the
Type Library editor may not be CORBA-compliant data
Shared instance — A single instance of the CORBA object han- types. Developers can easily research CORBA data types through
dles all client requests. Because the single instance is shared by the Delphi or VisiBroker Help files.
all clients, it must be stateless.
As we can see in Figure 3, an Interface and CoClass have been creat-
Threading. Use the Threading Model combo box to indicate how ed for our CORBA object. All we are concerned with is the
client calls invoke your remote data module’s interface. Again, there Interface. The CoClass that has been created is COM-specific, and
are two possible values: can be ignored. Note that the interface has taken our CORBA
Single-threaded — Each object instance is guaranteed to receive object name and prepended it with an “I”, as mentioned earlier.
only one client request at a time. Instance data is safe from
thread conflicts, but global memory must be explicitly protected. Methods and properties are added by right-clicking on the
Multithreaded — Each client connection has its own dedicated IOnlineAuction interface and selecting Method or Property from the
thread. However, the object may receive multiple client calls simul- New menu. Add these methods to the IOnlineAuction interface:
taneously, each on a separate thread. Both global memory and PlaceBid returns an Integer, takes a Double (call it Amount) and
instance data must be explicitly protected against thread conflicts. WideString (call it CustomerName) as parameters
GetCurrentPrice returns a Double, no parameters
In this example, the server object will be a shared instance because GetCurrentUser returns a WideString, no parameters
we want all clients to access the same auction object, so they can bid
against each other. If we were writing an object for a banking appli- Add the ProductName property to the IOnlineAuction interface.
cation, an object could be created that would contain information
specific to a banking account of the customer running the client When entering the parameters for the methods in the Type Library
application. In this case, an Instance-per-client setting would be more editor, the following information is required:
appropriate, since a separate object would be created for each client, Modifier specifies the nature of the parameter, such as whether it
making the contents of that object private to the client. should be treated as an in parameter, out parameter, etc.
Name is the name of the parameter.
The object is also created for single threading; since only one Type is the data type of the parameter. The list contains the list
client request will be processed at a time, multi-threading isn’t of CORBA-compliant data types.
necessary. Multi-threading is very useful when developing very Default Value specifies whether the parameter will have a
large applications that require a higher level of flexibility in situa- default value.
tions where the server must be able to handle multiple requests
that may be occurring simultaneously. For this simple example, For the purposes of this example, the modifier was kept blank for all
we won’t take advantage of this feature. parameters because no special setting was needed for this example. This
defaults the parameter to the in setting of IDL; thus modifications
Click OK to create the new unit and save the file. This will create the made to the parameter variable once the called procedure is completed
Pascal shell for the CORBA object won’t be reflected when control is returned. In fact, the Delphi compiler
interface. Instead of having to type in will show a hint for a parameter within its method if an attempt is
the interface manually, Delphi allows made to modify the value of the parameter. There are several types of
us to visually create the object inter- modifiers that can be used that correspond to IDL parameter types. The
face using the Type Library editor most commonly used parameters in IDL are in, out, and inout. This
(see Figure 3). (The Type Library means that parameter information flows into the server, out from the
editor is available from the main server, or both. This is done with the Type Library editor settings of
menu by selecting View | Type blank(in), out(out), and var(inout).
Library.)
Figure 4: Completed type When completed, the tree view for the Type Library editor should
library tree. The Type Library editor allows us look like Figure 4. Click on the “Refresh Interface” button (it looks

11 February 2000 Delphi Informant Magazine


Greater Delphi
like the two-arrowed recycle symbol) from the Type Library editor
to synchronize the source file for the CORBA object. Note: This
Begin Listing One — csrvobj.pas shell
isn’t entirely WYSIWYG, as is the standard Delphi IDE; the unit csrvobj;
“Refresh Interface” button must be clicked.
interface

Once the refresh button has been clicked, the Type Library editor uses
can be closed, and the source file can be saved (csrvobj.pas in this Windows, Messages, SysUtils, Classes, Graphics, Controls,
case). We now have our server object interface defined, and the ComObj, StdVcl, CorbaObj, CServer_TLB;

Pascal code shell from which we can add functionality for the type
object. The Type Library editor has created some files for us, such TOnlineAuction = class(TCorbaImplementation,
as the _TLB stub file that’s created based on the server applica- IOnlineAuction)
tion project name. In this example, since the project was named protected
function Get_ProductName: WideString; safecall;
CServer, it’s CServer_TLB.pas. Since this file is automatically
function GetCurrentPrice: Double; safecall;
generated, no additional work is needed on it. This file sets up function GetCurrentUser: WideString; safecall;
stub and skeleton classes for the server object, as well as defining function PlaceBid(Amount: Double;
several other classes that may be used, such as the CORBA object const CustomerName: WideString): Integer; safecall;
factory class and the COM CoClass class. The only thing we real- procedure Set_ProductName(const Value: WideString);
safecall;
ly need to know at this point is that the CORBA shell has been end;
created for us from how we defined the interface in the Type
Library editor, and a TLB file has been created from which we implementation
can get our object reference for the server.
uses
CorbInit;
Our server source file csrvobj.pas has been filled in from the
Type Library editor with the methods and properties that were function TOnlineAuction.Get_ProductName: WideString;
defined. The empty shell, csrvobj.pas, is shown in Listing One. begin
Now we need to code the implementation of the object. We need
end;
to add a few private variables to hold the current high-bid price,
the current high-bid customer, and the product being bid on. We function TOnlineAuction.GetCurrentPrice: Double;
also need to initialize these private variables in the constructor begin
for the object. Finally, we need to implement the object with
end;
code to provide functionality to the methods that have been cre-
ated for us. The completed source, with comments, is shown in function TOnlineAuction.GetCurrentUser: WideString;
Listing Two. begin

All that was provided by Delphi was the code shell; the rest had end;

to be filled in to give the object interface an implementation. We function TOnlineAuction.PlaceBid(Amount: Double;


now have the server for our object. To use this server of our const CustomerName: WideString): Integer;
CORBA object, all we need to do is add the csrvpas unit to the begin
uses clause of any form of a project. When this is done, the ini-
end;
tialization code for the object will be fired when that form is
used. Thus, the server will be started, and an object will be creat- procedure TOnlineAuction.Set_ProductName(
ed that is available for use. const Value: WideString);
begin
Until Next Month
end;
That’s it for this article. Next month, we’ll implement CORBA
clients, including one written in Java using Borland’s JBuilder initialization
product. See you then. ∆ TCorbaObjectFactory.Create('OnlineAuctionFactory',
'OnlineAuction','IDL:CServer/OnlineAuctionFactory:1.0',
IOnlineAuction, TOnlineAuction, iSingleInstance,
The files referenced in this article are available on the Delphi
tmSingleThread);
Informant Magazine Complete Works CD located in end.
INFORM\00\JAN\DI200002DB.
End Listing One
Begin Listing Two — Implemented csrvobj.pas
unit csrvobj;

interface
Dennis P. Butler is a Senior Consultant for Inprise Corp., based out of the Professional
Services Organization office in Marlboro, MA. He has presented numerous talks at // Note the included units for ComObj, CorbaObj,
Inprise Developer Conferences in both the US and Canada, and has written a variety // and Cserver_TLB.
of articles for various technical magazines, including CBuilderMag.com. He can be uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
reached at [email protected], or (508) 481-1400.

12 February 2000 Delphi Informant Magazine


Greater Delphi
ComObj, StdVcl, CorbaObj, CServer_TLB; // Method to place a new bid: Take parameters for amount
// of bid and customer who is placing the bid.
// Class is defined as a CORBA class that implements function TOnlineAuction.PlaceBid(Amount: Double;
// the IOnlineAuction interface. const CustomerName: WideString): Integer;
type begin
TOnlineAuction = class(TCorbaImplementation, if Amount > FCurrentPrice then
IOnlineAuction) begin
private FCurrentPrice := Amount;
// Private variables to hold object information FCurrentCustomer := CustomerName;
// about auction. Result := 1;
FProductName : WideString; end
FCurrentPrice : Double; else
FCurrentCustomer : WideString; Result := 0;
public end;
// Override create to initialize private variables.
constructor Create(Controller: IObject; // Code provided by Delphi to call the generated CORBA
AFactory: TCorbaFactory); override; // object factory to get an object reference for the
protected // server. Note parameters match what we defined in the
// Accessor methods for FProductName property. // CORBA object wizard. Since it's in the initialization
function Get_ProductName: WideString; safecall; // section, the code will run whenever this unit is
procedure Set_ProductName(const Value: WideString); // included in the uses section of another unit and the
safecall; // server will be started.
// Function to get the current price for the initialization
// auction product. TCorbaObjectFactory.Create('OnlineAuctionFactory',
function GetCurrentPrice: Double; safecall; 'OnlineAuction', 'IDL:CServer/OnlineAuctionFactory:1.0',
// Function to get the current customer for the IOnlineAuction, TOnlineAuction, iSingleInstance,
// auction product. tmSingleThread);
function GetCurrentUser: WideString; safecall; end.
// Function to place a new bid.
function PlaceBid(Amount: Double;
const CustomerName: WideString): Integer; safecall; End Listing Two
end;

implementation

// Included with Delphi to initialize CORBA object.


uses
CorbInit;

// Overridden create for our object.


constructor TOnlineAuction.Create(Controller: IObject;
AFactory: TCorbaFactory);
begin
inherited;
// Initialize our private variables.
FProductName := '<NA>';
FCurrentCustomer := '<NA>';
FCurrentPrice := 0;
end;

// Method to get the property value for the current


// auction product.
function TOnlineAuction.Get_ProductName: WideString;
begin
Result := FProductName;
end;

// Method to set the property value for the current


// auction product.
procedure TOnlineAuction.Set_ProductName(
const Value: WideString);
begin
FProductName := Value;
end;

// Method to get the current price of the high bid.


function TOnlineAuction.GetCurrentPrice: Double;
begin
Result := FCurrentPrice;
end;

// Method to get the current customer name of the high bid.


function TOnlineAuction.GetCurrentUser: WideString;
begin
Result := FCurrentCustomer;
end;

13 February 2000 Delphi Informant Magazine


Inside OP
VCL / Windows Messages / Delphi 1-5

By Jeremy Merrill

Modifying VCL Behavior


A Practical Example Using Visual Components

T o make a visual component behave differently from its defaults, we generally have to
create a new component that descends from the original component’s class. This
article will show how to dynamically change the behavior of a native Delphi visual com-
ponent without creating a new class.

How is this possible? The secret is to intercept the


unit LinkedLabel;
Windows messages being sent to the control. This
interface
can be accomplished by using a TControl property
named WindowProc, which essentially points to a
uses component’s Windows message event handler.
Messages, Classes, Controls, StdCtrls;

To demonstrate this technique, we’ll create a


type
TLinkedLabel = class(TLabel) LinkedLabel component, which will link itself to
private any TControl and dynamically modify its behav-
// The associate control. ior. TLinkedLabel will descend from TLabel, and
FAssociate: TControl;
will feature four additional published properties:
// Puts FAssociate into all caps mode.
FCapsLock: Boolean;
Associate — the companion control whose
// The distance between the label and the associate. behavior we’ll be modifying.
FGap: Integer; CapsLock — when this Boolean property is
// True when the label is on top of the associate. True, certain types of associate controls will
FOnTop: Boolean;
process lower-case keystrokes as upper case.
// Saves the original value of FAssociate.WindowProc.
FOldWinProc: TWndMethod; This doesn’t work with all controls, because
// Used to prevent infinite update loops. not all controls respond to the WM_CHAR
FUpdating: Boolean; message in the same way. Testing reveals that
protected
Edit, MaskEdit, Memo, and RichEdit controls
procedure Adjust(MoveLabel: Boolean);
procedure SetGap(Value: Integer);
all respond to the CapsLock property, while
procedure SetOnTop(Value: Boolean); ComboBox does not. Obviously, CapsLock will
procedure SetAssociate(Value: TControl); have little or no effect on many other compo-
procedure NewWinProc(var Message: TMessage); nents, such as a Button or CheckBox control.
procedure Notification(AComponent: TComponent;
Gap — the distance between the LinkedLabel
Operation: TOperation); override;
procedure WndProc(var Message: TMessage); override; and its associate control.
public OnTop — this Boolean property determines
constructor Create(AOwner :TComponent); override; whether the LinkedLabel will appear to the
destructor Destroy; override;
left of, or on top of, the associate control.
published
property Associate: TControl
read FAssociate write SetAssociate; In addition, TLinkedLabel will keep the Enabled
property CapsLock: Boolean and Visible properties of the LinkedLabel and its
read FCapsLock write FCapsLock; associate synchronized. It will also maintain a set
property Gap: Integer read FGap write SetGap default 8;
distance and orientation from the associate con-
property OnTop: Boolean read FOnTop write SetOnTop;
end; trol. This means that when you move the
LinkedLabel, the associate moves with it, and
Figure 1: The TLinkedLabel class declaration. vice versa.

14 February 2000 Delphi Informant Magazine


Inside OP
Let’s take a look at the TLinkedLabel class declaration, shown in FAssociate and FOldWinProc to nil, and FCapsLock, FOnTop, and
Figure 1. FUpdating to False, all without having to explicitly initialize them in
the constructor. Therefore, the only thing we need to set in the con-
Now let’s look at the different methods of this component in detail, structor is the default Gap value:
starting with the constructor. Note that when creating a new object,
all of its associated memory is cleared. This will automatically set implementation

procedure TLinkedLabel.Adjust(MoveLabel: Boolean); constructor TLinkedLabel.Create(AOwner: TComponent);


var begin
dx, dy: Integer; inherited;
begin FGap := 8;
if (Assigned(FAssociate)) then begin end;
if (FOnTop) then
begin
dx := 0; Now we come to the Adjust method, which is responsible for position-
dy := Height + FGap; ing the LinkedLabel component or the associate control, depending on
end the value of the MoveLabel parameter. As you’ll see in the code, the
else
begin
actual position of the LinkedLabel in relationship to the associate is
dx := Width + FGap; based on the Gap and OnTop properties (see Figure 2). Although OnTop
dy := (Height - FAssociate.Height) div 2; only provides us with two possible orientations, there are many other
end; possibilities that could easily be programmed into this component.
if (MoveLabel) then
However, adding a lot of “bells and whistles” to TLinkedLabel is not the
begin
Left := FAssociate.Left - dx;
focus of this article, and has, therefore, been entrusted to the reader.
Top := FAssociate.Top - dy;
end At this point, we come to the set methods of the Gap and OnTop
else properties (see Figure 3). These are needed so we can reposition the
begin
FAssociate.Left := Left + dx;
LinkedLabel when the Gap or OnTop values are modified.
FAssociate.Top := Top + dy;
end; Now we come to the SetAssociate method (see Figure 4).
end;
end;
To understand it, we need to discuss the WindowProc property in more
Figure 2: The Adjust method. detail. WindowProc is defined as of type TWndMethod. TWndMethod
can be found in the Controls unit with the following definition:
procedure TLinkedLabel.SetGap(Value: Integer);
begin TWndMethod = procedure(var Message: TMessage) of object;
if (FGap <> Value) then
begin
FGap := Value;
Notice that FOldWinProc is also defined as a TWndMethod, and
Adjust(True); that the NewWinProc method has the same parameter structure
end; as TWndMethod. This allows us to point FOldWinProc to the
end; current value of WindowProc and assign WindowProc to the
NewWinProc method.
procedure TLinkedLabel.SetOnTop(Value: Boolean);
begin
if (FOnTop <> Value) then Why do we need to use FOldWinProc if WindowProc is just another
begin event property? Because the difference between WindowProc and any
FOnTop := Value; other event property is that WindowProc is already pointing to an exist-
Adjust(True);
end;
ing event handler. If we simply point WindowProc to our own method,
end; the control will no longer be able to respond to any Windows messages.
To solve this problem, we set FOldWinProc to the current value of
Figure 3: The set methods of the Gap and OnTop properties.
WindowProc before pointing WindowProc to the NewWinProc method.

procedure TLinkedLabel.SetAssociate(Value: TControl); In NewWinProc, we call the old message handler, via
begin FOldWinProc, after and acting upon specific Windows messages.
if (Value <> FAssociate) then begin Because we modify the WindowProc property on the associate
if (Assigned(FAssociate)) then
control, it’s important that we restore its former value before
FAssociate.WindowProc := FOldWinProc;
FAssociate := Value; changing to a new associate component.
if (Assigned(Value)) then
begin It’s also important that we don’t leave the associate’s WindowProc
Adjust(True); property pointing to a routine that no longer exists. We therefore
Enabled := FAssociate.Enabled;
Visible := FAssociate.Visible;
call SetAssociate(nil) in the destructor, which, as we’ve seen, will
FOldWinProc := FAssociate.WindowProc; restore WindowProc to its original value:
FAssociate.WindowProc := NewWinProc;
end; destructor TLinkedLabel.Destroy;
end; begin
end; SetAssociate(nil);
inherited;
Figure 4: The SetAssociate method. end;

15 February 2000 Delphi Informant Magazine


Inside OP

procedure TLinkedLabel.NewWinProc(var Message: TMessage);


If you examine this routine, you’ll see we make no attempt to
var process Windows messages. We react to specific messages, then
Ch: Char; let the associate process them normally by calling FOldWinProc.
begin In the case of the WM_CHAR message, we change part of the
if (Assigned(FAssociate) and (not FUpdating)) then begin
message, causing the component to think an upper-case charac-
FUpdating := True;
try ter was pressed.
case(Message.Msg) of
WM_CHAR: Finally, we look at two different messages to see if the associate has
if (FCapsLock) then begin been moved. This is because components that descend from
Ch := Char(TWMKey(Message).CharCode);
if (Ch >= 'a') and (Ch <= 'z') then
TWinControl will get a WM_MOVE message when they’re
TWMKey(Message).CharCode := ord(UpCase(Ch)); moved, and other visual components (such as a Label) will get the
end; WM_WINDOWPOSCHANGED message. The WM_SIZE message
CM_ENABLEDCHANGED: is examined, because if the OnTop property is False, the position of the
Enabled := FAssociate.Enabled;
LinkedLabel will change based on the height of the component.
CM_VISIBLECHANGED:
Visible := FAssociate.Visible;
WM_SIZE, WM_MOVE, WM_WINDOWPOSCHANGED: The last method of our component is where we make adjustments
Adjust(True); to the associate when the LinkedLabel is changed (see Figure 6).
end; Rather than override existing methods of TLabel to do this, we
finally
FUpdating := False;
employ the same technique we used to modify the associate’s behav-
end; ior. Notice that instead of tapping into the WindowProc property,
end; we override the WndProc method. How is this the same technique?
FOldWinProc(Message); If you look at TControl ’s constructor, you’ll see that WindowProc is
end;
initialized to point at the WndProc method. So in essence, we are
Figure 5: The NewWinProc method. overriding the same method, but in a cleaner way, and without
having to store the original value of WindowProc.
procedure TLinkedLabel.WndProc(var Message: TMessage);
begin One final point should be made about the previous component.
if (Assigned(FAssociate) and (not FUpdating)) then begin You’ll notice the use of FUpdating in both NewWinProc and
FUpdating := True; WndProc. This variable is used to alert the LinkedLabel and the
try associate that the other component is making a change. If you
case(Message.Msg) of
CM_ENABLEDCHANGED: FAzssociate.Enabled :=Enabled;
don’t do this, it’s easy to create an infinite updating loop, or get
CM_VISIBLECHANGED: FAssociate.Visible := Visible; unexpected results. Here’s one flow of events that demonstrates
WM_WINDOWPOSCHANGED: Adjust(False); the need for the FUpdating variable:
end; The user drags the LinkedLabel to a new position.
finally
WndProc receives a WM_WINDOWPOSCHANGED message,
FUpdating := False;
end; and fires Adjust(False) to move the associate.
end; Adjust sets FAssociate.Left to the new value as part of reposition-
inherited; ing the associate.
end; FAssociate fires off a WM_MOVE message, indicating it has
Figure 6: Instead of tapping into the WindowProc property, we changed position.
override the WndProc method. NewWinProc detects the WM_MOVE message and calls
Adjust(True) in an attempt to move the LinkedLabel to match
In addition, we don’t want to be pointing to an associate that no the associate’s new position.
longer exists. By overriding the Notification method, we can know
when the associate control is destroyed, and reset our pointer to the As you can see, we haven’t even gotten a chance to change the asso-
associate accordingly: ciate’s Top property to match the LinkedLabel’s new position before
the associate tries to move the LinkedLabel. By using the FUpdating
procedure TLinkedLabel.Notification(AComponent: TComponent; variable, the associate will not notice the WM_MOVE message and
Operation: TOperation); won’t try to call Adjust to reposition the LinkedLabel.
begin
if ((Operation = opRemove) and
(AComponent = FAssociate)) then A Couple of Issues
SetAssociate(nil); There are a couple of problems with the TLinkedLabel component that
end; I did not address in this article. The following are brief descriptions:
You can cause all kinds of problems if you link two or more
Now we come to the NewWinProc method (see Figure 5). Here, LinkedLabels to the same component, and then destroy one or
we simply look for specific Windows messages being sent to the more of them. You can end up breaking the link to other
associate component. It’s important to realize that although this LinkedLabels, and even cause the linked component’s
method is only called by the associate control, it’s actually part WindowProc to point to a non-existent routine.
of the LinkedLabel, i.e. Self = LinkedLabel, not the associate If you link a LinkedLabel to a component on a different
control. This is identical to creating an OnClick event handler form, the Notification method won’t be called when that
for a button. The OnClick event handler is created as part of the component is destroyed. Calling FreeNotification when the
button’s parent form and is not a new method extending the component is linked will fix this, but that doesn’t really
TButton class. address the problem. The real problem is that we allowed it

16 February 2000 Delphi Informant Magazine


Inside OP
to be linked to the component on the other form in the first
place. What we really want to do is restrict associates to only
those components with the same parent as the LinkedLabel.
Although it’s not difficult to do this, it’s a little tricky to only
show eligible components in the Associate properties drop-
down list in the Object Inspector.

Conclusion
That’s about it. Replacing the WindowProc of an existing component
does have its limitations, but can be a very useful technique. I can’t
think of any other reasonable way to design a component like
TLinkedLabel and have the associate control move the LinkedLabel
when the associate is moved. I’m not going to try and list other pos-
sible uses for this technique, because they are countless and limited
only by a programmer’s ingenuity. ∆

The files referenced in this article are available on the Delphi


Informant Magazine Complete Works CD located in
INFORM\00\JAN\DI200002JM.

Jeremy Merrill is an EDS contractor in a partnership contract with the Veteran’s


Health Administration. He is a member of the VA’s Computerized Patient Record
System development team, located in the Salt Lake City Chief Information
Officer’s Field Office.

17 February 2000 Delphi Informant Magazine


Visual Programming
VFI / OOP / Object Repository / Delphi 4, 5

By Rick Spence

Visual Form Inheritance


Part I: An Introduction and Primer

If you use Visual Form Inheritance extensively, your development time will decrease,
your applications will have fewer errors, and your UI will have a more consistent look
and feel. Are these bold claims justifiable? This article shows how to get the most from a
Delphi feature known as VFI for short. Read on and see for yourself.

What Is Visual Form Inheritance? You could design each form separately, writing
VFI refers to Delphi’s ability to create new forms code for each. A better approach would be to start
that either inherit, or are derived from, existing with a generic form that contains controls and
forms. VFI shares the advantages of inheritance in code common to all the forms, then use VFI to
general, the primary one being the ability to cap- create the forms based on the generic form, cus-
ture the similarities of different classes in a hierar- tomizing each new form as required.
chy. In this case, the classes are forms generated by
Delphi’s Form designer. Put another way, VFI Once you’ve created these forms, you can still add
allows you to write code in one place that’s com- functionality to the generic form. For example,
mon to multiple forms. Writing code just once you may want to add a background logo to each
obviously increases productivity and decreases form or implement a generic search facility. All
errors. This applies to forms in one application you need to do is add this in the generic form,
and between applications. and the specific forms automatically inherit this
look and behavior.
There’s more to VFI than code reuse, however.
This is where the “Visual” part of VFI comes in. Understanding how VFI works requires a working
You can use the Delphi Form designer to lay out knowledge of a language’s implementation of
forms, and have that layout shared among forms. inheritance in general. A quick review is in order.
Having forms share a layout leads to a more con-
sistent look and feel, and improves productivity. Inheritance in Brief
Inheritance is an object-oriented programming fea-
Using VFI also allows you to design your forms in ture that allows you to create new classes based on
a hierarchy. All changes to forms higher in the existing classes. The existing class is called a super-
hierarchy affect all inheriting forms. Likewise, you class; the new class is called a subclass. Classes, of
can add components and/or code to forms, thus course, contain data and code. Subclasses can see
changing all inheriting forms. the public code and data of their superclasses.

Assume you’re creating an application to manage Delphi supports single inheritance, meaning class-
customers and their invoices. You determine you es directly inherit from only one class. (Some
need three general maintenance forms: one for the other languages support multiple inheritance,
customers, a second for their invoices, and a third which allows one class to inherit from more than
for the parts you’re selling. Although each form is one superclass.) Delphi also supports repeated
displaying different information, certain elements inheritance, which allows Class A to inherit from
of the forms are the same. Say, for example, that Class B, and Class B to inherit from Class C. In
each form needs a page control with two tab this case, Class A can see the public data and code
sheets: one with a database grid, the other with of Class B and Class C (see Figure 1).
table-specific edit controls. Each form also needs
buttons to save and cancel changes, add and Consider the following sample class produced
delete records, etc. by Delphi’s Form designer:

18 February 2000 Delphi Informant Magazine


Visual Programming
type protected section are visible to Repeated Inheritance
TFrmRichEdit = class(TForm) methods of the class, just as the A inherits from B, which inherits from C.
btnCancel: TButton;
declarations in the private sec-
btnOK: TButton;
rtfNotes: TRichEdit; tion. The difference between Class C

private private and protected has to do


{ Private declarations } with subclasses. Subclasses can
public see the protected declarations,
{ Public declarations }
end;
but not the private ones.
Class B
With that quick review behind
The class is named TFrmRichEdit and inherits from the TForm us, let’s put VFI to use.
class. TForm is one of many classes declared in the VCL. TForm
and its superclasses define data and code common to all types of Mechanics of VFI
forms. This is where you’ll find the form’s font, caption, and To create forms based on exist- Class A
color. It’s also where you’ll find the Close, Show, and ShowModal ing forms, you need to use
methods. Thus, TForm and its ancestors capture the similarities Delphi’s Object Repository. Figure 1: Repeated inheritance.
of all forms. If Delphi didn’t support inheritance, the Form Using the Object Repository,
designer would have to generate all these common methods and you can create a form based on a form already in the Object
data in every form you create. Repository, or in your current project. As you’ll see, you use the
same IDE menu items.
To instantiate the TFrmRichEdit class, you first need to declare an
object of that class, which Delphi’s Form designer does for you: To create a new form in your application, select File | New Form.
To create a form based on an existing form, select File | New.
var This opens the Object Repository. Now you can choose what you
frmRichEdit: TfrmRichEdit; want to create. You can add your own forms to the Object
Repository, but there are some forms supplied with Delphi you
Then you can instantiate the class by calling its constructor, Create, can use as starting points. Select the Forms page, and you’ll see
passing the form’s owner, Application: forms labeled Dual list box and About box (see Figure 2). These
forms are available for any application to use.
frmRichEdit := TFrmRichEdit.Create(Application);
Notice the Copy, Inherit, and Use radio buttons at the bottom of
There’s inheritance in action again. Who declares the Create the form. These tell Delphi how you want your new form to
method? One of TFrmRichEdit’s superclasses, i.e. TCustomForm. relate to the form in the Object Repository:
Copy means you want to duplicate the form in the Object
Once you’ve created an object based on a subclass, you can directly Repository in your own application. Your new form will be
access that object’s data and methods: completely independent from the form in the Object
Repository; if you subsequently change the form in the Object
frmRichEdit.btnOk.Enabled := False; Repository, this won’t change the new form.
Inherit means the new form is based on the form in the
and the data and methods of its superclasses: Object Repository and retains its link to this form; if you
change the form in the Object Repository, this will also
frmRichEdit.Caption := 'RTF Editor'; change all forms inherited from this form. This is the most
frmRichEdit.Show; common VFI option.

Caption is declared in TControl (several levels up in the hierarchy),


and the Show method is declared in TCustomForm.

So far we’ve discussed the public data and code of a class. What do we
mean by “public?” And are there other ways to declare data and code?
Public refers to the visibility, or scope, of the declarations. A class’ public
data and code are visible to the user of the class. In the previous sample
class, btnCancel, btnOk, and rtfNotes are all public instance variables.

There are two other ways to declare instance variables and code in a
class: You can make them private or protected. Again, if you review
the class declaration just shown, you will see two empty sections
labeled private and public. These are sections where you can add
your own code and data. Any additions you make to the private sec-
tion of the class are only visible to methods of the class. Users of the
class can’t see the private data or methods.

Delphi’s Form designer doesn’t generate a protected section for


you, but you can add one. Any declarations you make in the Figure 2: The Forms tab of the Delphi 4 Object Repository.

19 February 2000 Delphi Informant Magazine


Visual Programming
Usemeans you want to directly use the form in the Object A Quick Example
Repository. Any changes you make directly change the form in the Imagine you’ve created several forms inheriting from the Dual list
Object Repository, thus changing forms inherited from this form. box form in the Object Repository. You’ve customized the sub-
forms with information specific to your application, and now you
Forms in your current application are also available for reuse. want to add drag-and-drop capabilities to each form; you want to
When you open the Object Repository, there should be a tab allow your users to drag items from the left list box into the right
sheet whose caption is the name of your project. Select this and list box. If you hadn’t used VFI, you would have to implement
you’ll see the forms in your current project. The only difference the drag-and-drop capability in each subform. However, because
between working with forms in the Object Repository and work- you used VFI, you can add the code in one place: the form in the
ing with forms in your application is that you can only inherit Object Repository. Here’s a step-by-step approach:
from forms in your existing application, i.e. only the Inherit radio 1) Select the Dual list box form in the Object Repository, with the
button is available. It’s the only option that makes sense. Use radio button selected.
2) Set the DragMode property of the left list box, SrcList, to
Once you’ve created a new form that inherits from a form in the dmAutomatic. This automatically enables dragging when the
Object Repository, you can still change the new form. You can user holds the left mouse button down with an item selected.
add new components and write new methods. You can also The alternative mode is dmManual, in which case your program
change things you inherit from the form in the Object must explicitly enable dragging in code.
Repository. You can move and resize inherited components, but 3) Write the onDragOver event for the right list box, DstList, as:
you can’t delete a component you inherited. Attempting to do so
will result in a design-time error message. Although you can’t Accept := (source = srcList);
delete an inherited component, you can achieve the same effect
by making it invisible by setting its visible property to False. The onDragOver event is fired when the user drags an item
over the control. The event is used to determine whether the
Once you’ve changed a property of an inherited component, you’ve control is a valid destination for the dragged item. The code
broken the link for that property between the subform and the we wrote tells Delphi to accept the drop if the source of the
superform. For example, assume you move a button to the left in a drag is the SrcList list box.
subform, then move the same button in the form in the Object 4) Write the onDragDrop event of the DstList list box as:
Repository. Because the subform has broken the link to the Left
property from the superform, the component in the subform doesn’t IncludeBtnClick(Sender);
move (it would if you hadn’t broken the link). You can reestablish
the link by right-clicking on the component, and selecting Revert to The onDragDrop event is fired when the user drops the item.
inherited. This undoes all changes to the subclass component. This code simply fires the same event as if the user had clicked
the > button, so there’s no point in duplicating that code.
When working with methods you inherit from a superclass, you have
several options. You can either replace the superclass method altogeth- VFI under the Hood
er, or execute your own code in addition to the superclass method. One of my favorite things about Delphi is that nothing’s hidden.
Let’s look at an example. On the Forms page of the New Items dialog Everything we’ve just done is implemented in source code. To see
box, double-click the Dual list box form with the Inherited radio button how VFI actually works, all you need to do is examine the code pro-
selected. Double-click on the > button; this button enables you to type
move a selected item from the left list box to the right list box. The TDualListDlg = class(TForm)
IDE will generate the following method template for you: OKBtn: TButton;
CancelBtn: TButton;
HelpBtn: TButton;
procedure TDualListDlg2.IncludeBtnClick(Sender: TObject); SrcList: TListBox;
begin DstList: TListBox;
inherited; SrcLabel: TLabel;
DstLabel: TLabel;
end; IncludeBtn: TSpeedButton;
IncAllBtn: TSpeedButton;
The keyword inherited means “Call a method with the same name ExcludeBtn: TSpeedButton;
in the superclass.” The code in the superclass is what actually moves ExAllBtn: TSpeedButton;
procedure IncludeBtnClick(Sender: TObject);
the selected items from the left list box to the right list box:
procedure ExcludeBtnClick(Sender: TObject);
procedure IncAllBtnClick(Sender: TObject);
procedure TDualListDlg.IncludeBtnClick(Sender: TObject); procedure ExcAllBtnClick(Sender: TObject);
var procedure MoveSelected(List: TCustomListBox;
Index: Integer; Items: TStrings);
begin procedure SetItem(List: TListBox; Index: Integer);
Index := GetFirstSelection(SrcList); function GetFirstSelection(List: TCustomListBox):
MoveSelected(SrcList, DstList.Items); Integer;
SetItem(SrcList, Index); procedure SetButtons;
end; private
{ Private declarations }
public
This code is only called because the subclass explicitly makes a call { Public declarations }
to it using the inherited keyword. You’re free to place your addition- end;
al code before or after the inherited call. You’re also free to remove Figure 3: Class declaration when you use the Dual list box from
the inherited keyword, thus removing the call. the Object Repository.

20 February 2000 Delphi Informant Magazine


Visual Programming
duced by the Form designer. Let’s start by looking at the class decla- file. In fact, all you will see in the subform’s .DFM file are the sub-
ration generated when you simply copy a form from the Object form properties that are different from its superclass. That’s the best
Repository into your application. We’ll use the Dual list box as our way to see exactly which properties were changed.
example again. Figure 3 shows the class declaration.
The final thing we need to look at in this section is the structure
And all the code is right there in the implementation section of the Object Repository itself. You may be surprised to learn that
(Figure 3 doesn’t show that). The important line is the one that the Delphi Object Repository is nothing more than an interface to
declares the class: an .INI file. The Delphi 4/5 Object Repository is stored in the file
Delphi32.DRO in the \bin directory of your Delphi installation.
TDualListDlg = class(TForm) Open it with Notepad or some other text editor to see for yourself.
Figure 5 shows the portion concerning the Dual list box.
This class inherits directly from TForm. Everything about Dual list
box is in this .PAS file, and its associated .DFM file. The important line is the section heading. As you can see, it tells
Delphi where the code is located on the disk. There are similar
Now consider Figure 4, which shows the entire Pascal file when you entries for each form in the Object Repository.
inherit from the form in the Object Repository.
VFI in the Real World
Again, the line declaring the class is important: Technical articles often paint a rosy picture of development. Just
follow these steps and you’re in programming utopia. In the real
TDualListDlg3 = class(TDualListDlg) world, VFI can save you time and make your programs more con-
sistent. But it does take effort. The first time you use VFI, you’ll
This shows that the new form simply inherits from another class, spend a lot of time designing your form hierarchies. Here’s how it
TDualListDlg. TDualListDlg, of course, is the form in the Object usually goes the first time you use it.
Repository. Note how the new form’s uses clause includes that
unit (DUALLIST). Also note that the project source code, the You start developing the way you always did: You create forms and
.DPR file, includes that unit, as well: write code. Then you realize that several forms look pretty much the
same, and you’re writing similar code in each form. At this stage, you
uses decide VFI will help, so you find the common layouts and code and
Forms, move those into a generic form. You place that form in the Object
Unit1 in 'Unit1.pas' { Form1 },
Repository, then rework the child forms to inherit from the main
Unit2 in 'Unit2.pas' { Form2 },
DualList in '..\OBJREPOS\DUALLIST.pas' { DualListDlg }, form. But it doesn’t end there. It’s unrealistic to think you’ll come up
Unit3 in 'Unit3.pas' { DualListDlg3 }; with a perfect design the first time. What usually happens is you con-
tinue developing the subforms, continually looking for things you can
move up the hierarchy to avoid duplication. I consider this class design
Because your new form inherits from another form, your project a very similar process to database normalization: You’re trying to avoid
must include both forms. redundancy and duplication, and it takes time.

Changes you make to your subform are simply made to the sub- This type of design work is an iterative process. You might not see
form’s .PAS and .DFM files. For example, if your subclass changes a productivity benefits the first time you try it. Then again, you
component’s position, that change is recorded in the subclass’ .DFM might. An example springs to mind from one of my own develop-
ment projects. We designed a standard interface for editing database
unit Unit3; tables. We provided all the usual features — add, edit, delete, search
interface
for records, etc. — and allowed the user to access these activities
from the menu, as well as from the buttons located in a Coolbar. All
uses the common code and layout was stored in a common superclass
Windows, Messages, SysUtils, Classes, Graphics, Controls, (which we use in all our applications), and the actual editing forms
Forms, Dialogs, DUALLIST, StdCtrls, Buttons;
were derived from this common form. The user didn’t care for the
type Coolbar, and wanted it changed to a simpler interface. We had
TDualListDlg3 = class(TDualListDlg) almost 30 forms developed this way, but all we had to do was
private replace the Coolbar with a Toolbar in one form and recompile.
{ Private declarations }
public
{ Public declarations } [C:\PROGRAM FILES\BORLAND\DELPHI4\OBJREPOS\DUALLIST]
end; Type=FormTemplate
Name=Dual list box
var Page=Forms
DualListDlg3: TDualListDlg3; Icon=C:\PROGRAM FILES\BORLAND\DELPHI4\OBJREPOS\DUALLIST.ICO
Description=Dialog box with two list boxes. Supports moving
implementation items from one list to the other.
Author=Borland
{$R *.DFM} DefaultMainForm=0
DefaultNewForm=0
end. Ancestor=

Figure 4: Pascal file for the Dual list box inherited from the Figure 5: Portion of Delphi Object Repository concerned with
Object Repository. the Dual list box.

21 February 2000 Delphi Informant Magazine


Visual Programming
The real benefits of VFI, though, materialize the second or third
time you use a design. Remember that you can use the Object
Repository to share forms between applications. In our shop, the
first stage of our design is to list forms required for an application,
and note their similarities. We then decide whether to use existing
templates we’ve already developed, modify those slightly, or develop
new ones. Whichever way we go, we rarely start from scratch.

If you want to get started using VFI, I’d suggest you start with a
database maintenance form. Decide how you want the form to
look, design it once, and place it in the Object Repository. Place as
much common code in this generic form as possible, then create
actual forms containing data specific to the actual tables, inheriting
from this generic form. As you write code in the actual forms, con-
sider whether that code could be moved up the hierarchy. For each
piece of code you write, ask yourself whether that code is specific to
the data in this form, or whether it’s generic and other forms could
use it. If it’s generic, move it up the hierarchy.

Conclusion
VFI allows you to visually create new forms based on existing forms,
and provides the same benefits as inheritance in general. This, in
turn, leads to faster development time and more consistent and reli-
able applications.

Understanding how VFI actually works requires a good under-


standing of Delphi’s object model and inheritance in particular.
As you saw, VFI is nothing more than a language mechanism
(inheritance), some IDE features, and an .INI file! I consider it
one of the most powerful features of Delphi, and use it extensive-
ly. Next month, I’ll present a generic database maintenance form
I use in development, and show how this substantially reduces
my own development time. ∆

Rick Spence is Technical Director of Database Programmers Retreat


(http://www.dp-retreat.com), a training and development company with offices in
Florida and the UK. You can reach Rick directly at [email protected].
General inquiries should be directed to [email protected].

22 February 2000 Delphi Informant Magazine


Delphi at Work
Date and Time Issues / Delphi 2-5

By T. Wesley Erickson

Time Travels
Of Time Zones, Daylight Savings, and other Delights

I f your application uses dates and times, what will happen when it’s deployed to
users in different time zones? Have you taken the effects of Daylight Savings Time
into consideration?

We know that many locations do not recognize If all your application needs is the current UTC
Daylight Savings Time; but what about locations or local time, we could simply call the Win32
in the Southern Hemisphere, such as Brazil and API procedures GetSystemTime or
portions of Australia, where its implementation is GetLocalTime. These functions return a data
the opposite of that in the Northern Hemisphere? structure of type SystemTime:
Your particular application may not be affected,
but if you need to convert between local time and type
Universal Coordinated Time (UTC) for any rea- SystemTime = record
wYear: Word;
son, you should consider the effects that time-
wMonth: Word;
zone changes will have on your program. wDayOfWeek: Word;
wDay: Word;
Who cares about changes in time zones? Your wHour: Word;
users might. Consider the relatively trivial exam- wMinute: Word;
wSecond: Word;
ple of a telephone dialer: Wouldn’t it be nice if wMilliseconds: Word;
users were notified of the local time when call- end;
ing a phone number outside of the local calling
area? This would require a lookup table of area
codes to time zones, but it would certainly be a Most of the elements of the SystemTime record are
reasonable enhancement to a dialing program. self-explanatory; wDayOf Week is an unsigned 16-
Some types of programs are vitally concerned bit integer (i.e. Word) value in the range 0-6,
with time-zone changes, particularly technical which identifies the day of the week, correspond-
programs, or those relating to navigation and ing to Sunday through Saturday.
astronomy, to name a few.
The elements of SystemTime can be used to assign
You could ask your users to specify time-zone set- a Delphi DateTime variable:
tings when installing your product, but they
already specified their date, time, and time zone var
when they set up their computers. Besides, mobile ST : SystemTime;
DT : TDateTime;
computing is so pervasive, a user might easily
begin
work in multiple time zones in a single day. It // Get Universal Coordinated Time (UTC).
seems intrusive to ask the user for information ST := GetSystemTime(ST);
already in the registry. At the same time, it’s your with ST do
responsibility to confirm that their settings make DT := EncodeDate(wYear, wMonth, wDay) +
EncodeTime(wHour, wMinute, wSecond,
sense and to offer alternatives if necessary. wMilliseconds);
end;
A computer running Windows 95/98/NT main-
tains its internal time as Universal Coordinated
Time, and displays the local time based on the Note that SysUtils.Now is implemented in exactly
user’s time-zone setting, and the current state of this fashion, using GetLocalTime (SysUtils.Now
Daylight Savings Time. replaces GetCurrentTime, which is obsolete).

23 February 2000 Delphi Informant Magazine


Delphi at Work
Similarly, to determine if Standard or Daylight Time is in effect, we Relative dates are, by far, the more common, and are implement-
can call the Win32 API function GetTimeZoneInformation: ed as shown here:
wYear must be set to zero as a flag.
var wMonth identifies the month in which the change occurs.
Error : Double; wDayOf Week identifies the day of the week (0-6 corresponding
TZInfo : TTimeZoneInformation;
to Sunday-Saturday).
begin
Error := GetTimeZoneInformation(TZInfo); wDay identifies which occurrence of wDayOf Week (1-5
case Error of where 1 is the first occurrence, and 5 means “last
0 : { Unknown } ; wDayOf Week in the month”).
1 : { Standard Time } ;
2 : { Daylight Time } ;
end;
The resulting Relative date is combined with the encoded time from
end; the SystemTime record to specify the exact date and time when the
change occurs.

Time Zone Information Using this information, we can create a function that will tell us
GetTimeZoneInformation also returns a data structure (a record) that whether Daylight time is in effect for a given date and time.
contains the current time-zone settings, and the information needed We’ll assume for this discussion that TZInfo is a global variable
to convert between local and UTC times: of type TTimeZoneInformation that was returned by an earlier
call to GetTimeZoneInformation, perhaps during FormCreate, as
type shown in Listing One (beginning on page 25).
TTIMEZONEINFORMATION = record
Bias: Longint;
StandardName: array[0..31] of WCHAR;
Converting between Local Time and Universal Time
StandardDate: TSystemTime; We now have enough information to convert local times to
StandardBias: Longint; UTC, and vice versa. On Windows NT, we might use
DaylightName: array[0..31] of WCHAR; SystemTimeToTzSpecificLocalTime, which converts UTC to local
DaylightDate: TSystemTime; time in a specific time zone, but this function isn’t available to
DaylightBias: Longint;
end;
users of Windows 95. We’ll continue to assume that TZInfo is a
global variable of type TTimeZoneInformation returned by an ear-
lier call to GetTimeZoneInformation, as shown in Listing Two
Elements of TTimeZoneInformation are shown in Figure 1. (on page 26).

The dates and times represented by StandardDate and DaylightDate are We have one more step to make the program complete. Our pro-
implemented as a set. It’s not permissible to have only one or the other gram can decipher time-zone information and convert local times to
specified; either both dates are specified (meaning Daylight Savings Time UTC and back. How do we handle a situation where the time zone
is implemented), or both are unspecified, in which case wMonth must changes while our program is running? The answer is found in the
be set to zero as a flag. Dates may be stored in either of two formats: Borland FAQ database (FAQ2020D). First, add the following code
Absolute. wYear, wMonth, wDay, wHour, wMinute, wSecond, and to the private declaration section of your application:
wMilliseconds are combined to refer to a specific date and time.
Relative. Also called “day-in-month” format, refers to a particular TIMEZONE.EXE
occurrence of a day of the week, e.g. the last Sunday of the month. TIMEZONE.EXE is an example application designed to demonstrate using Delphi to access
time-zone information, to convert between local time and UTC, and to respond to system-
Bias Difference in minutes between local wide changes in the time-zone setting (see Figure A).
time and UTC, based on the formula
The program starts up initialized to the cur-
UTC = local time + Bias. rent local date and time, and lists the cur-
StandardName Null-terminated string identifying rent time-zone setting (top), as well as the
Standard Time, e.g. Pacific Standard UTC and date that correspond to the local
Time. May be empty, or set to user’s time and date in the edit controls.

preference with SetTimeZoneInformation. To keep the program simple, user inter-


StandardDate Record of type SystemTime that speci- action is limited to two edit controls and
fies the date and time when Standard two buttons. The user may select a date
using the DateTimePicker. The user may
Time begins. enter a time in the Time edit control. If
StandardBias Minutes added to Bias during Standard the string entered by the user fails to
Time (normally zero). convert to a valid time, the exception
handler clears the control and sets the
DaylightName Null-terminated string identifying Figure A: The Time Zone application.
time to midnight.
Daylight Time, e.g. Pacific Daylight Time.
May be empty, or set to user’s prefer- The display is updated when any of the following actions occur:
ence with SetTimeZoneInformation. The user clicks one of the controls.
The user selects a date, and the pop-up calendar closes.
DaylightDate Record of type SystemTime that speci- The user tabs from one field to another.
fies the date and time when Daylight The user presses R after making an entry in the Time edit control.
Time begins.
Two buttons are provided: Now sets the date and time to the computer’s current date and
DaylightBias Minutes added to Bias during Daylight time; Exit terminates the program.
Time (normally 60).
— T. Wesley Erickson
Figure 1: Elements of TTimeZoneInformation.

24 February 2000 Delphi Informant Magazine


Delphi at Work
private Result := False
procedure WMTIMECHANGE(var Message: TWMTIMECHANGE); else // Daylight Time is implemented.
message WM_TIMECHANGE; begin
// If wYear is zero, use relative SystemTime format.
if (TZInfo.StandardDate.wYear = 0) then
Then, add this wmTimeChange procedure to the implementation // Relative SystemTime format.
section: // Calculate DateTime Daylight Time begins using
// relative format. wDay defines which wDayOfWeek
procedure TFormName.wmTimeChange( // is used for time change: wDay of 1 identifies
var Message: TWMTIMECHANGE); // the first occurrence of wDayOfWeek in the month;
begin // 2..4 identify the second through fourth
// Get new Time Zone information. // occurrence. A value of 5 identifies the last
Error := GetTimeZoneInformation(TZInfo); // occurrence in the month.
// Use value returned by GetTimeZoneInformation begin
// for error trapping. // Start at beginning of Daylight month.
case Error of DTBegins :=
0 : { Unknown } ; EncodeDate(Y, TZInfo.DaylightDate.wMonth, 1);
1 : { Standard Time } ; case TZInfo.DaylightDate.wDay of
2 : { Daylight Time } ; 1, 2, 3, 4 :
end; begin
// Update the form using new date/time settings... // Get to first occurrence of wDayOfWeek.
end; // Key point: SysUtils.DayOfWeek is
// unary-based; TZInfo.Daylight.wDay is
// zero-based.
The wmTimeChange procedure can perform whatever action is while (SysUtils.DayOfWeek(DTBegins) - 1) <>
TZInfo.DaylightDate.wDayOfWeek do
necessary to update your application with the newly changed
DTBegins := DTBegins + 1;
time zone. WeekNo := 1;
if TZInfo.DaylightDate.wDay <> 1 then
Conclusion repeat
Hopefully, you will find this little foray into the Win32 API to be DTBegins := DTBegins + 7;
Inc(WeekNo);
time well spent. Your application is now ready for prime time. Feel until WeekNo = TZInfo.DaylightDate.wDay;
free to check out my Time Zone application discussed in the sidebar // Encode time Daylight Time begins.
TIMEZONE.EXE. ∆ with TZInfo.DaylightDate do
DTBegins := DTBegins + EncodeTime(
wHour, wMinute, 0, 0);
The files referenced in this article are available on the Delphi
end;
Informant Magazine Complete Works CD located in 5 :
INFORM\00\JAN\DI200002TE. begin
// Count down from end of month to day of
// week. Recall that we set DTBegins to the
// first day of the month; go to the first
// day of the next month and decrement.
DTBegins := IncMonth(DTBegins, 1);
T. Wesley Erickson is a Fire Suppression Captain with the Oceanside Fire DTBegins := DTBegins - 1;
// Find the last occurrence of
Department in Southern California. A firefighter for over 23 years, Wes holds a BS
// the day of the week.
in Computer Science and a teaching credential. His first involvement with com- while SysUtils.DayOfWeek(DTBegins) - 1 <>
puters was in 1966, participating in a program at the Data Processing TZInfo.DaylightDate.wDayOfWeek do
Installation at MCB Camp Pendleton while he was in high school. Wes learned DTBegins := DTBegins -1;
more than he ever wanted to know about time zones while writing a lunar co- // Encode time Daylight Time begins.
with TZInfo.DaylightDate do
longitude program, named the winner of the 1998 Computing Challenge by the DTBegins := DTBegins + EncodeTime(
Computing Section of the Association of Lunar and Planetary Observers, a world- wHour, wMinute, 0, 0);
wide organization of amateur astronomers. When not computing or star-gazing, end;
he can be found sailing, cycling, enjoying the company of his wife Mary, or play- end; // case.
// Calculate DateTime Standard Time begins using
ing with grandson Jordan. You may contact Wes at [email protected],
// relative format. Start at beginning of
or visit his Web site at http://home.pacbell.net/twerick. // Standard month.
STBegins :=
EncodeDate(Y, TZInfo.StandardDate.wMonth, 1);
case TZInfo.StandardDate.wDay of
1, 2, 3, 4 :
begin
Begin Listing One while (SysUtils.DayOfWeek(STBegins) - 1) <>
TZInfo.StandardDate.wDayOfWeek do
function DaylightSavings(DT: TDateTime): Boolean; STBegins := STBegins + 1;
var WeekNo := 1;
D, M, Y, WeekNo : Word; if TZInfo.StandardDate.wDay <> 1 then
DTBegins, STBegins : TDateTime; repeat
begin STBegins := STBegins + 7;
// Get Year/Month/Day of DateTime passed as parameter. Inc(WeekNo);
DecodeDate(DT,Y,M,D); until (WeekNo = TZInfo.StandardDate.wDay);
// If TZInfo.DaylightDate.wMonth is zero, // Encode time Standard Time begins.
// Daylight Time not implemented. with TZInfo.StandardDate do
if (TZInfo.DaylightDate.wMonth = 0) then STBegins := STBegins + EncodeTime(

25 February 2000 Delphi Informant Magazine


Delphi at Work
wHour, wMinute, 0, 0); end;
end;
5 : function UniversalTimeToLocal(UT: TDateTime): TDateTime;
begin var LT: TDateTime; TZOffset: Integer;
// Count down from end of month to day of begin
// week. Recall we set DTBegins to first LT := UT;
// day of the month; go to the first day of // Determine offset in effect for DateTime UT.
// the next month and decrement. if DaylightSavings(UT) then
STBegins := IncMonth(STBegins, 1); TZOffset := TZInfo.Bias + TZInfo.DaylightBias
STBegins := STBegins - 1; else
// Find last occurrence of day of the week. TZOffset := TZInfo.Bias + TZInfo.StandardBias;
while SysUtils.DayOfWeek(STBegins) - 1 <> // Apply offset.
TZInfo.StandardDate.wDayOfWeek do if (TZOffset > 0) then
STBegins := STBegins -1; // Time zones west of Greenwich.
// Encode time at which Standard Time begins. LT := UT - EncodeTime(TZOffset div 60,
with TZInfo.StandardDate do TZOffset mod 60, 0, 0)
STBegins := STBegins + EncodeTime( else
wHour, wMinute, 0, 0); if (TZOffset = 0) then
end; // Time Zone = Greenwich.
end; // case. LT := UT
end else
else if (TZOffset < 0) then
begin // Absolute SystemTime format. // Time zones east of Greenwich.
with TZInfo.DaylightDate do begin LT := UT + EncodeTime(Abs(TZOffset) div 60,
DTBegins := EncodeDate(wYear, wMonth, wDay) + Abs(TZOffset) mod 60, 0, 0);
EncodeTime(wHour, wMinute, 0, 0); // Return Local Time.
end; Result := LT;
with TZInfo.StandardDate do begin end;
STBegins := EncodeDate(wYear, wMonth, wDay) +
EncodeTime(wHour, wMinute, 0, 0);
end; End Listing Two
end;
// Finally! How does DT compare to DTBegins and
// STBegins?
if (TZInfo.DaylightDate.wMonth <
TZInfo.StandardDate.wMonth) then
// For Northern Hemisphere...
Result := (DT >= DTBegins) and (DT < STBegins)
else
// For Southern Hemisphere...
Result := (DT < STBegins) or (DT >= DTBegins);
end;
end;

End Listing One


Begin Listing Two
function LocalTimeToUniversal(LT: TDateTime): TDateTime;
var UT: TDateTime; TZOffset: Integer;
// Offset in minutes.
begin
// Initialize UT to something,
// so compiler doesn't complain.
UT := LT;
// Determine offset in effect for DateTime LT.
if DaylightSavings(LT) then
TZOffset := TZInfo.Bias + TZInfo.DaylightBias
else
TZOffset := TZInfo.Bias + TZInfo.StandardBias;
// Apply offset.
if (TZOffset > 0) then
// Time zones west of Greenwich.
UT := LT + EncodeTime(TZOffset div 60,
TZOffset mod 60, 0, 0)
else
if (TZOffset = 0) then
// Time Zone = Greenwich.
UT := LT
else
if (TZOffset < 0) then
// Time zones east of Greenwich.
UT := LT - EncodeTime(Abs(TZOffset) div 60,
Abs(TZOffset) mod 60, 0, 0);
// Return Universal Time.
Result := UT;

26 February 2000 Delphi Informant Magazine


On the ’Net
Web Applications / XML / Delphi 4, 5

By Keith Wood

Generating XML
And Delivering It Across the Web

I n previous articles, we were introduced to XML (Extensible Markup Language), and we’ve
seen how to process XML documents in a generic and reusable manner. This article looks
at generating the XML itself, and delivering XML documents across the Internet.

XML as Data
XML documents encode the structure and content
for domain-specific data. Their hierarchy of tags pro-
vides a format that’s easy to manipulate programmat-
ically, while remaining legible to humans. XML pro-
vides a mechanism to transfer data between applica-
tions that is independent of their underlying pro-
gramming language and operating system.

Because XML documents contain data, it seems


natural to want to generate them from existing
data sources, i.e. databases. In general, each
record from a table becomes an element in the
XML document. Within this element appear
sub-elements for each of the record’s fields.
Fields that form the primary key of the record
should be specified as the ID attribute of the
record element. We place an element identifying
each table around these record elements. Around
these, in turn, we have the document element
that corresponds to the database.

Records from dependent tables appear as ele-


ments within their parent record. The linking
fields need not appear as field elements nor as
IDREF attributes, because the position of the
record element within the parent adequately
describes the relationship.

Across the Internet


When we want to generate HTML dynamically
from a Delphi application, we can use the
TWebModule object (created using the Web
Application Wizard), and the TPageProducer
object (from the Internet tab on the Component
palette). TWebModule objects handle the intrica-

27 February 2000 Delphi Informant Magazine


On the ’Net
cies of dealing with the various server protocols (ISAPI, NSAPI, They may optionally have attributes specified in the usual HTML
CGI, Win-CGI). Indeed, they’re set up so we can write a program style. The OnHTMLTag event is triggered for each of these tags
for one protocol, then easily convert it for use with another. found, providing us with the type of the tag, its name, and any
attributes. We respond by using this information to supply the
A TPageProducer object allows us to provide an HTML template text that replaces the tag in the output document. Enhancements
— embedded as text or from an external file — that is output on to the basic PageProducer provide for the generation of HTML
request. Within that template, special tags are intercepted by the tables directly from a query or other data set.
component and presented to us for replacement. These tags are
embedded in angled brackets as usual, and start with a pound This process can just as easily be used to generate XML instead of
sign followed by the name of the tag. For example: HTML. We simply replace the HTML snippets in the PageProducer
objects with XML snippets, using the replacement tag mechanism to
<#movies> substitute the values from the database.

Watching Movies
To help demonstrate the techniques described in this article, we’ll
use a database that contains information about local movies, and
where and when they’re showing. (The example projects for this
article are available for download; see end of article for details.)
The overall structure of the database is shown in Figure 1. For this
demonstration, the tables have been set up as Paradox files.

The Movies table holds the name, rating, length, director, and
synopsis of each film, with the stars appearing in the related Stars
table. Similarly, the Cinemas table holds the name, phone num-
ber, address, directions, and facilities for each theater. Prices for
the different session times are described in the Pricing table
Figure 1: The example database. attached to the Cinemas table. In the Screenings table, a movie
and a cinema are brought together, detailing show dates, and any
restrictions or theater features. The Sessions table holds the actu-
al times for each showing, along with a reference to the pricing
scheme that applies.

Generation
To create the XML dynamically, we start with a new application using
the Web Server Application wizard (see Figure 2). The wizard is
accessed by selecting Web Server Application from the New page of the
New Items dialog box (select File | New from Delphi’s main menu).

We’ll select the CGI option, although there is no reason not to


Figure 2: The Web Server Application wizard.
use one of the other types. Into this Web module we place tables
and data sources corresponding to the
database described previously, link them
appropriately, and activate them (see
Figure 3).

Next, we add several PageProducer com-


ponents: one for the overall document,
and one for each of the tables. Our main
PageProducer contains the XML prolog
and highest-level tags — those corre-
sponding to the database and main tables
(see Figure 4).

Within the PageProducer objects for each


table is an XML document fragment
describing the record structure. The entire
snippet is enclosed in a tag indicating the
type of record. Following this are the indi-
vidual fields and an enclosing tag for any
dependent tables. Using the field names as
the substitution tag names makes the pro-
cessing simpler during replacement. See the
Figure 3: Putting the example application together. fragment for the movie element in Figure 5.

28 February 2000 Delphi Informant Magazine


On the ’Net
The event handler for the main PageProducer’s OnHTMLTag this task within the application, taking the table and
event replaces the contents of the table tags with XML represent- PageProducer as parameters. Event handlers for the PageProducer
ing the records. For each one, it must step through all the records objects associated with each table simply use the tag name to find
in that table and apply the appropriate template from another a field value from the table, or invoke the GetRecords method for
PageProducer, over and over. The GetRecords method performs dependent tables (see Figure 6).

<?xml version="1.0" standalone="yes"?> Formatting for individual fields is handled through the normal
<!--DOCTYPE movie-watcher SYSTEM "/movie-watcher.dtd"--> Delphi mechanisms: the DisplayFormat property, or the OnGetText
<?xml:stylesheet type="text/xsl" href="/mw3.xsl"?>
<movie-watcher>
event on the field itself. Some special formatting is performed using
<movies> the latter (see Figure 7).
<#movies></movies>
<cinemas> The autoincrementing identifiers for each table are made unique by
<#cinemas></cinemas>
prefixing them with a character corresponding to the table name
<screenings>
<#screenings></screenings>
(document-wide uniqueness is required of XML IDs). Some fields
</movie-watcher> are presented as attributes and aren’t included in the document if
their values are blank. Boolean fields are indicated by the presence or
Figure 4: Main document outline for XML. absence of an empty tag. Finally, the memo fields must provide their
actual value rather than their type.
<movie id="<#movie_id>"<#logo_url><#url>>
<name><#name></name> We generate the entire XML document by creating a default Web
<rating><#rating></rating> action for the module. Within this we set the content type to
<length><#length></length> text/xml and invoke the main PageProducer to create the actual con-
<director><#director></director>
<starring>
tent. Finally, we indicate that we’ve supplied the Web response by
<#stars> </starring> setting the Handled parameter to True. All this appears in the
<synopsis><#synopsis></synopsis> demonstration project CGIXML.dpr.
</movie>
Once the application is compiled and placed in our Web server’s
Figure 5: Document fragment for the movie element.
CGI directory, we can call it up and view the results (see Figure 8).
To do this, we’ll need to have Internet Explorer version 5 installed,
// Cycle through all the records in the table
// and generate the XML snippet.
as it’s currently the only browser to support XML. Also, we need to
function TwmdXML.GetRecords(tbl: TTable; pgp: place the HTML style sheet for this document, mw3.xsl, into the
TPageProducer): string; server’s normal document area so it can be retrieved.
begin
Result := '';
with tbl do begin
TRecordPageProducer
First; In the first example, we manually cycle through all the records in
while not EOF do begin
Result := Result + pgp.Content;
Next; // Make id unique.
end; procedure TwmdXML.IDGetText(Sender: TField;
end; var Text: string; DisplayText: Boolean);
end; begin
Text := Copy(Sender.FieldName, 1, 1) + Sender.AsString;
// Generate movie-watcher XML document. end;
procedure TwmdXML.pgpMovieWatcherHTMLTag(Sender: TObject;
Tag: TTag; const TagString: string; TagParams: TStrings; // Include attributes only if present.
var ReplaceText: string); procedure TwmdXML.AttributeGetText(Sender: TField;
begin var Text: string; DisplayText: Boolean);
if TagString = 'movies' then begin
ReplaceText := GetRecords(tblMovie, pgpMovie) if Sender.AsString <> '' then
else if TagString = 'cinemas' then Text := ' ' + ModifyName(Sender.FieldName) +
ReplaceText := GetRecords(tblCinema, pgpCinema) '="' + Sender.AsString + '"';
else if TagString = 'screenings' then end;
ReplaceText := GetRecords(tblScreening, pgpScreening);
end; // Include empty field tag only if flag in DB set.
procedure TwmdXML.EmptyFieldGetText(Sender: TField;
// Add details for a movie. var Text: string; DisplayText: Boolean);
procedure TwmdXML.pgpMovieHTMLTag(Sender: TObject; begin
Tag: TTag; const TagString: string; TagParams: TStrings; if Sender.AsBoolean then
var ReplaceText: string); Text := '<' + ModifyName(Sender.FieldName) + '/>';
begin end;
if TagString = 'stars' then
ReplaceText := GetRecords(tblStars, pgpStars) // Display longer text.
else procedure TwmdXML.MemoGetText(Sender: TField;
ReplaceText := var Text: string; DisplayText: Boolean);
tblMovie.FieldByName(TagString).DisplayText; begin
end; Text := Sender.AsString;
end;
Figure 6: Handling tags for the main document and
movie element. Figure 7: Specialized formatting for various fields.

29 February 2000 Delphi Informant Magazine


On the ’Net
a table to generate the section of the XML document that’s The PageProducer component (from the Component palette’s
derived from it. As we’ve seen, this process was repeated several Internet tab) allows us to generate a section of a document
times on the different tables, all of which are doing basically the from a template. The QueryTableProducer and
same thing. In true Delphi tradition, we now capture that DataSetTableProducer components transform the contents of
process within a component, making it available for future use a query, or any data set respectively, into an HTML table for
with minimum effort. inclusion in a document. What we want is somewhere in
between: to be able to process each record in a data set, but
without the hard-coded HTML output.

To achieve this, we create our own class, TRecordPageProducer,


which generates its section of the document for each record in the
attached data set. It builds on the abilities of TPageProducer in that
the document fragment can be specified as either embedded text
or a file reference, and inherit the substitution operations on fields
within the snippet.

We create the new component and derive it from TPageProducer.


Next, we add the new properties. Obviously, we add one to
refer to the attached data set, DataSet, as well as NoRecsFile
and NoRecsDoc, to allow for the reporting of a lack of data. The
constructor and destructor are overridden to allocate and release
the string list used by the NoRecsDoc property, and the
Notification method is overridden to clear our reference to the
data set if it’s deleted.

So much for housekeeping; now we can add the new function-


ality. Browsing through the code for TPageProducer we find
that all content requests end up going through the
ContentFromStream method. This means that if we override this
one method to cycle through each record, it will work no matter
Figure 8: The formatted document in the browser.
how the content is requested.

In our version of the method, we first check if DataSet exists, is


// Iterate through the records in the dataset. open, and contains data. If so, we reposition the data set to the
function TRecordPageProducer.ContentFromStream( beginning before stepping through each record and applying our
Stream: TStream): string;
template to it (see Figure 9). Here we make use of the functionali-
var
stmNoRecs: TStream; ty of the ancestor to perform the processing of the template
begin through the call to the inherited ContentFromStream. Note that we
Result := ''; must reset the template stream to the beginning each time around
if Assigned(FDataSet) then the loop as it is processed within the inherited method.
if FDataSet.Active then
if FDataSet.RecordCount > 0 then
// Cycle through all the records. To automatically substitute field values for tags with their names,
with FDataSet do begin we must override another inherited method. Some examination
First; reveals the DoTagEvent routine as the one we want. In
while not EOF do begin
TPageProducer, it simply calls the OnHTMLTag event handler if
Stream.Position := 0;
Result := it exists. Instead, we want it to try to match the tag name with a
Result + inherited ContentFromStream(Stream); field name, and only call the event handler if that fails (see
Next; Figure 10). An exception is raised if the data set isn’t active, or if
end; the field doesn’t exist. We trap this and redirect processing to the
Exit;
end;
user event instead.
// No data found.
// Replace field references automatically.
if FNoRecsFile <> '' then
procedure TRecordPageProducer.DoTagEvent(Tag: TTag;
stmNoRecs := TFileStream.Create(FNoRecsFile,
const TagString: string; TagParams: TStrings;
fmOpenRead + fmShareDenyWrite)
var ReplaceText: string);
else
begin
stmNoRecs := TStringStream.Create(FNoRecsDoc.Text);
try
if Assigned(stmNoRecs) then
ReplaceText :=
try
FDataSet.FieldByName(TagString).DisplayText;
Result := inherited ContentFromStream(stmNoRecs);
except
finally
inherited DoTagEvent(Tag, TagString,
stmNoRecs.Free;
TagParams, ReplaceText);
end;
end;
end;
end;

Figure 9: Generating a document snippet for each record. Figure 10: Automatically replacing field references.

30 February 2000 Delphi Informant Magazine


On the ’Net
Using this new component, instead of the basic PageProducer, makes
our code that much simpler. The original application has been updat-
ed in CGIXML2.dpr to demonstrate the new abilities.

Enhancements
The applications presented here demonstrate how to produce an
XML document from an existing database on demand. Recall,
however, that Web modules are able to accept additional parame-
ters from the user. These can be used to further customize the
output, either by providing a subset of the data in the first place,
or by referring to a different style sheet from within the docu-
ment. The latter allows for presenting the same XML document
in different ways, and can include its own selection criteria. In
fact, we could generate a customized style sheet, as well as the
original XML.

Another useful enhancement would be some sort of automatic


generation of the XML document directly from the database.
This could take the form of an expert that allows us to select the
database, tables, and fields required, specify how each field is to
be presented (as an element or an attribute), then create the Web
module for us. Alternately, it could work from the DTD and
match this with the fields in a selected database.

Conclusion
The Internet technologies built into Delphi enable us to quickly
generate server-side applications for processing and delivering
data. We can use these abilities to produce XML documents, as
well as conventional HTML documents. The full functionality of
Delphi can be brought to bear on the problem, allowing us to
access databases and to customize the produced documents.

To make the processing of information from data sources easier,


we created the TRecordPageProducer component that cycles
through each record in its attached data set and applies its
HTML/XML template to each one. ∆

References
XML specification: http://www.w3.org/XML
XML information: http://www.xml.com,
http://www.xmlsoftware.com

The files referenced in this article are available on the Delphi


Informant Magazine Complete Works CD located in
INFORM\00\JAN\DI200002KW.

Keith Wood is an analyst/programmer with CCSC, based in Atlanta. He started


using Borland’s products with Turbo Pascal on a CP/M machine. Often working
with Delphi, he has enjoyed exploring it since it first appeared. You can reach
him via e-mail at [email protected].

31 February 2000 Delphi Informant Magazine


New & Used

By Alan C. Moore, Ph.D.

TSyntaxMemo
The Easy Road to Syntax Highlighting

I f you display source code in your applications (e.g. code editors, experts, or similar
tools), you want that code to look professional. In most cases, you want to duplicate
the syntax highlighting of the application that produced or compiled the code. If you’re
working with Delphi code, you would like your editor/viewer to have the same look as
the editor in Delphi’s IDE. You could go to a lot of trouble parsing the code and using a
RichEdit control to display it. I’ve tried it; it’s a lot of work.

On the other hand, you could try the excellent set essential, so it’s a property of the two memo com-
of components we’ll be examining in this review: ponents; it’s required to enable syntax-highlighting
dbRock Software’s TSyntaxMemo. Although this functionality. In fact, with either TSyntaxMemo or
library consists of only three components, there’s a TDBSyntaxMemo, you can have multiple parsers.
great deal more power here than you might Supporting TSyntaxMemoParser is a powerful
expect. As we’ll see, you can take advantage of the scripting language, and an arsenal of built-in
main features of this library by simply dropping a parsers for most of the common programming
couple of components on a form and setting a few and scripting languages: Pascal (Delphi), C, Java,
properties. Or you can get under the hood and do HTML, etc. Let’s start by taking a detailed look at
some amazing things. We’ll begin with an the main component.
overview of the library, then take a closer look at
its components and their properties. Though TSyntaxMemo isn’t a descendant of
TMemo, you can use it exactly as you would
An Overview Delphi’s TMemo component. A syntax highlighting
The library consists of three components: editor component, TSyntaxMemo implements all
TSyntaxMemo, the main component; properties, methods, events, and messages of
TDBSyntaxMemo, a data-aware version of the TMemo, as well as many new ones. In one version
main component; and TSyntaxMemoParser, a non- of my Sound Component Expert, I concluded the
visual component that provides the parsing func- component-producing expert by displaying an edit
tionality for the other two. TSyntaxMemoParser is window whose main component was a TMemo.
When I learned of this library and had a chance to
try it out, I was delighted with the difference that
substituting a TSyntaxMemo component for a
TMemo component made. Figure 1 shows an even
more impressive use of these components. In this
deceptively named Automatic C to Pascal
Converter, there is C code in the top pane and
Pascal code in the lower pane.

So, how does TSyntaxMemo perform its magic?


TSyntaxMemo depends on one or more
TSyntaxMemoParser components to analyze code
in preparation for syntax highlighting. You can use
up to six different TSyntaxMemoParser compo-
nents with each TSyntaxMemo component. Of
course, each TSyntaxMemoParser component
would define a different source code format to be
highlighted. The selected TSyntaxMemoParser
Figure 1: C and Pascal code displayed in the Automatic C to component is controlled through the ActiveParser
Pascal Converter. property of TSyntaxMemo.

32 February 2000 Delphi Informant Magazine


New & Used
TSyntaxMemo supports OLE-based drag and drop, allowing text your users a great deal of control
from other applications to be easily moved to and from editors cre- over printing, word wrapping,
ated using TSyntaxMemo. Unlimited undo and redo is built in. The word selection, and more.
library also includes support for bookmarks, regular expressions in
search queries, display of glyphs in a customizable gutter, and file There are also over 60 methods.
sizes limited only by available memory. Many you would expect, such as TSyntaxMemo is a powerful set of
syntax-highlighting components for
LoadFromFile, LoadFromStream, Delphi and C++Builder. TSyntaxMemo
TSyntaxMemo sports a powerful arsenal of nearly 70 new properties. I’ll SaveToFile, and SaveToStream. includes full integration with
AddictSpell and supports on-the-fly
discuss some of them in a general way and a few of the more important You can also save and retrieve spell-checking as in recent versions of
ones in more detail. The ActiveParser and various Parser? properties give bookmarks, and manipulate them popular word processors. This feature-
you complete control over which language (and what type of syntax in other ways. The various cate- rich component set supports unlimited
undo/redo, use of Clipboard, and print-
highlighting) you’re currently supporting. LanguageNames provides the gories into which the properties ing of highlighted text. It includes a
names of those languages you’re supporting, so you can display them in fall — navigation, printing, word good Help file and several excellent
a menu. This is demonstrated in one of the example programs we’ll wrapping, and word selection — demonstration programs. Customers
who register for the current version
examine shortly. ClipCopyFormats controls which formats can be copied are also supported by these meth- will be entitled to free upgrades to
to the Clipboard, and CursorTokenText provides the name of the type of ods. There are also methods for future versions of TSyntaxMemo.
format at the cursor position. working with special glyphs and dbRock Software
marks. Finally, some 25 events 26 Kemp Avenue
Users have come to expect certain options in a modern editor. allow you to monitor and respond Paisley PA3 4JS
Scotland
With properties to support word wrap, undo/redo, setting tabs, to these and other kinds of activi- United Kingdom
and bookmarks, TSyntaxMemo provides just about all the editing ty during editing.
capabilities you might want. The Options property we’ll be exam- Phone: 07712 899461
E-Mail: [email protected]
ining provides even more choices in this and other areas. A power- With the data-aware version of this Web Site: http://www.
ful example program, TSMEd, demonstrates most of the library’s component, TDBSyntaxMemo, you dbrocksoftware.com
Price: US$169
features (see Figure 2). The menu item selected in this example can edit or display a database’s
shows clearly the languages supported. BLOB (binary large object) test field
with all the syntax highlighting you need. This component uses the Text
Support for text color, font styles, and background color is essen- property to represent the contents of the BLOB field. As in any memo
tial (it’s included in the three def_? properties), but the control component, it allows multiple lines of text. A descendant of
over the appearance of this control goes much further. With the TSyntaxMemo, TDBSyntaxMemo contains the additional properties —
Gutter, GutterColor, GutterFont, and GutterGlyph properties, you DataField and DataSource — necessary for it to work with a BLOB
can control the width, color, font, and glyphs used in the gutter field in a table.
— if you choose to use the gutter, that is. Likewise, the
LineGlyphs, LineColor, and LineTextColor properties control the The final component, TSyntaxMemoParser, performs the actual work of
glyphs, the background color, and the text color for a single line. analyzing text in a particular language, and determines how that text
SelColor and SelTextColor control the background color and the should be highlighted. It has considerably fewer properties and methods,
text color for selected text. Finally, margin color allows you to and is based on a powerful scripting language that controls the parsing.
get or set the background color for the margin area between the
left-edge gutter and the text area. Although you can use the Compile property only at design time,
you can use the CompileScript method at run time to compile the
In sum, the TSyntaxMemo properties give you a great deal of control parser-defining text in the file specified by the Script property.
over the elements of a code editor — from navigation, streaming Similarly, by assigning to the Script property at design time, the
data, and searching, to special highlighting. One of these properties specified script will be compiled, and the compiled version of the
is particularly rich. The Options property is a set of some 20 choices script will be saved with the application. However, at run time
that control editing and other behaviors. The options give you and you need to call the CompileScript method to explicitly compile a
script. UseRegistry must be True or RegisterKey will have no effect.
Parsers rely on scripts, because it’s these text files that define the
key elements of a language that may require special formatting
for displaying or printing.

Parser Scripts
TSyntaxMemoParser uses scripts to define the analysis and appearance of
text in a TSyntaxMemo control. This library includes a number of built-
in scripts for popular languages and a good deal of information to help
you build your own. With these scripts, you can define most aspects of
the editing environment, such as TabColumns, Gutter settings, the prop-
erty editor display, and auto-replace entries. These scripts are written in
a standard way, with specific sections that carry out certain functions.
The dbRock Software Web site includes an excellent example of tech-
niques for defining special syntax circumstances. Learning this scripting
language isn’t trivial, particularly if you want to accomplish complex
tasks. Once you’ve mastered it, however, you can extend the capabilities
Figure 2: The languages supported in the example program TSMEd. of these components considerably.

33 February 2000 Delphi Informant Magazine


New & Used
Conclusion
I’m most impressed with this library. It’s clearly the best solution for
any developer who needs to add syntax highlighting functionality to an
application — either in a browser or a code editor. TSyntaxMemo
comes with full source code, a very good Help system, and excellent
support. One of the features I almost forgot to mention is the excellent
built-in property editor. Though the price is a bit high, it’s worth
remembering that you will never need to pay for an upgrade — they’re
free. Visit the dbRock Software Web site. If nothing else, the down-
loadable demonstrations highlight the awesome power of Delphi, and
the talent of the excellent developers we have in our midst. I recom-
mend these components highly and without reservation. ∆

The files referenced in this article are available on the Delphi


Informant Magazine Complete Works CD located in
INFORM\00\JAN\DI200002AM.

Alan Moore is a Professor of Music at Kentucky State University, specializing in music


composition and music theory. He has been developing education-related applica-
tions with the Borland languages for more than 10 years. He has published a num-
ber of articles in various technical journals. Using Delphi, he specializes in writing
custom components and implementing multimedia capabilities in applications, par-
ticularly sound and music. You can reach Alan on the Internet at [email protected].

34 February 2000 Delphi Informant Magazine


File | New
Directions / Commentary

Delphi 5: A Portent?

B y now most of you have read numerous reviews and articles about Delphi 5. Many of you have already upgraded.

5
Rather than simply go over that territory again, I would like to take a different approach and try to answer these
questions: How is the release of Delphi 5 different from that of Delphi 4? And what does this indicate about changes in
Inprise’s approach?

Delphi is Inprise/Borland’s flagship product. One indi-


Delphi capabilities. Breakpoints, always important in debugging, are more
cation of this is that the Delphi sessions have been the powerful than ever: You can now organize them into groups,
best attended at recent Inprise conferences. In the first enabling or disabling them at will. You can even associate various
Delphi session, the presenter asked, “When should we actions with your breakpoints. There is a new floating-point
release Delphi 5?” As you can probably imagine, many debugging window, as well as several new debugging com-
in the audience shouted back, “Now!” But the presen- mands and options.
ter was ready for that and came back with “Wrong answer! We’ll
release it when it’s ready.” Although it may seem the obvious answer, Of course there are many new database features, new and
it does represent a shift in philosophy, a return to the emphasis on improved components, and even some new tools in the
quality that made this company great in the first place. It represents a Enterprise edition. The database enhancements have
shift of strategy for the company compared to the rushed release of been covered extensively in this magazine (in particular,
Delphi 4. The strategy embodies a rediscovery of one of the cardinal see Dr Cary Jensen’s “Delphi 5,” also in the August, 1999
rules of software production: Better late than lousy. Delphi Informant Magazine). One of the frequently men-
tioned new features is frames — similar to compound
In reviewing the new features, I’ll begin with one that is sel- components in many ways, but more flexible and dynamic. I
dom mentioned: the Help files. Because I have been work- was immediately intrigued with how these frames might com-
ing on a book on Delphi and multimedia, the expand- pare with the so-called “features” I wrote about in my review of
ed Help in this and other Microsoft API areas Nevrona Designs’ RAD tool, Propel (see the July, 1998 of Delphi
caught my attention early on. The new Help is more Informant Magazine). I learned that although frames are certainly
extensive and better organized. If you want to get a more powerful than compound components, they are not nearly as
good idea of the other improvements, check out the help topic, powerful as Propel’s features, which are more flexible. With features
“What’s new in Delphi.” I’ll outline some of the highlights. you can manipulate (delete, move, add to) the contained components
without changing the main feature, and you can encapsulate the
An IDE overhaul. The enhancements to Delphi 5 fall into various behavior of the original components in the composite feature. The
categories, including the IDE, debugger, database functionality, and president of Nevrona Designs has informed me that he intends to
VCL. The most impressive changes are to the IDE. In fact, I can’t release the latest incarnation of Propel, ND-Patterns, as a free tool,
recall any version of Delphi that matches this explosion of new capa- charging only for the source code and technical support. Visit their
bilities. Based on the reaction I saw to these features at the confer- Web site at http://www.nevrona.com for more details.
ence, and since, it certainly seems that Inprise is working to keep
Delphi the best development environment on the market. Let’s Besides this wonderful assortment of new features and improve-
examine some of the details. ments, I am equally impressed with what isn’t in Delphi 5: The bugs
that plagued the release of Delphi 4. Of course, there are bugs —
All versions of Delphi 5 allow you to customize and load different there are always bugs. But the number and severity seems insignifi-
desktop layouts (including debugging desktops), and they feature an cant, especially when compared to the last release. This is a direct
Object Inspector with a new look that includes images in drop-down result of Inprise obeying the better-late-than-lousy rule.
lists and categories of properties. Now you can even modify the edi-
tor’s properties by customizing its key mappings. There was a session I agree with my colleagues who’ve praised this release of Delphi. It’s
on this latter capability at the Inprise conference, the first time I an outstanding development tool! Further, I predict that if Inprise
remember a Borland engineer delving this deeply into Delphi’s inner continues to exercise such attention to quality and detail, the future
workings. The Project Manager includes a number of new features will be bright for Delphi and Inprise.
that will remind CodeRush users of certain plug-ins under version 4.
Among many others, the file-management capabilities now include — Alan C. Moore, Ph.D.
drag-and-drop copying of files from a Windows folder into a project.
To-Do Lists, which allow you to keep track of project-related tasks, Alan Moore is a Professor of Music at Kentucky State University, special-
are available in the Professional and Enterprise editions. izing in music composition and music theory. He has been developing
education-related applications with the Borland languages for more than
Better support for debugging. Another area that includes major 10 years. He has published a number of articles in various technical jour-
improvements is the debugger (see Robert Vivrette’s article, “Delphi 5 nals. Using Delphi, he specializes in writing custom components and
Drill-Down,” in the August, 1999 Delphi Informant Magazine for implementing multimedia capabilities in applications, particularly sound
further details). The debugging windows now include drag-and-drop and music. You can reach Alan on the Internet at [email protected].

35 February 2000 Delphi Informant Magazine

You might also like