Delphi Informant Magazine (1995-2001)
Delphi Informant Magazine (1995-2001)
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.
Excel Software
Price: US$495 for a single license; site
licenses are available.
Phone: (515) 752-5359
Web Site: http://www.excelsoftware.com
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.
-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
TTask1
TTask2 TTask3
+ExecuteTask
+ExecuteTask +ExecuteTask
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.
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.
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. ∆
By Dennis P. Butler
CORBA
Part I: Creating a Server
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-
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.
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;
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.
implementation
By Jeremy Merrill
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.
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;
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. ∆
By Rick Spence
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:
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.
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.
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.
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).
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.
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.
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).
<?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.
Figure 9: Generating a document snippet for each record. Figure 10: Automatically replacing field references.
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.
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.
References
XML specification: http://www.w3.org/XML
XML information: http://www.xml.com,
http://www.xmlsoftware.com
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.
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.
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?