Delphi Informant 95 2001
Delphi Informant 95 2001
FEATURES REVIEWS
12 Patterns in Practice 34 ReportBuilder Pro 4.0
The Singleton Pattern — Xavier Pacheco Product Review by Bill Todd
Mr Pacheco begins a new column devoted to design patterns, and their
creation and use with Delphi. He gets things started with the Singleton
pattern, which ensures only one instance of a class exists.
DEPARTMENTS
21 Multi-Tier 2 Delphi Tools
The Briefcase Model — Bill Todd 5 Newsline
The Briefcase Model is easy to understand: download part of a database 37 File | New by Alan C. Moore, Ph.D.
onto a laptop, and run. Now, implementing it — with or without MIDAS
— is also relatively straightforward, as Mr Todd explains.
By Ron Loewy
If the user fills out a form and clicks the Submit button that Because the result is built sequentially from the headers to
connects to the ASP application via a <FORM ...> tag (whose the end of the response, you have to set the values of the
action points to the ASP application), the request sequence is headers, cookies, etc. before you write the content of the
repeated. Usually, however, an HTTP POST request is sent, result. The Response object has a property called Buffer that,
and the names of the variables in the forms and the values the when set to True, caches all the output, and doesn’t send it
user entered are sent after the HTTP headers. to the user until the Flush or End method has been called. If
you don’t want to write sequentially to the output, remem-
When the request is received by the Web server, it determines ber to set Buffer to True at the start of your ASP page.
this is a request for an ASP script, using the main program’s
extension (e.g. mypage.asp), and the ASP interpreter is acti- The following properties can be used to set the HTTP
vated with the request information. headers of the page:
The Cookies collection can set cookies that are related to
ASP parses the request information and allows you to access it as your ASP page. You will get these cookies in the Request
properties and collections of the Request object. The QueryString object the next time the user connects to your page.
property, for example, includes all the parameters passed in the The ContentType property sets the type of response you
URL after the ? character. If, for example, the URL was send. By default, text/html is assumed, but if your
http://path-to-server/mypage.asp?Name=Ron, we could find the response is an image, you’ll need to set it to image/gif,
value of the Name parameter using the following syntax: image/jpeg, etc.
The Status property sets the status that is returned. By
Your Name is <%= Request.QueryString("Name") %> default, “200 OK” is returned, and the browser will dis-
<%= Server.HTMLEncode("A < 2") %> The Session object’s Session_OnStart event is called when a new
session is created. The code in this event can be used to create
will be translated to: session objects, or initialize variables used by the session. The
Session_OnEnd event is called when the session is terminated.
A < 2
The Application Object
The browser will be able to display this code properly (A < The Application object is used to store information that is
2), where, if you wrote A < 2 in the source, the browser global in scope to the application, and is shared between
Like every other Automation object, you’ll want to start by To test the object, I created a simple HelloWorld.asp, and
creating an ActiveX library; click on ActiveX Library on the installed it in a virtual directory defined in my Web server as
ActiveX page of the New Items dialog box (File | New). I DIASP. This directory must have read and script authorization.
saved my library as DIASP.dpr in my work directory. Now
add an Automation object to the project (also from the The code that calls our object from the ASP file is simple:
New Items dialog box). I gave the name ASPObject to the
new class, and saved the implementation unit as <%
ASPObj.pas. Set ASPObj = Server.CreateObject("DIASP.ASPObject")
%>
<H3>This page uses the ASP aware Delphi automation object </H3>
Every Automation object that wants direct access to the ASP <% ASPObj.HelloWorld %>
objects needs to add the ASPTypeLibrary_Tlb unit created
when we imported the ASP DLLs to the uses statement. We The result is shown in Figure 2. Notice that our Delphi
can now add a reference to a scripting context in the object code accessed the output stream directly via the ASP
definition. A scripting context is an ASP interface named Response object.
IScriptingContext that provides our object with access to the
ASP objects in the context of the session that uses the object. Why Use Delphi?
It’s obvious from this sample that it is easy to access the
Our class definition will now look like this:
type
TASPObject = class(TAutoObject, IASPObject)
private
FScriptContext: IScriptingContext;
public
property ScriptContext: IScriptingContext
read FScriptContext;
end;
procedure TASPObject.VerifyCard;
var
VerifyObject: TCreditCardVerify;
begin
VerifyObject := TCreditCardVerify.Create(nil);
try
VerifyObject.CardNumber :=
ASPRequest.Form('CardNumber');
VerifyObject.SetCardTypeByName(
ASPRequest.Form('CardType'));
VerifyObject.SetExprDateFromStr(
ASPRequest.Form('ExprDate'));
Figure 4: After the verification component does its job, the
ASPResponse.Write('Checked Validity for Card Number ' +
VerifyObject.CardNumber + '<br>');
result is reported back using the ASP Response object.
case VerifyObject.Valid of
ccvValid : ASPResponse.Write('Card is valid!');
ccvExpired : ASPResponse.Write('Card Expired!'); facilities, and existing code modules over the primitive
ccvInvalid : ASPResponse.Write( tools available for script development.
'Card did not pass validation!');
end;
finally Let’s create, for example, a credit-card validation routine in
VerifyObject.free; Delphi. Assume your Web page receives credit-card orders,
end; and you need to verify that the credit-card number is valid.
end;
Usually, you’ll need some software that can connect to verify
the credit-card information supplied and use your merchant
Figure 3: The VerifyCard method uses the credit-card verifica- account, but for the purpose of this article, we’ll assume that
tion component. using the credit-card number validation algorithm is enough.
While this algorithm won’t rival the scheduling problems the
ASP objects from our Delphi code, but why would we NT operating system developers had to face, I wouldn’t want
even bother to do that if we can write the same code in to implement it in VBScript.
VBScript or JScript?
The CcardVer.pas unit defines a Delphi object
Using Delphi to interact directly with the ASP objects TCreditCardVerify that, based on the properties CardType,
provides several advantages. The first is speed; a compiled ExprDate, and CardNumber, will return the validity of the
Delphi object will be much faster than a script that needs card information. We will now add a new method to our
to be interpreted by the Active Script engine used by ASP. object that will access credit-card information sent from
If the logic you need to perform is complicated, your users an HTML form, verify it, and return a response to the
will receive better service if the calculations are performed user. (Don’t forget to use the Type Library editor to add
in a compiled module. The other advantage to using the new VerifyCard method.)
Delphi to write your logic is the power of the language,
and the ability to use existing code. I would not bother The code for the new method uses the credit-card verification
with Delphi code for simple ASP scripts, but when the component, as shown in Figure 3. This code uses the ASP
need for complicated logic or business rules arises, I would Request object to access the parameters passed from the
rather use Delphi’s strong development tools, debugging CreditCardVerify.html form. After the verification compo-
Conclusion
Creating ASP-aware objects with Delphi is almost as easy as
creating any other kind of Automation object. If you are
writing ASP applications, Delphi code can be used to speed
calculations and development time.
Ron Loewy is a software developer for HyperAct, Inc. He is the lead developer
of eAuthor Help, HyperAct’s HTML Help-authoring tool. For more information
about HyperAct and eAuthor Help, contact HyperAct at (515) 987-2910, or
visit http://www.hyperact.com.
By Xavier Pacheco
cally have some, if not much, visibility into the ancestor Figure 4: TUserConfiguration based on the inheritance model.
(I would like to thank Anne Pacheco and Steve Teixeira for procedure TSingleton.ShowSingletonName;
proofing this and many of my articles.) ∆ begin
ShowMessage(ClassName);
end;
The files referenced in this article are available on the Delphi
Informant Works CD located in INFORM\99\MAR\DI9903XP. class function TSingleton.Singleton: TSingleton;
begin
Result := Snglton.Singleton;
end;
// TStorageCfg. initialization
function TStorageCfg.ReadUserConf(AUserConfiguration:
TUserConfiguration): Boolean; finalization
var // Free the global object, only if not freed previously.
FileStream: TFileStream; if FUserConfiguration <> nil then
FName: string; FUserConfiguration.Free;
begin end.
Result := False;
FName := ChangeFileExt(Application.ExeName, '.dat');
if FileExists(FName) then begin End Listing Two
FileStream := TFileStream.Create(FName, fmOpenRead);
try
FileStream.ReadComponent(AUserConfiguration);
Begin Listing Three — XWRegUtils.pas
Result := True; unit XWRegUtils;
finally
FileStream.Free; interface
end;
end; uses Classes;
end;
// Saves the component specified by AComponent and its
procedure TStorageCfg.SaveUserConf(AUserConfiguration: // properties to the system registry in the location
TUserConfiguration); // specified by AregKey.
var procedure ComponentPropsToReg(AComponent: TComponent;
FileStream: TFileStream; const ARegKey: string);
begin
(PropName = 'Tag')) then Begin Listing Four — MainFrm.pas
// For now, only process integers, strings, and unit MainFrm;
// other objects.
if AReadProps then interface
case PropList[i]^.PropType^.Kind of
tkInteger: uses
SetOrdProp(AObject, PropList[i], Windows, Messages, SysUtils, Classes, Graphics, Controls,
ARegIni.ReadInteger(ARegSection, Forms, Dialogs, StdCtrls;
PropName, 0));
tkString, tkLString: type
SetStrProp(AObject, PropList[i], TMainForm = class(TForm)
ARegIni.ReadString(ARegSection, edtUserName: TEdit;
PropName, EmptyStr)); edtPassword: TEdit;
tkClass: begin lblUserName: TLabel;
TempObject := TObject(GetOrdProp( lblPassword: TLabel;
AObject, PropList[i])); procedure FormClose(Sender: TObject;
ProcessProps(TempObject, PropName, var Action: TCloseAction);
True, ARegIni); procedure FormCreate(Sender: TObject);
end; procedure edtUserNameChange(Sender: TObject);
end procedure edtPasswordChange(Sender: TObject);
else end;
case PropList[i]^.PropType^.Kind of
tkInteger: var
ARegIni.WriteInteger(ARegSection, MainForm: TMainForm;
PropName, GetOrdProp(AObject,
PropList[i])); implementation
tkString, tkLString:
ARegIni.WriteString(ARegSection,PropName, uses UserCfg;
GetStrProp(AObject, PropList[i]));
tkClass: begin {$R *.DFM}
TempObject := TObject(GetOrdProp(AObject,
PropList[i])); procedure TMainForm.FormCreate(Sender: TObject);
ProcessProps(TempObject, PropName, False, begin
ARegIni); with UserConfiguration do begin
end; // Change the storage location to the system registry.
end StorageType := stRegistry;
end; if not ((MainScreenPos.Width = 0) or
end; (MainScreenPos.Height = 0)) then
finally SetBounds(MainScreenPos.Left, MainScreenPos.Top,
FreeMem(PropList, MainScreenPos.Width, MainScreenPos.Height);
SizeOf(PPropInfo) * TypeData.PropCount); edtUserName.Text := UserName;
end; edtPassword.Text := Password;
end; end;
end; end;
end.
By Bill Todd
There are two architectures that can form the There are two ways to handle this situation.
basis for a briefcase-model application. The The server can attempt to connect to the
first is the classic multi-tier application, database, and, if it fails, can notify the client
where the client application runs on one that the database isn’t available. This is by far
machine. The application server (the middle the more complex solution, because the client
tier) runs on another machine on the net- will have to call a custom method on the
work, and the database server runs on yet server to determine if the database is avail-
another machine. In this case, a MIDAS able, and, if it isn’t, modify its behavior to
license is required. In a briefcase application, work with local data, even though the appli-
when the client application starts, it must cation server is running.
determine if the application server is avail-
able. The easy way to see if the server is avail- A much simpler approach is to assume the
able is to set the Connected property of the user knows whether she or he is connected
connection component in the client applica- to the network, and provide two icons to
tion to True. If the server isn’t available, an start the client application. One icon is
exception is raised. used when connected to the network, and
the other when not connected. The icon
One of the nice things about the MIDAS that starts the client when the network isn’t
licensing requirements is that you can build available can include a command-line para-
briefcase-model applications without having meter that tells the client to run in brief-
to purchase a MIDAS license. As long as the case mode.
client application and the application server
that provides its data run on the same A Sample Application
machine, no license is required. The sample application accompanying this
article (see Figure 1) demonstrates these
This architecture complicates a briefcase techniques. (The client and server projects
application because the application server is discussed in this article are available for
always available. Starting the client when not download; see end of article for details.) To
connected to the network will cause the run the sample application, compile and
application server to start, but the server run the EbSrvr server first, so it will register
won’t be able to connect to the database. itself as an Automation server.
The DisableServerFeatures method: The next block of code in the main form’s OnCreate event
handler attempts to load any data in local files by calling
{ Disable features that are only available when the LoadLocalData method, shown in Figure 4. This
connected to the application server. } method returns True if local data is found. The method
procedure TEcMainForm.DisableServerFeatures;
begin also checks if each file exists, and if so, calls the corre-
View1.Enabled := False; sponding client dataset’s LoadFromFile method to load the
ApplyChanges1.Enabled := False; data. In this application, there is no way to save order data
FilterStringLabel.Visible := False;
FilterLabel.Visible := False; without saving customer data, so the return value is set to
end; True if the local customer file is found.
Bill Todd is president of The Database Group, Inc., a database consulting and
development firm based near Phoenix. He is a Contributing Editor of Delphi
Informant, co-author of four database-programming books, author of over 60
articles, and a member of Team Borland, providing technical support on the
Inprise Internet newsgroups. He is a frequent speaker at Inprise conferences in
the US and Europe. Bill is also a nationally known trainer and has taught
Paradox and Delphi programming classes across the country and overseas. He
was an instructor on the 1995, 1996, and 1997 Borland/Softbite Delphi World
Tours. He can be reached at bill@dbginc.com or (602) 802-0178.
Shell Notifications
Getting Windows to Share Some of Its Secrets
If the pidlPath data member of this record specifies a valid The LParam data member will be set to the ID of the event
PIDL for a valid folder, you’ll receive events that affect the that occurred. This will be one of the SHCNE_xxx values
folder itself, as well as any items in the folder. If you set the shown in Figure 1. It’s possible that multiple event IDs
bWatchSubtree data member to True, you’ll receive events for could be or’ed together, so it would be best to test for the
the entire sub-tree rooted at the specified folder, e.g. all fold- presence of a given ID value, using a logical and operation,
ers and items below the specified folder in addition to the rather than via an equality test using the = operator.
folder itself. If you set the pidlPath data member to nil, you’ll
receive events for every folder and item on the system. If the The WParam will be a pointer to a record of type
value of the ItemCount parameter is greater than 1, you must TTwoPIDLArray. The name WParam, which is C-style short-
supply the same number of TNotifyRegister records to the hand for Word Parameter, is an anachronism. It’s now actual-
Items parameter in the form of a vector, one record packed ly a 32-bit long type, just like LParam, and so can store a
after another into a buffer large enough to hold all of them. pointer. This record points to an array containing the two
PIDLs associated with the event, as shown here:
If the SHChangeNotifyRegister function succeeds, the return
value is a handle of a shell change notification object. You TTwoPIDLArray = packed record
should save this handle for later use. If the function fails, the PIDL1: PItemIDList;
PIDL2: PItemIDList;
return value is 0. end;
Shut It Off Those of you familiar with the SHChangeNotify function may
When you’re finished monitoring the notification events,
be wondering why you only receive PIDLs from shell change
you should pass the notification handle returned by
notification messages, even though the SHChangeNotify func-
SHChangeNotifyRegister to SHChangeNotifyDeregister. The
tion accepts PChar and DWORD data types, as well as PIDLs.
export ordinal value of SHChangeNotifyDeregister is 4, and
The reason is that all the data types are automatically convert-
the function declaration is as follows:
ed to PIDLs by the shell before being sent anywhere. For
SHCNF_PATH and SHCNF_PRINTER types, this seems
function SHChangeNotifyDeregister(Notification: THandle):
BOOL; stdcall; obvious enough. For the SHCNF_DWORD type, a 10-byte
“fake” PIDL is created, with the two DWORD items immedi-
The Notification parameter takes the handle of a shell ately following the cb data member. Ignore the cb data mem-
change notification object returned by a successful call to ber; it has no use in this situation except as a placeholder. This
SHChangeNotifyRegister. As you might guess, if the func- encoding scheme is encapsulated for your convenience by the
tion succeeds, the return value is True; if it fails, the return TDWORDItemID record type, as shown here:
value is False.
TDWORDItemID = packed record
Getting the Message cb: Word; { Ignore }
dwItem1: DWORD;
After registering to receive shell notification messages, the dwItem2: DWORD;
shell will send its notification messages to the window you end;
specified in the Window parameter of the
SHChangeNotifyRegister function. You must then crack the The SHCNE_FREESPACE event is handled as a special case.
relevant data out of the message to get useful information. When the drive is passed in as a path or a PIDL, it’s converted to
Delphi doesn’t define a special message record type for a DWORD contained by the above encoding scheme, with dri-
these messages, so simply use the standard TMessage type. ves A: to Z: mapping onto bits 0 to 25. For example, drive D:
The ProcessID parameter takes the ID of the process that gen- Fortunately, the shell seems to generate most of the notifications
erated the memory map. This value is contained in the itself. Sometimes, it may be directly responsible for an event, in
LParam data member of the shell notification message. which case, it’s easy enough for it to make the call to
SHChangeNotify. However, for things likely to originate in
The PIDLs parameter is output-only, and takes a variable of another application, such as file creation, it would presumably
type pointer to a record of type TTwoPIDLArray. You may ini- have to be monitoring the system somehow to generate the
tialize the pointer variable to nil before calling the function. event. The result is that these notifications can be a bit unreli-
Do not allocate an actual TTwoPIDLArray record. When the able, and often, there is a noticeable delay between the event and
function returns, this pointer will point to a TTwoPIDLArray the notification. Also, the shell has only a 10-item event buffer,
In short, don’t depend on these notifications for mission- First, we’ll define the component’s public interface. There are
critical applications. two main issues with this component: where to watch, and
what to watch for. The API provides the capability to filter a
Don’t Believe Everything You Read few criteria: event type, interrupt or non-interrupt, the folder
Another problem is that the Windows documentation isn’t to watch for events, and whether that folder’s sub-folders
always completely accurate in its descriptions of the various should also be watched. Naturally, we also need some means
events. Following are variations from the documentation of turning this thing on or off.
we’ve observed after actual implementation.
Identifying the folders to watch is the only unusual aspect of
An SHCNE_ATTRIBUTES is supposed to happen when “the all this. As we mentioned previously, not all folders can be
attributes of an item or folder have changed.” However, we have identified by file system paths. Using PIDLs to identify these
only witnessed an SHCNE_ATTRIBUTES event occurring folders is impractical for the design-time property editor, how-
when the printer status changed. Changing file and folder ever, because PIDLs are opaque data types and can’t be manu-
attributes produces an SHCNE_UPDATEITEM event instead. ally edited by a developer.
SHCNE_NETSHARE and SHCNE_NETUNSHARE are sup- The solution is to use two properties. One property is a new
posed to occur when you share or unshare a folder. However, on enumerated type, TkbSpecialLocation, which encapsulates the list
Windows NT, the SHCNE_NETUNSHARE event never of Windows API constants that correspond to various “special”
occurs. You get a SHCNE_NETSHARE event on both occa- folders, e.g. Control Panel. These constants can be used with the
sions. On Windows 95, they appear to work as advertised. SHGetSpecialFolderLocation API function to obtain a PIDL to
that folder. By setting a property to one of these enumerated val-
An SHCNE_UPDATEIMAGE event is claimed to signify that ues, special locations can be selected without requiring the devel-
an image in the system image list has changed. However, images oper to type in the data for the PIDL. One of the values of
in the system image list should never change. What the event TkbSpecialLocation is kbslPath. Setting this value will enable a
really means is that something that was using that particular second property to allow the developer to enter a specific file sys-
icon index in the system image list is now using something else. tem path to monitor. Here’s the final list of published properties:
Typical uses of SHCNE_UPDATEIMAGE include the Recycle
Bin changing between empty and full, and the icon associated property Active: Boolean
with a CD drive when a CD is inserted or removed. Document property HandledEvents: TkbShellNotifyEventTypes
property InterruptOptions: TkbInterruptOptions
icons, which change as a result of changing a file-type associa- property RootFolder: TkbSpecialLocation
tion, do not generate an SHCNE_UPDATEIMAGE. They will property RootPath: TFileName
produce an SHCNE_ASSOCCHANGED event instead. property WatchChildren: Boolean;
Figure 4: Event procedure type definitions. Deciding what the events will be was fairly simple. There
should be a component event for each possible shell event.
In addition, we’ll define events to encapsulate the three
property OnAnyEvent: TkbShellNotifyGenericEvent
property OnDiskEvent: TkbShellNotifyGenericEvent
“collective” events defined by the Windows API (for
property OnGlobalEvent: TkbShellNotifyGenericEvent those hardy souls who like working with raw Windows
property OnAssociationChanged: TkbShellNotifySimpleEvent data). The list of events and their definitions are found
property OnAttributesChanged: TkbShellNotifyGeneralEvent
property OnDriveAdded: TkbShellNotifyGeneralEvent
in Figure 5.
property OnDriveRemoved: TkbShellNotifyGeneralEvent
property OnExtendedEvent: TkbShellNotifyGenericEvent The last area of the public interface to consider, the run-
property OnFolderCreated: TkbShellNotifyGeneralEvent
property OnFolderDeleted: TkbShellNotifyGeneralEvent
time methods, lends a few candidates for consideration. It’s
property OnFolderRenamed: TkbShellNotifyRenameEvent handy to have auxiliary methods for setting the Active prop-
property OnFolderUpdated: TkbShellNotifyGeneralEvent erty on and off. Also, virtually any component that handles
property OnFreespaceChanged: TkbShellNotifyGeneralEvent
property OnImageUpdated: TkbShellNotifyIndexEvent
system data needs some sort of reset capability. These meth-
property OnItemCreated: TkbShellNotifyGeneralEvent ods are shown here:
property OnItemDeleted: TkbShellNotifyGeneralEvent
property OnItemRenamed: TkbShellNotifyRenameEvent
procedure Activate;
property OnItemUpdated: TkbShellNotifyGeneralEvent
procedure Deactivate;
property OnMediaInserted: TkbShellNotifyGeneralEvent
procedure Reset;
property OnMediaRemoved: TkbShellNotifyGeneralEvent
property OnNetworkDriveAdded: TkbShellNotifyGeneralEvent
property OnResourceShared: TkbShellNotifyGeneralEvent
property OnResourceUnshared: TkbShellNotifyGeneralEvent There are, of course, many details to implementing a com-
property OnServerDisconnected: TkbShellNotifyGeneralEvent ponent that go beyond the core functions that the compo-
nent encapsulates. As there are many other excellent refer-
Figure 5: Design-time published events.
ences that cover the details of implementing custom com-
ponents in Delphi, this article will not cover them. Instead,
For those developers who like to get down and dirty, we’ll sur- it will concentrate only on those pieces of the component’s
face a couple of run-time properties that allow them to med- implementation that directly relate to the specific problem
dle at the API level. There are really only two bits of informa- of shell notifications.
tion we can provide: the notification handle, and the actual
root PIDL. These properties are: The implementation of shell notifications revolves around the
calls to SHChangeNotifyRegister and SHChangeNotifyDeregister.
property Handle: THandle Everything we do in this component will be in support of
property RootPIDL: PItemIDList those function calls. Let’s outline how the public properties
and methods relate to those functions.
Because this component encapsulates a notification mech-
anism, it should be clear that events are at its heart. We’ll The property most directly linked to the calls is Active.
want to pre-crack the event data for the developer’s conve- Setting this property to True will cause
nience, of course. Every event will include the Sender para- SHChangeNotifyRegister to be invoked with parameters
meter, as usual, and a Boolean value identifying whether governed by the other four published properties. As you might
the event was generated by an event. Some review of the guess, setting it to False will cause SHChangeNotifyDeregister
API documentation reveals that we have five basic patterns to terminate the notifications.
of “data” parameters:
1) No extra parameters. The mechanics of calling the API functions are delegated to
2) One DWORD. two private methods, StartWatching and StopWatching, which
3) One non-nil PIDL, which might represent a path. will be discussed later. Meanwhile, here’s a code snippet from
procedure TkbShellNotify.StopWatching;
begin
{ Deregister the notification handle
and set the handle to nil. }
SHChangeNotifyDeregister(Self.FHandle);
Self.FHandle := 0;
end;
By Bill Todd
To build a report, begin by dropping a database source. You can also create your
TTable and TDataSource on a Delphi form. own pipeline components to supply data
Next, move to the ReportBuilder page of from proprietary sources. Set the pipeline
the Component palette, and drop a component’s DataSource property to con-
TppBDEPipeline and TppReport on your nect it to your DataSource component,
form. The BDEPipeline component is one and you’re ready to double-click the Report
of a family of pipelines that supply data component to open the designer, shown
to your report. Use BDEPipeline to get in Figure 1.
data from a BDE (Borland Database
Engine) database, or the DBPipeline if Ergonomically Speaking
you’re working with a BDE replacement. The designer is well laid out and easy to
The TextPipeline component lets you use use. The first row of toolbars includes the
data in a text file for your report, and the non-data-aware, data-aware, and advanced
JITPipeline supplies data from a non- components. The next row begins with a
toolbar (which is empty in Figure 1). The
contents of this toolbar vary depending on
the component you select in the designer.
For example, if you select a data-aware
component, the toolbar contains a drop-
down list of data pipeline components, and
a drop-down list of fields from the current-
ly selected pipeline. To the right is a text-
control toolbar that will be familiar to any-
one who has used a Windows word proces-
sor. At the end of the formatting toolbar
are Bring To Front and Send To Back but-
tons for working with layered components.
Number Crunching
ReportBuilder allows you to create calculated
values in several ways. First, of course, you
can add Delphi calculated fields to your
datasets. Second, the TppDBCalc component
lets you calculate the sum, count, min, max,
or average for a group of records. Finally, the
TppVariable component lets you add code to
its OnCalc event handler to generate values
any way you wish. If the value of one
TppVariable component uses the value of
another, you can set its CalcOrder to ensure
the variables calculate in the correct order to
produce a correct result. ReportBuilder also
lets you choose to generate reports in one
pass or two. One pass is faster, but using a
two-pass report lets you display grand totals
at the beginning of the report, or use “page n
Figure 3: The Report Tree and Data Tree windows. of m” page numbers.
The Bottom Line Bill Todd is president of The Database Group, Inc., a database consulting and
I hate creating reports. One of the first things I look for development firm based near Phoenix. He is a Contributing Editor of Delphi
in a reporting tool is how fast it lets me create the dozens Informant, co-author of four database-programming books, author of over 60
of simple reports that most applications require. The icing articles, and a member of Team Borland, providing technical support on the
on the cake is the Report Wizard. While it’s a great tool Inprise Internet newsgroups. He is a frequent speaker at Inprise conferences in
for end users, it saves time for developers as well. After the US and Europe. Bill is also a nationally known trainer and has taught
setting up your data sources and pipeline components, Paradox and Delphi programming classes across the country and overseas. He
fire up the Report Wizard. It will let you choose a report was an instructor on the 1995, 1996, and 1997 Borland/Softbite Delphi World
Tours. He can be reached at bill@dbginc.com or (602) 802-0178.
style, select the fields to be displayed, and define the
groups you require; click the Finish button to create your
report layout. Even if the report requires additional manu-
al customization, getting the basic layout created without
having to manually place and align each component is a
real time saver.
U nlike TAPI and some of the newer APIs, the multimedia APIs have been a part of Delphi since its
inception. Unfortunately, the documentation and examples (in Delphi and elsewhere) are minimal.
Increasingly, I get messages from developers struggling to work with these APIs and frustrated by the lack
of information, so I’m writing a book on the topic (The Tomes of Delphi: 32-bit Multimedia Programming
will be published by Wordware this summer). I will also be exploring selected multimedia topics in these
pages. This column, which presents an overview of the multimedia APIs, is the first such contribution.
The 32-bit multimedia APIs fall into three general groups — low- uct. The command strings consist of words arranged in English-like
level, mid-level, and high-level — with low-level APIs more device- sentences. For example, the words associated with the playback of a
specific, high-level APIs more generic, and mid-level APIs some- sound include “load,” “play,” and “stop.” For example:
where in between. In Windows 3.x, the MCI was the highest-level
API available. With Windows 95, a higher-level interface was intro- mciSendString("pause movie", nil, 0, nil);
duced: the MCIWnd class.
Conveniently, these command strings have corresponding command
Low-level APIs. The low-level functions require more work, but messages, but messages are more complicated. The command mes-
provide a high degree of control over various media devices. These sage corresponding to the above command string looks like this:
APIs include functions to work with various multimedia file types,
audio, audio mixers, and joysticks. They also include the most pre- mciSendCommand(MovieDevice, MCI_PAUSE, 0, DWORD(nil));
cise timing functions in the Windows API. Considering multime-
dia’s time-critical requirements, this should come as no surprise. As where MovieDevice is the handle of the movie-playing device
Bob Swart demonstrated at last summer’s Inprise Conference, these returned by calling another command message using the
timing functions can even be used to profile applications. MCI_OPEN command. As you can see, there’s quite a bit more
involved with command messages than with command strings.
The Waveform API provides a low-level interface to audio devices. Sometimes, you’re required to supply the third and fourth parame-
Most of these functions begin with “Wave,” or more specifically, ters (flags and parameters, respectively). In a running application,
“WaveIn” or “WaveOut.” The former group provides functions for command messages are much faster than command strings, because
recording .WAV files, the latter for playback. Because this is a low- the latter must be parsed.
level API, you generally need to call several of these functions. Often
grouped with these files, but at a higher level, the PlaySound function A high-level class. The MCIWnd class is even easier to use, provid-
provides an easier means of playing .WAV files. Closely related to ing a quick way to create a multimedia control and associate it with
these functions are the auxiliary audio functions that control auxiliary some type of multimedia event. For example, the following state-
audio devices. All of these are prefixed with “Aux,” e.g. AuxSetVolumn. ment (based on code from Delphi’s online help) creates a button on
a form that, when pressed, plays a video clip:
Another important set of low-level functions are those that control the
MIDI (Musical Instrument Device Interface), enabling communica- MCIWndCreate(hwndParent, g_hinst, WS_VISIBLE or WS_CHILD or
tion with a sound card’s built-in synthesizer. With these MIDI func- MCIWNDF_SHOWALL, 'sample.avi');
tions, you can work with sounds directly, select various patches
(instruments), and perform many sound-playing operations on the fly. Because this column is introductory in nature, it probably raises
more questions than answers. However, I plan to explore these top-
The low-level multimedia input/output functions provide a variety of ics in more detail in future articles. I also plan to devote another
file operations with buffered and unbuffered files, files in standard column to sources of information on multimedia programming,
Resource Interchange File Format (RIFF), memory files, or files using both books and Internet sites. In the meantime, I’d like to hear
custom formats. Similarly, there are low-level functions for working from you concerning your experiences, questions, and discoveries in
with .AVI files. These include a large number of routines for file I/O, working with multimedia in Delphi. ∆
streaming AVI data, and editing AVI streams. Finally, there are several
functions for working with joysticks, and a handful of timer functions. — Alan C. Moore, Ph.D.
The MCI. The Media Control Interface is a mid-level API that pro- Alan Moore is a Professor of Music at Kentucky State University, special-
vides a fairly easy means of working with a variety of multimedia izing in music composition and music theory. He has been developing
files and devices, so you can create a sophisticated multimedia appli- education-related applications with the Borland languages for more than
cation with minimum coding. Best of all, the MCI provides two 10 years. He has published a number of articles in various technical jour-
approaches (two sets of commands) for performing these tasks. The nals. Using Delphi, he specializes in writing custom components and
first, mciSendString, is ideal for prototyping applications; the second, implementing multimedia capabilities in applications, particularly sound
mciSendCommand, is faster and better suited for the finished prod- and music. You can reach Alan on the Internet at acmdoc@aol.com.