Delphi Informant 95 2001
Delphi Informant 95 2001
Serendipity!
Delphi 2.0 Makes Its Debut
ON THE COVER
7 Delphi 2.0 — Richard Wagner 34 The Way of Delphi — Gary Entsminger
It’s official — Delphi 2.0 has all the power and functionality of Last month, Mr Entsminger recounted great journeys and single steps
the 16-bit version, with astounding new features. Sporting new in object-oriented development, and discussed two technologies:
UI controls, database enhancements, visual form inheritance, a Dynamic Data Exchange (DDE) and Object Linking and Embedding
revved-up IDE, and much much more, Delphi 2.0 is ready for (OLE). This month, he continues the discussion and introduces OLE
launch. Mr Wagner introduces the new tool and even provides capabilities you can build into your Windows 95 applications.
us with a taste of its multithreading capability.
REVIEWS
FEATURES 42 HyperTerp Pro — Product review by Robert Vivrette
13 Informant Spotlight — Jeff Chant There may be times when a user needs to add or modify the function-
Delphi’s Outline component is the principal control for presenting ality of a delivered Delphi application. But how can you provide this to
hierarchically-related data in your Windows applications. If you’ve the user without making the source code available? Implement a
never used this powerful control, or are just now learning to use it, scripting language, such as HyperTerp Pro, says Mr Vivrette.
then this article is for you. Mr Chant takes us through the intricacies
of TOutline with an interactive example. 44 WISE Installation System — Product review by Micah Bleecher
Roughly broken down, a Windows application development cycle
22 OP Tech — Bill Todd involves designing, building, testing, deploying, and finally,
Error handling is serious business. So serious, in fact, that this installing. If you’re in the market for a capable installation utility,
month Mr Todd begins a two-part series on handling errors in Mr Bleecher strongly recommends the WISE Installation System.
Delphi applications. He covers the use of the Create, Free, and
Dispose methods to help with allocating resources and cleanup, DEPARTMENTS
discusses trapping for RTL exceptions, and will help you under- 2 Editorial
stand the RTL exception hierarchy. 3 Delphi Tools
5 Newsline
27 DBNavigator — Cary Jensen, Ph.D.
This month, Dr Jensen completes his two-part series on filtering
Paradox data in Delphi applications. First, he compares the use
of Table and Query components to filter data, and then shows
you that the Query component — combined with a bit of SQL
— is an effective tool to get the data you’re after.
isual programming without walls. Delphi 1.0 — and only Delphi 1.0 — makes it possible for the
V Windows 3.x environment. Now Delphi 2.0 offers it for Windows 95 development.
Just as it did the first time I supported in Windows 95). Test PowerBuilder Visual Basic 3.0 Delphi 1.0 Delphi 2.0
fired it up, Delphi continues It’s also important to note that (loops/sec.) 3.0 (16-bit) (16-bit) (16-bit) (32-bit)
to dazzle me with its myriad Windows 95 does not support Sieve 0.22 11.95 52.77 179.37
qualities: speed, power, ele- VBXes, thus Delphi 2.0 does Whetstone 0.04 1.41 4.70 15.53
gance, ease-of-use, extensibili- not support them. This could File write 0.05 0.42 0.74 2.89
ty, sound design, and — be painful if you’re relying on File read 0.05 0.33 1.75 5.28
something else. Let’s call it a VBX that hasn’t made the
extravagance. Yes, Delphi 2.0’s metamorphosis into an OCX, Delphi 2.0 also sports a greatly And speaking of the
feature set is nothing short of although most have. enhanced compiler that deliv- Informant Communications
extravagant. How else can you ers executables even faster than Group Web site — at
refer to a product which — There are other changes that those of Delphi 1.0 (see table). www.informant.com, natch
just for starters — offers over are integral with a move to Moreover, the compiler now — don’t forget to cast your
90 ready-made components? Windows 95. As Mr Wagner shares the same back-end as ballot for the First Annual
pointed out in October, there Borland’s C++ product, mak- Delphi Informant Reader’s
With no real head-to-head com- is no more 64KB limit, and ing it possible, for example, to Choice Awards. You can also
petition for Delphi 1.0, and a Integer and Cardinal values are compile .OBJ files directly have your say on our
less-than-hurried acceptance of now 32-bit. Also, Borland has into your Delphi applications. CompuServe forum (GO
Windows 95 in the business been careful to comply with all In short, Delphi 2.0 is an ICGFORUM), or by just
world, Borland decided to Windows 95 UI standards. impressive upgrade to an mailing in the Official Ballot
spend a little extra time with already awesome product, and you’ll find later in this issue.
Delphi 2.0 and add even more And okay, despite all the nice a testament to the solid foun- And about that ballot. It
features. The result is impres- things I said earlier, Delphi dation upon which it is built. contains two farshtunken
sive, and is described by DI 1.0 — or more properly, errors: 1) the results will
Contributing Editor, Richard Object Pascal 1.0 — does Moving to another topic, your appear in the April issue, and
Wagner, beginning on page 7. have one annoying character- response to our new Web site 2) the voting deadline is
istic: its meshugeh, 255-char- has been overwhelmingly posi- February 20, 1996. The win-
But this isn’t Richard’s first acter string type. But that’s tive. Here’s a sample: ners will be announced, for
look at Delphi 2.0. He initially been taken care of with the first time, in late March
previewed it for us in October Delphi 2.0. You can have Dear Editor, at Software Development 96
of last year. This month’s offer- strings as long as you like. Thanks for the web site! I’m West at Moscone Center in
ing is really an update that Better yet, they behave as an avid reader of Delphi San Francisco.
describes the changes made Pascal strings and as C (null- Informant and very glad
since then. In case you missed terminated) strings, which will you’ve added a new dimen- Thanks for reading,
that issue, let me give you a sure make those API and DLL sion to your publishing. Now
quick run down. calls easier. Besides the greatly I’m able to locate files ...
enhanced string type, Object spotlighted in the monthly
All that is necessary to turn a Pascal 2.0 offers two entirely articles. You don't know how Jerry Coffey, Editor-in-Chief
Delphi 1.0 application into a new data types: the variant useful that can be.
fully-functional, 32-bit, type can store a string, integer, Internet: [email protected]
Windows 95 application, is to or floating-point value and is Keep up the good work, CompuServe: 70304,3633
recompile it under Delphi 2.0 particularly useful for OLE and the web site. Fax: 916-686-8497
(unless, of course, you make automation; and the Snail: 10519 E. Stockton
an exotic API call, i.e. one that ANSIChar type has been Your loyal reader, Blvd., Ste. 142,
has changed or is no longer added to support Unicode. Wai Chong Elk Grove, CA 95624
ISBN: 1-883577-72-1
Price: US$44.99,
Stylus Announces Telephony Support For Delphi
Canada $62.99
(1000 pages, CD-ROM)
Stylus Innovation, Inc., of VBX controls and a graphical transferring and conferencing.
Phone: (800) 410-0192 Cambridge, MA has announced workbench for creating tele- Included in the Visual Voice
the release of its telephony phony and fax applications toolkit is the Voice Workbench,
toolkit, Visual Voice for Delphi, that include interactive voice a graphical tool to define the
which allows Delphi developers response (e.g. touch-tone telephony components of an
to add telephony features to banking), fax-on-demand, and application and generate the
their applications. voice mail. Typical business corresponding application logic.
Visual Voice is a suite of systems built with Visual Voice The Workbench is used to cre-
include 24-hour customer ate and maintain all telephony
order services, benefits enroll- objects, including voice
ment hotlines, and brochure prompts, menus, and files.
fax-on-demand systems. Stylus’ 32-bit OCX version of
Visual Voice also features Visual Voice is currently in beta.
modules for text-to-speech and
voice recognition support. In Price: Visual Voice for Windows 3.x
addition, it provides interfaces begins at US$495. There are no run-time
to the following functions: fees required.
answering inbound calls, plac-
ing outbound calls, prompting Contact: Stylus Innovation, Inc., One
for touch-tone input when calls Kendall Square, Building 300, Cambridge,
are established, playing and MA 02139
recording voice files, sending Phone: (617) 621-9545
and receiving faxes, and inte- Fax: (617) 621-7862
grating with a PBX to perform E-Mail: Internet: [email protected]
call control functions such as Web Site: http://www.stylus.com
By Richard Wagner
Delphi 2.0
The Serendipitous Tool Makes Its 32-Bit Debut
erendipity. This is the word that came to mind when I first saw the latest
S field test of Delphi 2.0. The enhancements from earlier field tests were
both wonderful and unexpected. If you recall, in the October 1995 Delphi
Informant, we previewed Delphi 2.0 during its initial beta release. At that time,
it already sported some amazing enhancements over the 16-bit version of the
product, including: a 32-bit optimizing compiler, OCX support, Windows 95 UI
components, multithreading support, a new 32-bit database engine, new data
types, closer coupling with C++, and an enhanced IDE.
The Delphi 2.0 R&D team was obviously not content with the status quo. In subsequent
builds, there has been a steady influx of major new features that, when combined, make Delphi
2.0 one of the most extensive version upgrades I’ve ever seen. This article will focus on many of
the new features that have been added to the 32-bit product since our October survey.
New UI Controls
In addition to the eight
Windows 95 UI components
we discussed in October,
there are three new ones:
TListView, THotKey, and
TStatusBar. First, the
TListView control will enable
you to use one of the funda-
mental Win95 user interface
elements in your applica-
tions. Anywhere you go in
Windows 95 — such as My Figure 1: Windows Control Panel uses a ListView to present
Computer, Control Panel, or options to the user.
Windows Explorer — you
work with a ListView control (see Figure 1). It’s an intuitive way of displaying list infor-
mation in a variety of styles (Large Icon, Small Icon, List, or Details).
Using a TListView component, you can use this method of presentation in your Delphi appli-
cations as well. For example, Figure 2 shows a sample application called Resource Explorer
that ships with the current Delphi 2.0 build. Using TTreeView and TListView components, it
effectively presents resource data in a metaphor similar to the Windows Explorer. Given the
Diving inside the code, we must first separate the two queries
into different routines that we’ll call Query1Thread and
Figure 2: This sample Delphi application employs TreeView and
ListView components.
Query2Thread. The first query is executed when
Query1Thread is called:
success of the Windows 95 user interface, you’ll probably
want to incorporate TreeView and ListView components into function Query1Thread(parms: pointer) : LongInt; far;
begin
your application UIs. Form1.LongQuery.Open;
end;
The TStatusBar component provides a simple way for
developers to give their applications a standard Windows Then, the second query is executed when Query2Thread is
95 status bar, while the THotKey component allows the called:
user to quickly perform a specified action using a combina-
tion of keys (such as CC). All the Windows 95 UI com- function Query2Thread(parms: pointer) : LongInt; far;
begin
ponents available with Delphi 2.0 are briefly described in Form1.ShortQuery.Open;
the sidebar on page 12. end;
Using Delphi 2.0, you can create threaded applications. It procedure TForm1.Button2Click(Sender: TObject);
var
will be important for developers to resist the urge of going ThreadID : dWord;
“thread crazy” and create multiple threads in even the tiniest begin
of applications. With this caveat in mind, when multi- T3 := createThread(nil,0,@Query1Thread,nil,0,threadID);
setThreadPriority(T3,FirstTrackBar.Position);
threading is used wisely, it can be extremely powerful, and is T4 := createThread(nil,0,@Query2Thread,nil,0,threadID);
perhaps the most convincing factor for developing Win32 setThreadPriority(T4,SecondTrackBar.Position);
applications instead of creating programs for the Windows end;
3.1 16-bit environment.
Many articles on multithreading (especially on how to best
In our October preview, we had a high-level discussion on employ it) are surely in the works. But even when running
Delphi 2.0’s multithreading support. In this installment, let’s this rudimentary demonstration, you can visibly see the
take a closer, interactive look at multithreading by building a results of what threading can do for you.
basic two-thread application. (Note that documentation for
multithreading was minimal at press time.) Database Enhancements
Delphi 2.0 now provides several new enhancements that data-
Suppose you want to execute two queries simultaneously in an base application developers will definitely appreciate. These
application, but would like to prioritize the queries. To do include cached updates, TField lookup fields, table filters, and
this, you could place the queries into separate threads. Figure 3 two enhanced data controls.
shows the UI for this example. The priority of the two threads
is dependent on the position of the TrackBars shown on the Cached Updates. An important part of any client/server
left of the form, and the results of the queries are shown in the application is how the client works with the SQL server to
two TDBGrid controls. process data. In Delphi 1.0, every client data event is individ-
ually sent to the database as a separate SQL transaction. This Delphi 2.0 makes filtering much easier by adding filter support
process is known as navigational updating. to the basic TTable component. By setting the TTable’s Filtered
property to True, each record is evaluated when it’s retrieved by
Delphi 2.0 continues to support navigational updates, but adds the data set. You can configure a filter to “block out” all records
a new transaction mechanism called cached updates. Rather that do not match your criteria by placing the data filter in the
than sending each data update as a separate SQL transaction, OnFilterRecord event handler for the TTable object.
cached updates gather multiple client transactions and send
them in a batch to the server within a single SQL transaction. For example, suppose you want to filter out all records in a
Customer table for people not living in Massachusetts. To do
Cached updates have several uses, but can be particularly effec- this, you would write the following filter:
tive in situations where network traffic and record contentions
are major concerns. However, keep in mind that both naviga- procedure TForm1.CustomerTblFilterRecord(
DataSet: TDataSet; var Accept: Boolean);
tional and cached update models have advantages and disad- begin
vantages. Therefore, the developer is responsible for employing Accept := DataSet['State'] <> 'MA';
the proper transaction method within a given context. Not end;
Figure 4:
The New Field
dialog box.
Figure 7:
New and
improved
TDBGrid
Figure 5: supports
A lookup combination
field is boxes and
shown in other data
the controls.
enhanced
TDBGrid
component.
The TDBCtrlGrid is a powerful new component for display- updated. Additionally, if you change the event handler for the
ing data. Like TDBGrid, TDBCtrlGrid allows you to display Find Next button on the StdDataForm, the associated handler
multiple records of a table, but in a more flexible manner. in GridViewForm and RecGridForm will be updated as well.
Not only can you display any data aware control in it, but
you can also specify how many rows and columns of records The importance of visual form inheritance is clear for object-
to display. If you have ever used Paradox for Windows, you oriented programmers. In Delphi 2.0, you can extend the
will find that the TDBCtrlGrid is essentially the equivalent of OOP boundary to now include forms, as well as data mod-
Paradox’s multi-record object. Figure 8 shows an example of a ules (which we’ll discuss below).
TDBCtrlGrid.
Data Modules and Visual Form Linking. When developing
multi-form database applications in Delphi 1.0, I find the form-
Figure 8:
specific nature of data access components to be frustrating.
The new Because of this limitation, if you use a table across 16 different
DBCtrlGrid forms, you must place a TTable component on each of the 16
component forms if you linked that table to a UI object. Additionally, if you
gives you need to synchronize the data across multiple forms, you are
greater
flexibility in
forced to write the code to perform the synchronization.
presenting
multiple Thankfully, Delphi 2.0 added data modules to combat this
records of unnecessary chore. A data module serves as a container for non-
data. visual database components (see Figure 10). Using a data mod-
ule, you can manage your database access code from a central-
ized facility. You can also keep all your database logic in this sin-
gle location rather than spread throughout the application.
New IDE Features A data module is form-like, so you can work with data mod-
The development environment of Delphi 2.0 continues to ules quite similarly as you do with Delphi forms. However, a
mature with the addition of visual form inheritance, data data module does not have a UI and thus will never appear to
modules, visual form linking, and an Object Repository. the end user of your application.
Visual Form Inheritance. One of the biggest shortfalls of Delphi Having a centralized data house is possible through visual
1.0 was the inability to truly subclass TForm objects. Delphi 2.0 form linking. With this functionality, you can link a data
answers this deficiency with visual form inheritance. This func- component on one form to a data aware component on
tionality allows you to create descendant forms from an original another form or data module. Delphi 2.0 links forms and
base form, all without writing any code. Unlike Delphi 1.0’s ver- data modules by linking their associated unit files. Therefore,
sion of “form templates” (that were really just copies of your once you declare a form’s unit in the uses clause of another
favorite forms), Delphi 2.0 allows you to create true “abstract” form, you can access any of its data components. Figure 11
forms within and across projects. When you update an ancestor shows the dot notation reference in the Object Inspector.
form, all its descendants are updated at the same time. You can
even view this simultaneous update on screen. Object Repository. Delphi 2.0 uses the Object Repository as
an organizing tool to manage form objects, data modules,
To illustrate, we’ll look at a demonstration application called project experts, and project templates. The Object Repository
Gdsdemo. It’s an example of presenting the same data using is really an enhanced version of Delphi 1.0’s Gallery. After
both a TDBGrid and a TDBCtrlGrid. However, it also shows adding an object to this facility, you can inherit, reference, or
the power of inheritance. copy the object in future applications. You’ll notice the con-
tents of the Object Repository are displayed when you select
Gdsdemo uses a base form called GDSStdForm (see Figure 9) the File | New from Delphi’s menu (see Figure 12). In multi-
that has the company logo header used by two forms. A developer environments, you can maintain a single network-
descendant form of GDSStdForm, called StdDataForm (also based Object Repository and reference it from every develop-
shown in Figure 9), adds filtering controls onto the base form. er workstation.
Neither of these forms are ever seen by the user — they are Noteworthy Changes
simply abstract objects being used by their children: There are many miscellaneous changes that are noteworthy
GridViewForm and RecGridForm (again, see Figure 9). Both of (including, but not limited to):
these forms inherit the properties, events, and methods from • The File | Use Unit command adds the select unit to the
their two ancestor forms. Therefore, if you change the bitmap uses clause of the current unit file (or associated unit file
of GDSStdForm, all other forms in the application would be of the active form).
➤
➤
Figure 9: Delphi 2.0 features visual form inheritance. In this example, the form StdDataForm (second from the top) inherits all the proper-
ties and behavior of GDSStdForm. In turn, GridViewForm and RecGridForm (at the bottom of the figure) inherit from StdDataForm.
By Jeff Chant
DBOutline
Using TOutline to Manage Hierarchical Data
Most developers will appreciate the value of this feature when confronting a table that refers
to itself for a parent record. Such self-referencing tables are numerous in the business world:
employee files referring to themselves for a supervisor; address masters referring to themselves
for a parent address; and business segment tables referring to themselves for a parent segment.
These recursive relationships are often many levels deep, and the deeper the nesting becomes
the more difficult it is for end-users to manage them with normal grid- or form-based inter-
faces. Solving this problem demands a tool capable of editing hierarchically-related data. The
TOutline component — a hierarchy-oriented tool — fulfills this requirement perfectly.
Producing the sample application is a multi- Figure 1: This application provides a hierar-
stage process. First, make a self-referencing chic view of a self-referencing employee
table. The user can manipulate the hierarchy
table. Next, create the form and add the compo- (i.e. the contents of the Supervisor field)
nents. Third, construct an algorithm to popu- using drag-and-drop.
late the TOutline component with information
from the self-referencing table. Once the algorithm is complete, implement drag-and-
drop procedures to allow the manipulation of the outline nodes. Finally, establish a sim-
ple procedure to keep the current record of the TTable synchronized with the currently
selected TOutlineNode.
Prior to Calculations
The information shown in the sample application includes
the last and first names of the employees. Rather than explic-
itly populating the outline with the string values of both the
LastName and FirstName fields, we’ll use a single calculated
field generated by the Fields editor.
Figure 5:
Sequence of
procedural
calls used to
populate
the outline.
It will probably take several iterations of the table before Within ProcessChildRecord is the escape clause to the repeat-
all records are successfully added to the outline. An ing loop — if the previous loop added no new records to
employee record cannot be added to the outline if its par- the outline (i.e. Boolean variable, IterateWithoutAdd, is
ent has not yet been added. This can happen if the True), the current loop will add any remaining records as
employee’s parent record is the child of another record, children of node Index 0, indicating the parent records
and the employee’s parent record appears after the employ- could not be found. Orphaned records will never occur if
ee record within the indexed table. If the inner while not the self-referencing referential integrity name was added to
the Paradox table as instructed earlier. The code is added This problem is solved by using the OnMouseMove event to
simply as a safety precaution. For instance, if the applica- trigger BeginDrag. Code can be added to the MouseMove
tion is modified to access a table with no recursive relation event handler that will note, if the outline is currently drag-
defined (or a table unable to accept or enforce one), the ging, whether the mouse pointer has moved beyond the
handling of orphaned records will prevent the algorithm outline’s borders. If so, the outline will be scrolled in the
from entering an endless loop. appropriate direction. However, the OnMouseMove event
shouldn’t actually cause the scrolling, or scrolling will stop
One More Loop when the mouse ceases moving. Instead, the OnMouseMove
As a final step, AddNodeText loops once more through the event should enable the TTimer component, and TTimer’s
table, locating the TOutlineNode that corresponds to each OnTimer event will handle the scrolling. The completed
record, and replacing the node’s text: MouseMove handler is shown in Figure 9.
(EmpNo) with EmpNo + ' ' + DisplayField The X and Y coordinates of the mouse pointer are com-
pared to the TOutline top and left coordinates (0) and
This step can’t be performed as the nodes are added because of height to determine if the TOutline should be scrolled and,
the way the GetTextItem method operates. The text of the node if so, in which direction. The Boolean variables ScrollUp,
must exactly match the text being searched to find a match. ScrollDown, ScrollLeft, and ScrollRight have their values set
depending on the scrolling direction. These variables
If nodes were added with: instruct the TTimer component after it is enabled. Note
that TTimer is disabled if the outline should not be
EmpNo + ' ' + DisplayField scrolled. This stops TTimer from continuing to scroll after
the mouse has been moved back within the borders of the
then before seeking a node corresponding to the Supervisor TOutline.
field of the current record, the record pointer would have to
be moved to the record whose EmpNo field contained the The OnTimer event of TTimer evaluates the current dragging
value of Supervisor. Here, the DisplayField value would have state and the Boolean variables (ScrollUp, ScrollDown,
to be retrieved for use in the search. This would create at least ScrollLeft, and ScrollRight), and it sends the appropriate mes-
one, and usually several, SetBookMark/FindKey/GotoBookMark sage to the Outline control’s window. If TOutline is not in a
combinations for each child record — a much greater perfor- dragging state, TTimer is disabled. This ensures that the timer
mance hit than one loop through the table. is stopped if the left button is released while the mouse is still
outside the boundaries of the TOutline (see Figure 10).
Drag and Drop
With the TOutline populated, the task remains to enable WM_VSCROLL and WM_HSCROLL are the messages
drag-and-drop of the TOutlineNodes. To accomplish this, use sent to a window when its vertical and horizontal scroll
with Outline1 do that the source of the OnDragOver event is the TOutline con-
begin trol and, if so, accept a drop if it occurs.
if not Dragging then
begin
if (Shift = [ssLeft]) then The OnDragDrop event must trigger the move of the dragged
BeginDrag(False); node to be a child of the dropped-on node (moving all its
end children with it). In addition, the OnDragDrop event must
else
begin update the dragged node’s corresponding record in Table1 to
if (Y < 0) or (Y > Height) or reflect the new Supervisor. This is accomplished in four steps:
(X < 0) or (X > Height) then 1) Extract the EmpNo values from the dragged outline node
begin
ScrollUp := False; and the dropped-on outline node.
ScrollDown := False; 2) Move the TTable record pointer to the dragged outline
ScrollLeft := False; node’s employee number using TTable’s FindKey
ScrollRight := False;
if Y < 0 then method.
ScrollUp := True 3) Call TTable’s Edit method, move the dropped-on outline
else if Y > Height then node’s employee number into the record’s Supervisor field,
ScrollDown := True;
if X < 0 then and call TTable’s Post method.
ScrollLeft := True 4) Call TOutline’s MoveTo method to move the dragged node
else if X > Height then and its children to the dropped-on node.
ScrollRight :=True;
Timer1.Enabled := True;
end The handler checks to ensure that a parent is not being dragged
else to one of its own children before accepting the drop. This pre-
Timer1.Enabled := False;
end;
vents the creation of a cyclical reference (i.e. employee 102 is
end; the supervisor of employee 54, and employee 54 is the supervi-
sor of employee 102). If this check were not put in place, and a
procedure TForm1.Timer1Timer(Sender: TObject);
cyclical reference was created, the node would not actually
begin move until the outline was cleared and reloaded. During
with Outline1 do reload, the records would be added as orphaned records.
begin
if Dragging then
begin Notice that, if the dragged node is dropped on the master
if ScrollUp then parent Employees, the extracted EmpNo value is blank.
SendMessage(Handle, WM_VSCROLL, SB_LINEUP,
SB_THUMBTRACK)
Hence the purpose of the master parent — drop a node on it
else if ScrollDown then and the node becomes a top-level parent.
SendMessage(Handle, WM_VSCROLL, SB_LINEDOWN,
SB_THUMBTRACK);
if ScrollLeft then
Keeping the Table Synchronized
SendMessage(Handle, WM_HSCROLL, SB_LINEUP, To keep the contents of the TDBEdit fields synchronized with
SB_THUMBTRACK) the currently selected node on the outline, the record pointer
else if ScrollRight then
SendMessage(Handle, WM_HSCROLL, SB_LINEDOWN,
of the TTable must be moved whenever the currently selected
SB_THUMBTRACK); node changes. This is best accomplished through the OnClick
end event of the outline. The key field value is extracted from the
else
Timer1.Enabled := False;
outline node, and the FindKey method of Table1 is called to
end; reposition the record pointer.
end;
To complement record synchronization, there are lines
Figure 9 (Top): The completed MouseMove handler.
throughout the code that hide/show the ScrollBox (thereby hid-
Figure 10 (Bottom): Setting boundaries with the Timer procedure.
ing/showing the TDBEdit fields grouped within), depending
bars (respectively) are clicked. SB_LINEUP and on whether the EmpNo extracted from the currently selected
SB_LINEDOWN indicate the direction of scroll (with UP TOutlineNode is found in the table. The scrollbox is hidden if
as left and DOWN as right on the horizontal bar). the record cannot be found. This should only occur when the
SB_THUMBTRACK indicates that the scroll box should master node Employees is selected or, in a multi-user environ-
move to the corresponding position. When many records ment, if the record was deleted and the DataSet refreshed.
with extensive scrolling are involved, it might be worth-
while to expand the evaluation of mouse movements to Conclusion
include SB_PAGEUP and SB_PAGEDOWN messages if TOutline organizes layers of hierarchical data with ease — that’s
the mouse moves above 0 - 30, or below Height + 30. its power. With a simple algorithm, the outline can be loaded
from a self-referencing database — opening the door to drag-
Once dragging and scrolling are functional, the DragOver and-drop manipulation of the data's hierarchy. Self-referencing
handler must be created. All this handler must do is check tables are by no means the only relation type where TOutline can
var
Jeff Chant is the owner of Maelstrom Software, an Ontario-based company specializing Form1: TForm1;
in the design and construction of Delphi tools and applications. He is also a SYNON ReIterate, IterateWithoutAdd,
architect, working with large warehouse management systems on the AS/400. He can ChildAdded, NodeDrag, ScrollUp,
ScrollDown, ScrollLeft,
be reached on CompuServe at 71431,62. ScrollRight: Boolean;
const
Begin Listing One — Unit1
{ Index to sort the table by }
unit Unit1;
SortIndexName = 'ByName';
{ Field to display after SeekField }
interface
DisplayField = 'DisplayField';
{ The key field of the self-reference }
uses
SeekField = 'EmpNo';
SysUtils, WinTypes, WinProcs, Messages, Classes,
{ The field referencing the key field }
Graphics, Controls, StdCtrls, Forms, DBCtrls, DB,
RecursiveField = 'Supervisor';
DBTables, Grids, Outline, Buttons, Mask, ExtCtrls,
Dialogs;
implementation
type
{$R *.DFM}
TForm1 = class(TForm)
ScrollBox: TScrollBox;
procedure TForm1.FormCreate(Sender: TObject);
Label1: TLabel;
begin
EditEmpNo: TDBEdit;
Table1.Open;
Label2: TLabel;
end;
EditLastName: TDBEdit;
Label3: TLabel;
procedure TForm1.Table1CalcFields(DataSet: TDataset);
EditFirstName: TDBEdit;
begin
Label4: TLabel;
Table1DisplayField.AsString :=
EditPhoneExt: TDBEdit;
Table1LastName.AsString + ', ' +
Label5: TLabel;
Table1FirstName.AsString;
EditHireDate: TDBEdit;
end;
Label6: TLabel;
EditSalary: TDBEdit;
procedure TForm1.SpeedButton1Click(Sender: TObject);
Label7: TLabel;
begin
EditSupervisor: TDBEdit;
{ Hide the scrollbox and its TDBEdit fields }
Panel1: TPanel;
ScrollBox.Visible := False;
DataSource1: TDataSource;
with Table1 do
Panel2: TPanel;
begin
Table1: TTable;
Screen.Cursor := crHourglass;
SpeedButton1: TSpeedButton;
try
Outline1: TOutline;
Form1.ParentLoad;
Table1EmpNo: TIntegerField;
Form1.ChildLoad;
Table1LastName: TStringField;
Form1.AddNodeText;
Table1FirstName: TStringField;
finally
Table1PhoneExt: TStringField;
Screen.Cursor := crDefault;
Table1HireDate: TDateTimeField;
end;
Table1Salary: TFloatField;
end;
Table1Supervisor: TIntegerField;
end;
Table1DisplayField: TStringField;
Timer1: TTimer;
procedure TForm1.ParentLoad;
procedure FormCreate(Sender: TObject);
begin
procedure Table1CalcFields(DataSet: TDataset);
with Table1 do
procedure SpeedButton1Click(Sender: TObject);
begin
procedure Outline1MouseMove(Sender: TObject;
Outline1.Clear;
Shift: TShiftState;
{ Add a Master Parent for all valid employees }
X, Y: Integer);
Outline1.Add(0, 'Employees');
procedure Outline1DragOver(Sender, Source: TObject;
{ Index the table for outline display order }
X, Y: Integer;
IndexName := SortIndexName;
State: TDragState;
First;
var Accept: Boolean);
while not eof do
procedure Outline1DragDrop(Sender, Source: TObject;
begin
X, Y: Integer);
procedure Timer1Timer(Sender: TObject);
{ If the record is top-level (has no parent) }
procedure Outline1Click(Sender: TObject);
if FieldByName(RecursiveField).AsInteger = 0 then
private
{ Add the record as a child of the Master { Indicate that a reiteration will be
Parent created above (index=1) } necessary }
Outline1.AddChild(1, ReIterate := True;
FieldByName(SeekField).AsString); { If this is the second iteration through the
Next; file with no new added child nodes, add the
end; record as an orphan }
{ Set the index back to the primary key } if IterateWithoutAdd = True then
IndexFieldNames := SeekField; begin
First; Outline1.AddChild(0,FieldByName
end; (SeekField).AsString);
end; end;
end
procedure TForm1.ChildLoad;
begin else
ReIterate := True; { If node representing parent record does exist,
IterateWithoutAdd := False; add the record as a child }
begin
{ While there are still child records that have not ChildAdded := True;
been added to a parent } Outline1.AddChild(NodeIndex,FieldByName
while ReIterate do (SeekField).AsString);
begin end;
ReIterate := False; end;
ChildAdded := False; end;
while not Table1.eof do
begin; procedure TForm1.AddNodeText;
Form1.ChildReIterate; var
Table1.Next; NodeIndex: integer;
end; begin
Table1.First; with Table1 do
begin
{ If an iteration through the table was performed, First;
and there were record(s) not yet added to the while not eof do
outline, but no records were added to the outline begin
throughout the iteration } NodeIndex := Outline1.GetTextItem(FieldByName
if ReIterate and (SeekField).AsString);
not ChildAdded then if NodeIndex <> 0 then
IterateWithoutAdd := True; Outline1[NodeIndex].Text :=
end; FieldByName(SeekField).AsString + ' ' +
end; FieldByName(DisplayField).AsString;
Next;
procedure TForm1.ChildReIterate; end;
var end;
NodeIndex: integer; end;
begin
with Table1 do procedure TForm1.Outline1MouseMove(Sender: TObject;
begin Shift: TShiftState;
{ If record has parent (i.e. Supervisor not 0) } X, Y: Integer);
if FieldByName(RecursiveField).AsInteger <> 0 then begin
begin with Outline1 do
NodeIndex := Outline1.GetTextItem(FieldByName begin
(SeekField).AsString); if not Dragging then
begin
{ If record was not added to the outline in a if (Shift = [ssLeft]) then
previous iteration (can't be found in outline) } BeginDrag(False);
end
if NodeIndex = 0 then else
Form1.ProcessChildRecord; begin
end; { If the mouse has been dragged outside the
end; TOutline component }
end; if (Y < 0) or (Y > Height) or
(X < 0) or (X > Height) then
procedure TForm1.ProcessChildRecord; begin
var ScrollUp := False;
NodeIndex: integer; ScrollDown := False;
begin ScrollLeft := False;
with Table1 do ScrollRight := False;
begin if Y < 0 then
NodeIndex := Outline1.GetTextItem ScrollUp := True
(FieldByName(RecursiveField).AsString); else if Y > Height then
ScrollDown := True;
{ If node representing parent record does if X < 0 then
not exist in the outline } ScrollLeft := True
if NodeIndex = 0 then else if X > Height then
begin ScrollRight :=True;
By Bill Todd
In this two-part series, you’ll learn how to handle exceptions, ensure that cleanup code is
always executed, and create custom exceptions and exception handlers.
To handle cleanup chores, Delphi provides the try..finally construct. The easiest way to understand
it is to examine the code from the sample project, CLEAN.DPR (its form is shown in Figure 1).
The form features Table, DataSource, and DBGrid components. The Table component is connect-
ed to the Customer table in the DBDEMOS database. It also contains two Buttons that let you
change the active index from CustNo (the primary index) to ByCompany (the secondary index).
This OnClick event handler switches the indexes:
In addition, it’s critical that code executes when resources Figure 3: The code from the Find button’s OnClick handler.
are allocated and you must ensure they are released.
Memory, files, and Windows resources must be recovered Here’s the important issue: Whenever you create a new instance
if an error occurs. In an object-oriented environment, this of an object by calling its constructor, you are allocating memo-
is best illustrated by creating an instance of an object in ry. Therefore, you are responsible for deallocating that memory
your code. by calling the object’s destructor method. Otherwise, memory
will not be available until you restart Windows.
Figure 2 shows the form from the sample project
ALLOC.DPR. After a company name is entered, the form To ensure that CustTbl’s Free method is called, all the code
searches the customer table and reports if a match was found. in this procedure (following the call to TTable’s Create
The Find button’s OnClick event handler declares a variable method) is enclosed in a try..finally block. The call to the
named CustTbl of type TTable that searches the customer destructor, Free, follows the finally keyword so that it will
table (see Figure 3). However, CustTbl must first be created execute even if an exception is raised by any of the code
by calling its constructor method, Create. between try and finally. (Note that try.. finally blocks can
be nested to any depth.)
Exception Description
EDivByZero An attempt to divide by zero.
ERangeError The number or expression result is beyond
Figure 7: the range of the integer type.
The INTERR EIntOverflow A mathematical operation caused an integer
project’s overflow.
main form.
Exception Description
EInvalidOp The processor encountered an invalid instruc-
tion. This usually means the processor is trying
to execute data due to a pointer error.
EZeroDivide Attempt to divide by zero.
EOverflow A floating point operation overflowed.
procedure TForm1.MathErrorClick(Sender: TObject);
var EUnderflow A floating point operation underflowed.
i,j,k,l,m: Integer;
begin Exception Description
i := 23; EFault The base exception object for all faults.
j := 0;
l := 2; EGPFault General protection fault. The most common
m := 4; cause of GPFs is an uninitialized pointer.
EStackFault Illegal access to the stack segment.
try
k := i div j * (l div m); EPageFault The Windows memory manager could not
except access the swap file.
on EIntError do k := 0; EInvalidOpCode The processor encountered an undefined
end; instruction. This is usually caused by trying
to execute data.
{ Display the result. }
Result.Caption := IntToStr(k); EBreakpoint The program generated a breakpoint interrupt.
end; ESingleStep The program generated a single step interrupt.
Figure 8: The OnClick handler for the MathErrorHandled button. Figure 9 (Top): Integer math exceptions. Figure 10 (Middle): Float-
ing point math exceptions. Figure 11 (Bottom): Hardware exceptions.
handle it because the calculation is enclosed in a try..except • EIntError is a generic integer math exception. You can
block. The except code sets the result to zero and that’s the test for it, or for the specific integer math errors shown
end of the exception. Clearly, this is much easier than han- in Figure 9.
dling the possibility of division by zero without try..except. • For floating point math operations, the generic exception is
To detect if j or m is zero would require: EMathError. The individual floating point errors are listed
in Figure 10.
if (j = 0) or (m = 0) then
• The generic hardware exception is EProcessorException and
k := 0
else its specific descendants are listed in Figure 11.
k := i div j * (l div m);
With the exception of EGPFault, you should never encounter
If the computation were more complex, testing for all possible or need to worry about any of the hardware exceptions. They
integer math errors requires a lot of code. Note that when you only occur if a serious hardware or operating system failure
run a program in the IDE, it runs under the control of the happens or if you are running under a debugger. The
Interactive Debugger. You will always see its exception dialog box remaining four categories of exception do not have a
whether your code handles the exception or not. To view what generic exception. You must test for each specific exception
the user will see, run the application from Program Manager. that you want to handle:
• For input/output errors, there is a single exception,
When an exception is raised in a try..except block, Delphi EInOutError. It has a field named ErrorCode containing the
checks if it’s listed in the except section. If so, then the code operating system error code for the error that occurred.
for that exception is executed. In this example, if any integer • EInvalidCast is a single typecast exception that occurs any-
math exception occurs then the value of k is set to zero. time you attempt a typecast using the AS operator and the
typecast fails.
Understanding the RTL Exception Hierarchy • All conversion exceptions raise the EConvertError excep-
Before continuing, you must understand how the seven tion. For example, if you call StrToInt and the string can-
classes of RTL exceptions are organized. Three of the cate- not be converted, then EConvertError is raised.
gories — integer math, floating point math, and hardware • Two heap exceptions can occur when using dynamic
exceptions — have a hierarchy: memory. They are listed in Figure 12.
Exception Description If you look at the except clause, you’ll notice that it contains
checks for two exceptions, EDivByZero and EIntError. In this
EOutOfMemory An attempt to allocate memory on the heap case, a specific message is displayed for EDivByZero and
failed.
another message for all other integer math exceptions. The
EInvalidPointer An attempt was made to dispose of a pointer
that points to an address outside of the heap. order of the tests in this code is critical. If you first test for
EIntError, you will never see the EDivByZero message. This is
procedure TForm1.MathErrorWithMessageClick(Sender: because EIntError includes all integer math exceptions includ-
TObject); ing EDivByZero. This construct lets you handle as many
var exceptions as needed in a single try..except block.
i,j,k,l,m: Integer;
begin
i := 23; Conclusion
j := 0; This article has presented the basics of Delphi's exception
l := 2;
m := 4; handling mechanism. You have seen how to handle resource
try allocations to ensure that the resources are freed if a run-time
k := i div j * (l div m); error occurs. You have also seen how to handle exceptions
except
on EDivByZero do begin raised by run-time library procedures. The second part of this
k := 0; series explores using the exception object, silent exceptions,
MessageDlg('Divide by zero error',mtError,[mbOK],0); writing your own custom exception handler, and more. ∆
end;
on EIntError do begin
k := 0; This article was adapted from material from Delphi: A Developer’s
MessageDlg('Integer math error.',mtError,[mbOK],0); Guide by Bill Todd and Vince Kellen [M&T Books, 1995 —
end;
end; 800-488-5233].
{ Display the result. }
Result.Caption := IntToStr(k); The demonstration projects referenced in this article are avail-
end;
able on the Delphi Informant Works CD located in
Figure 12 (Top): Heap exceptions. Figure 13 (Bottom): The OnClick INFORM\96\FEB\DI9602BT.
event handler for the MathErrorWithMessage button.
Handling Multiple Exceptions Bill Todd is President of The Database Group, Inc., a Phoenix area consulting and devel-
The third button on the INTERR.DPR form, opment company. He is co-author of Delphi: A Developer’s Guide [M&T Books, 1995],
MathErrorWithMessage, shows how to handle multiple Creating Paradox for Windows Applications [New Riders Publishing, 1994], and
exceptions in a single try..except block (see Figure 13). Paradox for Windows Power Programming; Technical Editor of Paradox Informant; a
member of Team Borland; and a speaker at every Borland database conference. He can
be reached at (602) 802-0178, or on CompuServe at 71333,2146.
First, we’ll compare Table and Query components for selecting subsets of records from a table.
Then, we’ll continue with three basic techniques for selecting records using queries. The rea-
sons for displaying a subset of records from a table were discussed in last month’s article, so
they won’t be repeated here.
The techniques in this article use Query components, and therefore involve SQL (Structured
Query Language). While SQL itself is not necessarily difficult to use, there are many complex
issues involved that are far beyond the scope of this article. Therefore, we’ll only cover the
issues that are relevant to filtering tables.
A second major difference is that Query components do not have range-related methods
(such as SetRange, ApplyRange, and so on). Instead, they have a SQL property that you use
to define SQL statements. For example, to filter a table, a SELECT statement is used. This
is SQL DML (Data Manipulation Language) statement, and is used to define which data
will be returned as an answer set.
The second is the DatabaseName property that you must set ed. For example, the following statement will select all fields
to either a BDE alias or a subdirectory path. DatabaseName from each record where the CustNo field is equal to 1221:
defines the location of the tables being queried. There is,
however, one situation where DatabaseName is not required. SELECT * FROM CUSTOMER
WHERE CustNo = 1221
If you include the alias name in the Query’s SQL statement,
DatabaseName can remain blank.
Non-numeric comparison values must be enclosed in quotes.
For example:
When selecting subsets of records, you’ll use the SQL SELECT
statement. It has two required parts or clauses: SELECT and SELECT StateName FROM STATES
FROM. In SELECT, you specify the fields to include in the WHERE StateCode = 'AZ'
answer set that is returned, and you use the FROM statement
to specify the tables from which the fields are selected. Selecting Also, any comparison operators, including >, <, >=, and so forth,
subsets of records requires one additional clause, WHERE, for can be used as a comparison operator in a WHERE clause.
identifying the appropriate records. There are additional clauses
that you can use with the SELECT statement, but they are A WHERE clause can also include multiple conditions, using
outside the scope of this article. (For information on these the AND and OR operators. For instance, assuming the
other clauses, select Help | Topic Search from Delphi’s menu DatabaseName property is set to DBDEMOS, the following query
and enter SQL Statements in the Search All dialog box.) will select all fields from the ORDERS table where CustNo is
1221 and the SaleDate is greater than 1/1/94:
All the queries in this article will be single-table queries, since
Delphi 1.0 only supports editing of single-table queries. Let’s SELECT * FROM ORDERS
WHERE CustNo = 1221 AND
consider the SELECT statement. SELECT is followed by a SaleDate > '1/1/94'
comma-separated list of field names that you want to include
in the answer set. The FROM clause includes a comma-sepa- Using these basic rules, it’s now possible to demonstrate three
rated list of the tables where these fields are found. For exam- ways of selecting subsets of records using Query components.
ple, the following SQL statement will select the CustNo and
Company fields from the CUSTOMER.DB table: Linked Queries
SELECT CustNo, Company FROM CUSTOMER The easiest, although least flexible, way to select a subset of
records with a query is to use a linked query. In a linked
The above example assumes that the DatabaseName property query, the subset of records is defined by values in another
has been set to DBDEMOS, an alias that points to the location DataSource, such as a Table. The WHERE condition(s) use
of the CUSTOMER.DB table. (Delphi creates this alias dur- one or more fields in the DataSource’s DataSet to select spe-
ing installation. If you do not have this alias already estab- cific records to display.
lished, you must create it before trying these examples.)
To do this, you set the Query component’s DataSource prop-
Alternatively, you can include the alias in the table name. For erty to the name of the DataSource that contains the field(s)
example, the following SQL statement does not require the used in the Query’s WHERE clause. Then, within the
DatabaseName property to be assigned a value: WHERE clause, you include the field name(s) from the
DataSource’s DataSet in comparisons. The only trick to this is
SELECT CustNo, Company FROM ':DBDEMOS:CUSTOMER.DB' that the field names must be preceded by colons (:) so the
Query can distinguish them from static conditions.
If the DatabaseName property has been set to DBDEMOS, the fol-
lowing SQL statement has the same effect as the preceding one: This technique is difficult to describe, but easy to demon-
strate with an example. Follow these instructions to create a
SELECT CustNo, Company FROM CUSTOMER linked query:
1) Create a new project. On the new form, add two Label
To select all fields from the specified table, you can replace components, two DBEdit components, a DBGrid, two
the individual field names with an asterisk. For example, DataSources, a Button, a DBNavigator, a Table, and a
the following statement selects all fields from the CUS- Query component. Your form should resemble Figure 1.
TOMER.DB table (again, assuming the DatabaseName 2) Set Form1’s Caption property to Linked Form Example,
property has been set to DBDEMOS): and its Position property to poScreenCenter.
SELECT * FROM CUSTOMER 3) Set the Caption property for Label1 to Company: and the
Caption property for Label2 to Customer Number:. Next, set
While the SELECT clause specifies which fields (or columns in the DataSet property of DataSource1 to Table1, and the
SQL vernacular) will be included in the answer set, the DataSet property of DataSource2 to Query1. Set the
WHERE clause specifies which records (or rows) will be includ- DatabaseName property of Table1 to DBDEMOS, and the
Figure 2: A
Figure 1: SQL SELECT
A new statement
form for for a para-
the meterized
LINKQRY query.
project.
This code is associated with the OnClick event handler for the
Show Orders button on Form1.
Figure 4: The
Query component’s First, the code closes the Query component on Form2 (in
Params property case it’s already open — you cannot change the parame-
editor.
ters of an open query). Next, the CustNumber parameter is
assigned a value using the ParamByName method. The
value assigned to this parameter is based on the current
record of Table1.
The ParamByName method allows you to set a parameter’s
value based on the parameter’s name. For example, this state- Next, the query is opened, which executes the SELECT state-
ment uses the ParamByName method, and is equivalent to the ment. The Caption property of Form2 is then assigned an
preceding one that uses the Params property: appropriate title. Finally, the ShowModal method is used to
Query1.ParamByName('CustNumber').AsInteger := 1221;
display Form2. The result is that the DBGrid on Form2 dis-
plays only those records associated with the Customer that
Figure 5 is the main form of the PARAM.DPR project that the user has selected on Form1.
demonstrates parameterized queries. This form contains a
DBGrid, two Buttons, a DataSource, and a Query. The Table This example is somewhat more complicated since the Query,
is associated with the CUSTOMER.DB table in the directory whose properties are being modified, appears on another form.
that the DBDEMOS alias points to. Remember, to refer to Form2 and its objects from Form1, the unit
associated with Form2 (in this case, it’s ParamU2) must be listed in
a uses clause in the unit associated with Form1 (ParamU in this
example). The ParamU unit is shown in Listing Two on page 32.
Listing Three on page 32 lists all the code for ParamU2.
Figure 5:
Form1 of the
project Changing the SQL Property at Run Time
PARAM.DPR. Parameterized queries are great when the fields you need to use
to select a subset of records are known. However, to create a
query where you sometimes select all records (i.e. when there is
no WHERE clause), and sometimes select a subset based on one
or more parameters, a parameterized query is not an option.
Figure 6 is the second form in this project. It contains a Likewise, if the table being queried is not known until run
DBGrid, a Button, a DataSource, and a Query. This Query’s time, a parameterized query cannot be used because a para-
SQL property is associated with the parameterized SQL state- meter cannot appear in the FROM clause. Instead, you must
ment shown earlier. modify the contents of the SQL property at run time. This
permits you to create flexible queries.
Here is the critical code in this example:
Since the SQL property is a StringList property, it can be mod-
procedure TForm1.Button2Click(Sender: TObject); ified using the methods of the TStringList class. Among the
begin most useful methods are Add and Clear. Add inserts a new line
Form2.Query1.Close;
Form2.Query1.ParamByName('CustNumber').AsInteger :=
into a StringList in the last position, whereas Clear empties a
Table1.FieldByName('CustNo').AsInteger; StringList. The following is an example of how these two state-
Form2.Query1.Open; ments can be used to define a new SQL statement at run time:
Form2.Caption := 'Orders for ' +
Table1.FieldByName('Company').AsString;
Query1.Close; { Close the query if it’s open }
Form2.ShowModal;
end; Query1.SQL.Clear; { Remove old SQL statements }
Query1.SQL.Add('SELECT * FROM ORDERS');
Query1.SQL.Add(' WHERE CustNo = 1211');
Query1.SQL.Open; { Open the new query }
Cary Jensen is President of Jensen Data Systems, Inc., a Houston-based database devel-
Figure 9: opment company. He is author of more than a dozen books, and is Contributing Editor of
An answer Paradox Informant and Delphi Informant. Cary is this year’s Chairperson of the Paradox
set based Advisory Board for the upcoming Borland Developers Conference. He has a Ph.D. in
on the cri- Human Factors Psychology, specializing in human-computer interaction. You can reach
teria shown Jensen Data Systems at (713) 359-3311, or through CompuServe at 76307,1533.
in Figure 8.
end. implementation
End Listing Two
{$R *.DFM}
TheQuery.Free;
except
end;
result := False;
end;
end;
end; procedure TForm1.CloseButtonClick(Sender: TObject);
begin
procedure TForm1.BuildSQLString(var TheQuery: TStringList); Close;
begin end;
if BeginDate.Text <> '' then
if not IsDate(BeginDate) then end.
begin End Listing Four
BeginDate.SetFocus;
raise Exception.Create('Date value expected'); Begin Listing Five — The Sqlu2 Unit
end; unit Sqlu2;
try implementation
BuildSQLString(TheQuery);
{ Query is done. Process it } {$R *.DFM}
Form2.SQLStatements.Lines := TheQuery;
Form2.ShowModal; end.
finally End Listing Six
By Gary Entsminger
Creating an
OLE Object Manager
State of the Object Art: Part II
Don’t you hate it when someone breaks into an article before it even gets rolling? I do. But I can’t help myself. I
must warn you that the project we develop in this article behaves a bit differently on systems running
Windows 3.1 than those running Windows 95. And worse yet. I’m not sure why. So… if you’re a
Windows 95 user, you’ll experience all the thrills and chills of our project. If you’re using Windows 3.1,
compile the project and let me know what happens.
Let’s say that you want to create an application manager program that runs a set of applications
to display a corresponding set of data files on a single screen or page of text. Imagine a scenario
where you could:
• obtain the values of your favorite stocks from an Internet provider
• view the data in a table
• manipulate the data and display results with a Delphi model application
• store the results in a second Paradox table
Each time a user selects File | New Object Window from the applica-
tion’s menu, a child window is created to contain a new object.
Alternatively, users can drop an object onto OLEFrameForm and
automatically create a new child window containing the dropped
object. Additionally, OLEFrameForm has event procedures for cas-
cading and tiling child windows and arranging child window icons. Figure 2: Our main form, OLEFrameForm, at
design time.
Note that any child window will use OLEFrameForm for basic
window management. Therefore, a child window must reference
OLEFrameForm in its unit’s implementation section to avoid a dragged object. Alternatively, in two steps, the user can create
circular reference. This is necessary because OLEFrameForm uses a child window and paste an object from the Clipboard into a
the child’s unit, OLEOBJ.PAS, in the interface section of its unit. child window.
You can use the Objman application to launch sets of applica- First, we’ll create the user interfaces of both forms, and then
tions and documents, as a model for a more complex File we’ll write the “glue” code. To begin, create a new project by
Explorer-type application, or as a file or application organizer. selecting File | New Project from Delphi’s menu. Delphi will
But before we get into the project details, let’s briefly review a automatically create a form and its corresponding unit. Then
few central aspects of OLE. select File | Save Project As and save the form’s unit as OLE-
FRAME.PAS. This will be the main form. Save the project as
OLE Servers, Containers, and Objects OBJMAN.DPR.
Basically, OLE is a way for applications to share data. It differs
from DDE (Dynamic Data Exchange) by allowing the “glue” In the Object Inspector, change the form’s FormStyle property to
application to connect to the OLE server application that creat- fsMDIForm. This establishes the form as an MDI “parent.” Now
ed the data. [For more information on DDE, see Gary change the form’s Caption property to OLE Object Manager,
Entsminger’s article “Great Journeys, Single Steps” in the and change its Name property to OLEFrameForm. Figure 2 shows
January 1996 Delphi Informant.] our form in design view.
In DDE, you simply receive a copy of data, but not the applica- From the Standard page of the Component Palette, select the
tion that created it. Additionally, you cannot edit the original MainMenu component and add it to the form. Double-click on
data. In OLE, you get both — the data and the application that the MainMenu component to display the Menu Designer. Add a
created it. Therefore, you can use the application to manipulate menu item (&File), and a sub-menu item (&New Object Window)
the data. as shown in Figure 3.
The application that creates an OLE object is called the OLE Figure 3:
Use the
server and you use it to edit the object. For example, you can use Menu
Microsoft Excel as an OLE server to edit spreadsheets. Likewise, Designer to
the Borland Database Desktop can be used as an OLE server to add a menu
edit tables. item
(&File) and
a sub-menu
In short, in an OLE interaction, one application acts as the serv- item (&New
er and another as an OLE container that contains the OLE Object
objects. The OLE object is the data — or a representation of the Window).
data — that the OLE server creates and maintains (e.g. a spread-
sheet, table, .TXT file, .DOC file, etc.).
Merging MDI Menus
In the Objman project, you’ll use Delphi to create an OLE contain- MDI applications always merge the menus of child windows
er application that can interact with many OLE server applications. with the main menu of the parent MDI window (i.e. form).
However, you can control how this merging occurs.
Creating the Project
As mentioned, Objman uses two forms (an MDI container All menu items have a GroupIndex property that is used to speci-
and an OLE object container) to allow users to add new MDI fy whether menu items are inserted or replaced in a menu. By
children that contain OLE objects. Each window can contain default, all items in a menu have the same GroupIndex value, but
one OLE object. In one step, the user can drag an object onto you can explicitly change these values according to a group of
the main form to create an MDI child and a link to the menu merging rules.
For instance, any menu item with a GroupIndex property of 1, open, edit, or convert the object within the OLE container.
3, or 5 will be replaced by the menu items with correspond- Although you always add this menu item when using OLE
ing index values in the MDI child (the OLE server). Merging containers, you never write event code for it. OLE handles it.
the menus of the OLE server and its container is called in-
place activation. To enable each child to merge its File1 menu items with
OLEFrameForm’s File1 menu, set the GroupIndex property of the
Note that in non-MDI applications, File1 menu item to 1 (to match the OLEFrameForm’s File1 menu
the main menu’s AutoMerge property item). If you’re in an experimental mood, try leaving either the
determines if the menus (TMainMenu) main menu’s File1 GroupIndex property or the child’s set to 0.
of forms other than the main form Either way the second File1 menu item is added to the first, not
merge with the principal form’s main replaced.
menu in non-MDI applications at run
time. Figure 5 shows OLEObjectForm at design time.
In the Object Selector, select the &Object menu item and set Now, declare an array of type BOLEFormat with one element
its Enabled property to False. The Edit | Object command for each object format to be processed. For example, to allow
remains dormant until the OLE container holds an object. dragging and dropping of linked objects only, declare a one-
When the container has an object, Edit | Object becomes active element array. Likewise, to allow dragging and dropping of
and allows the user to access the object. The user can then linked and embedded objects declare a two-element array.
Control then passes to the new child window when it’s displayed The TOLEObjectForm.Tile1Click event procedure instructs
on screen (see the CreateMDIChild procedure). The complete OLEFrameForm to tile the child windows:
code for OLEFRAME.PAS is shown in Listing Seven beginning
on page 40. procedure TOLEObjectForm.Tile1Click(Sender: TObject);
begin
OLEFrameForm.Tile
OLEOBJ.PAS end;
Next, let’s proceed to the code for the child form,
OLEObjectForm. Since OLEFrameForm is the MDI manager, At this point, the TOLEObjectForm.ArrangeIcons1Click event
menu items on child windows must ask OLEFrameForm to procedure informs OLEFrameForm to arrange any minimized
handle window management when the user requests it. child window icons:
Conclusion
In the next installment, we’ll continue our OLE discussion as we
implement additional functionality in our OLE Object Manager.
For example, to allow a user to selectively close child windows, a
parent window needs to keep track of its children (in an array, for
example), then match a user’s selection from a menu (or equivalent)
to the corresponding child window. Deleting a child is equivalent to
Figure 8 (Top): The TOLEObjectForm.PasteObject1Click event proce- deleting an object. In Objman2, we’ll get to that. See you then. ∆
dure. Figure 9 (Middle): Here, Objman is shown with Microsoft
Word in a child window. Figure 10 (Bottom): In this running exam- The demonstration project referenced in this article is available on
ple of Objman, the user is entering information into a table with the
Database Desktop.
the Delphi Informant Delphi Informant Works CD located in
INFORM\96\FEB\DI9602GE.
Gary Entsminger is the author of The Way of Delphi [Prentice-Hall, 1996], The Tao of
Objects [M&T Books, 1995], Secrets of the Visual Basic Masters [Sams, 1994], and
Developing Paradox Databases [M&T Books, 1993].
unit OLEFrame;
public
{ Description: An OLE main MDIform to contain MDI child { Public registration info to drag-and-drop
windows. Use this form to launch and manage MDI child a single-element array to allow linking only }
In the project, OBJMAN.DPR, in this article, OLEFRAME.PAS { Public created MDI child that can be
launches MDI child windows described in OLEOBJ.PAS. accessed from child forms }
function CreateMDIChild: TOLEObjectForm;
the objects.
var
The OLEFrame is the main form for the OLE Object Manager. OLEFrameForm: TOLEFrameForm;
Each time a user selects New Object Window from its main
menu, it creates a child window to contain a new object. implementation
This form is an MDI window. It contains event procedures procedure TOLEFrameForm.FormCreate(Sender: TObject);
{ This is the child unit for this project }; { Create a new MDI child }
type Result.Show;
MainMenu1: TMainMenu;
File1: TMenuItem; procedure TOLEFrameForm.Exit1Click(Sender: TObject);
procedure TOLEObjectForm.ArrangeIcons1Click(
Sender: TObject);
begin
OLEFrameForm.ArrangeIcons
end;
procedure TOLEObjectForm.PasteObject1Click(
Sender: TObject);
var
ClipFmt: Word;
DataHand: THandle;
Info: Pointer;
begin
if PasteSpecialEnabled(Self, OLEFrameForm.Fmts) then
end.
End Listing Eight
HyperTerp Pro
HyperAct’s Scripting Language for Delphi
yperTerp Pro is, simply put, a scripting lan- Many of us are familiar with DOS batch files. These are actu-
Documentation
My one key complaint with
HyperTerp regards its docu-
mentation. Its accompanying
manual is a flimsy, 20-page
affair that covers installation of HyperTerp Pro by HyperAct, Inc. is a
scripting language that programmers can
the software, a simple tutorial, include in their Delphi applications. With
a four-page reference on the HyperTerp, users can implement custom
scripts (or macros) to add user-defined
three variations of the script functionality to the program. This can be
interpreter, and a few more done by adding a HyperTerp component
to the Component Palette, which in turn
pages of some extensions to automatically attaches a scripting inter-
the example code. As someone preter to the program. Although the doc-
umentation is lacking, HyperTerp
Figure 1: The demonstration program included with HyperTerp show- new to scripting languages, the includes a solid online language refer-
ing some of its graphics capabilities. documentation creates more ence to assist you with the scripting syn-
tax. If your system requires scripting,
questions than it answers. HyperTerp Pro is the tool for you.
After dropping the interpreter component onto your project,
HyperAct, Inc.
you will need to write a couple of event handlers to provide Fortunately, the package 3437 335 Street
links between your program and the scripting engine. The includes two help files. One West Des Moines, IA 50266
Phone: (515) 987-2910
demonstration program includes about four or five event han- is (for the most part) an elec- Fax: (515) 987-2909
dlers of about six to eight lines of code each. If you know tronic form of the printed E-Mail: CIS:76350,333 or Internet:
[email protected]
what you want HyperTerp to do, it won’t take long to make material, while the other is a Web Site: http://www.hyperact.com/
these connections. fairly complete language ref- Price: Standard version, including
the .DCU files, US$149; Professional
erence file. I would have liked version that includes all source code
The HyperTerp script files are ASCII text files, and it’s a sim- to see some more detailed files, US$395. There are no royalties
for distributing the scripting engine.
ple matter to create a quick program that loads/saves and exe- discussion of some of the
cutes these scripts. (Actually, the sample program described in commands, but it’s sufficient
the documentation does just this.) as a reference on the scripting language. You’ll find that you’ll
be working quite close to this language reference as you get
Inside HyperTerp up to speed with HyperTerp’s scripting syntax. The inclusion
As mentioned, HyperTerp is a subset of Pascal. The Standard of this reference went a long way toward taking the bad taste
function library includes 19 math, 10 string, six file, and two out of my mouth caused by the printed “manual.”
memory functions and procedures. The Extended library adds
six more file functions and six system functions (primarily HyperTerp comes in two flavors. The standard version
date/time stuff ). The Windows library adds 27 of the more includes the .DCU files for the component objects and sells
common Windows API routines, allowing scripts to create for US$149. The professional version adds all the source code
windows, dialog boxes, add and manipulate controls, access files as well and sells for US$395. There are no royalties for
profile strings from .INI files, and so on. distributing the scripting engine. The enclosed literature indi-
cates that the scripting engine will work within a DLL if
There are also some procedures and functions unique to its desired. Both versions are completely native Delphi objects —
language. For example, the ForEachFile procedure allows the no DLLs or VBXes. HyperAct has provided a demonstration
user to automatically traverse all the files that match a valid version of HyperTerp in library 22 (3rd Party Products) of the
DOS wildcard mask in the current directory (and optionally Borland Delphi forum on CompuServe (GO DELPHI). The
in all the subdirectories) and perform a common operation file name is PTRPDEMO.ZIP. They also have a Web site at
on each of these files. http://www.hyperact.com/.
Due to its interpreted nature, HyperTerp probably won’t Robert Vivrette is a contract programmer for a major utility company and
break speed records, but it is nonetheless a capable, solid Technical Editor for Delphi Informant. He has worked as a game design-
system. What it does, it does very well. I don’t need the fea- er and computer consultant, and has experience in a number of pro-
tures it provides, but I know there are many developers gramming languages. He can be reached on CompuServe at
who would welcome HyperTerp’s power and flexibility. 76416,1373.
The screen layout is intuitive, as seen in Figure 1. The left side of In addition to numerous examples, WISE has an Installation
the screen displays a list box containing 57 script actions and the Expert that guides you through the entire process, and the resulting
right side displays a list of the selected script actions. To use a script script can be edited as if you started from scratch. It’s so easy that
action, simply highlight it and drag it to the selection window. you can create a professional install script in a matter of minutes.
When you run the compiled installation program, each action is Programming and Control Structures. Although there are only
executed sequentially, much like a batch file. Although the WISE 57 script actions, these are more than enough to do anything
For those of you accessing licensee to phone support beyond the first 30 days and free
data via ODBC, WISE easily upgrades throughout the year.
installs and configures both
16- and 32-bit ODBC drivers Demonstration Versions. You can download a demonstration
as well. version of WISE from their CompuServe forum or from the
The WISE Installation System, by GLBS Web site at http://www.glbs.com. The demonstration
GLBS, Inc., is a solid installation utility Technical Support. The prod- restricts you to using the resulting executable on the comput-
for Windows-based applications. uct comes with 30 days of free er it was created on, but is otherwise fully functional.
WISE’s IDE is intuitive and creates
technical support. Although I
16-bit installation files. Script actions
are executed as if they are batch files needed to leave a message on Conclusion
and are edited in individual property voice mail, GLBS technical WISE has too many features to mention in the space of this article.
editors. An Installation Expert can also support staff promptly So don’t hesitate to download a demonstration and try WISE for
guide you as you create an installa- returned my call, was helpful, yourself. I am confident that you will be won over by this product
tion script. WISE also supports system as I was. My only complaint is that I would like to see more detail
and was knowledgeable of the
access, multi-media, Windows 95, the
BDE, and much more. If you’re look- product. In addition, they in the manual — not only regarding each of the features, but also
ing for the right installation tool, have a section in program installation methodology in general. There are many new
WISE is a worthwhile investment. CompuServe’s Windows issues with Windows 95 and I would like to see some detailed infor-
Third-Party H Forum. To mation on the subject, even in the form of a help or supplemental
WISE Installation System
Great Lakes Business Solutions, Inc. access this forum, type WISE- text file. Nonetheless, the money I spent on WISE was well spent. I
2200 North Canton Center Road, INSTALL at the GO prompt wish I could say that about all developer utilities I’ve purchased. ∆
Ste. 220 and GLBS will answer ques-
Canton, Michigan 48187
Phone: (800) 554-8565 tions online. They are good
E-Mail: CIS: GO WISEINSTALL about making maintenance Micah Bleecher is a partner in Datacraft Systems, Inc., a database consulting firm that
Web Site: http://www.glbs.com/ releases available from their serves the Northeast. They specialize in Delphi, Paradox, and Web database integra-
Price: US$199; Annual Technical
Support Contract, US$95. BBS using passwords for regis- tion. Mr Bleecher can be reached at (609) 227-0202, e-mail:
tered users. An annual mainte- [email protected], or visit the Datacraft Systems Web site at:
nance contract entitles the http: //www.datacraft.db.com.