October 2001, Volume 7, Number 10: On The Cover
October 2001, Volume 7, Number 10: On The Cover
October 2001, Volume 7, Number 10: On The Cover
ON THE COVER
5 DBNavigator 22 On Language
Introducing WebSnap — Cary Jensen, Ph.D. Console Applications: Part III — Mike Edenfield
Cary Jensen introduces WebSnap, a powerful collection of components Mike Edenfield completes his myth-dispelling console programming
and helper classes for creating sophisticated Web sites with a mini- series, by demonstrating how console applications can employ mes-
mum of effort. In short, many developers will find that WebSnap sage queues, create GUI windows, multithread, and more.
alone will justify the upgrade to Delphi 6 Enterprise.
REVIEWS
FEATURES 26 GTSizer
11 Kylix Tech Product Review by Bill Todd
7 Things You Need to Know about CLX — Nick Hodges
28 ExpressPrinting System 2
From “Qt Is the Operating System,” to case-sensitivity, to “It Ain’t
Product Review by Ron Loewy
the Win32 Messaging System,” Nick Hodges thinks there are some
things you need to know before diving into a Kylix project.
DEPARTMENTS
14 Multi-tier
2 Delphi Tools
Multiple Remote Data Modules, One Connection — Bill Todd
31 Best Practices by Clay Shannon
Bill Todd demonstrates how to share a single connection to multiple
remote data modules, and how to design multiple remote data 33 File | New by Alan C. Moore, Ph.D.
modules so they can easily interact with each other. 35 Symposium by Jerry Coffey
IT-Map International
Price: US$875
Contact: [email protected],
[email protected]
Web Site: http://www.dbpal.com,
http://www.it-map.com
Introducing WebSnap
Delphi 6 Enterprise Makes Web Development a Snap
Unlike the WebApp modules, of which you may have only one in your
application, you typically have many Web modules (WebPageModules
and/or WebDataModules) in your WebSnap application. Normally
you will have one WebPageModule for each Web page that your
application can serve up. WebDataModules, like their data module
counterparts, are containers for components, such as data access com-
ponents, that you want to share among two or more WebPageModules
or WebDataModules. The design of your WebSnap application will
determine how many WebDataModules — if any — you will use.
It’s interesting to note that all Web module units declare a function in
their interface section that returns a reference to a module instance.
Figure 3: Use the Application Module Page Options dialog box to
This function is similar to the instance variable used for forms (such as select the type of producer to add to your WebAppPageModule.
Form1 for a form class named TForm1). By using a function, however,
the Web application can ensure you’re referring to the correct instance
of your Web module if you compile your Web server extension as The final check box on this dialog box permits you to save your page
an ISAPI/NSAPI or Apache DLL (since these types of servers can be module settings as a default, defining the default values that will be
accessed simultaneously by two or more threads of the Web server). displayed the next time you use this wizard.
You create WebPageModules using the WebSnap Page Module WebSnap Producers
Wizard on the WebSnap page of the Object Repository. This In Web Broker applications you use producer components, such as
wizard, shown in the Figure 4, permits you to control various the PageProducer and QueryTableProducer, to hold, manipulate, and
aspects of the page module generation. Use Type to identify the even generate HTML. The same is true of producers in WebSnap. In
type of producer component you want to appear on the page fact, WebSnap can make use of all of the same producers available to
module. If you select a producer component that supports script- Web Broker applications.
ing, such as a PageProducer or AdapterPageProducer, select the
scripting language using the Script Engine combo box. (The various In WebSnap, however, even the relatively simple PageProducer
producer components are described later in this article.) has an important new capability. Specifically, the PageProducer,
as well as all of the other standard types of producers used
Normally you will want an HTML or XSL template generated for in WebSnap applications (AdapterPageProducer, DataSetPagePro-
your page. This template will typically include some static HTML or ducer, InetXPageProducer, and XSLPageProducer), can have an
XSL/XML, but can also include scripting instructions (if your producer HTML or XSL/XML template associated with it. In addition to
component supports it), and transparent tags. If you don’t want the this template holding regular HTML and transparent HTML tags
HTML or XSL/XML template generated for you, uncheck New File. (tags that have the form <#tagname> and which are designed to
be replaced at run time by the producer’s OnHTMLTag event
Use the Page area of the WebSnap Page Module Wizard to define the handler), these templates can also include scripting instructions,
name and title for your page. If your page is a published page, the name just like Active Server Pages (only the XSLPageProducer compo-
corresponds to the pathinfo part of the URL that will automatically cause nent cannot include scripting instructions, but XSL is itself a
your page to be displayed. If you don’t want this page to be available, programming language). Unlike ASPs, however, these instructions
uncheck Published. You can also require that the user has logged into your are executed by a scripting engine at the request of your WebSnap
Web site before serving this page. If so, enable Login Required. application, instead of being executed at the request of the Web
server itself.
In the Module Options area you can define how the page’s life cycle
will be managed. Set Creation to On Demand if you want the page Many of the scripting instructions added to an HTML template
module to be created the first time it is requested, or to Always when are generated by the various WebSnap components that you use,
you want the module to be created when the Web server extension is based on the WebSnap features you configure at design time. You
first accessed. Use the Caching combo box to define how the page is can also add your own scripting instructions, which permits you
destroyed. Set it to Cache Instance to instruct your Web server extension to do things such as add <!#INCLUDE> directives to insert
to not destroy your Web page module when it has finished processing HTML, or other content, directly into the HTML stream, invoke
a request. Set Caching to Destroy Instance to request that the page COM servers, access Java objects, or just about anything that your
module’s destructor be invoked when it has completed its response. scripting language permits.
<%=Modules.Page5.Adapter1.Field1.DisplayText%>
Before you can use an adapter, you must create at least one field for
it, although in most cases you will create many. For example, to use
a LoginFormAdapter, you will likely create at least two fields: one
associated with a user name, and another associated with the password.
WebSnap Dispatchers
Dispatchers are the components that manage the handling of the
requests that are received by your Web server extension. WebSnap
includes three types of dispatchers: WebDispatcher, PageDispatcher, and
AdapterDispatcher.
Figure 7: The Web Page Editor with a partially configured
While WebDispatcher is a crucial component in Web Broker applica- AdapterFieldGroup and an AdapterCommandGroup that isn’t
tions, it’s far less common in WebSnap applications. The WebDispatcher yet configured.
component considers the pathinfo of an HTTP request, and sends it to a
WebActionItem. In WebSnap applications, this process is handled by the
PageDispatcher, which considers the pathinfo part of the HTTP request
and sends it to the WebPageModule of the same name.
Figure 6:
Creating a Web-
DataModule using
the WebSnap Data
Module Wizard.
Figure 9: This Web page is generated entirely by WebSnap
adapter components, and required no design-time code.
By Nick Hodges
M any Delphi programmers are giving Kylix a close look. Naturally, one of the first
things new Kylix developers will do is try to port existing VCL-based applications
to Kylix. Of course, they’ll also be building CLX applications from scratch.
While the VCL and CLX are similar in many from the operating system more than even the
ways, they’re different enough to make moving a VCL does. Of course, you have access to libc via
VCL application to Kylix more than a copy-and- the libc.pas unit, but you can build robust CLX
recompile exercise. Programmers experienced applications and never use it.
with how Windows and the VCL behave will
find a number of differences in using the two CLX relies on Trolltech’s Qt libraries. Thus, Qt
class libraries. This article presents a few things becomes the base library for all visual controls
that a VCL programmer might want to know used in an application. In Kylix, the Qt libraries
before porting to Kylix, or creating applications are wrapped up in the shared objects, libqt.so
with CLX from scratch. and libqtintf.so. These two files contain all the
logic to display your GUI-based application. At
1. Qt Is the Operating System its root, CLX rarely interfaces with the Linux
The biggest and most obvious difference operating system. Instead, it talks almost exclu-
between Delphi and Kylix is the operating sively to Qt. So it’s Qt that provides the equiva-
system. Kylix runs on, and builds native applica- lent of the Windows message loop (more on this
tions for, Linux. The applications should run on later), and all calls used to create and manage
any Linux distribution and are desktop-neutral. Windows are made to the Qt libraries.
That’s the view from the user’s side.
There are only a few places in CLX where
From the developer’s side, Kylix does a very good the framework calls the X library directly, and
job of shielding the developer from Linux. While almost none where it calls Linux directly. CLX
you have access to all the X-library and libc makes use of the RTL, but the actual presenta-
calls, and all your CLX controls can be used tion of GUI-based applications, and all the event
directly with Xlib calls, you can build robust and handling, are done via Qt. Thus, CLX program-
complex applications without having to delve mers will rarely deal directly with Linux in the
into these lower levels of Linux programming. same way that a VCL programmer deals with the
CLX and the Kylix run-time library (RTL), as Win32 operating system. Indeed, if they did, the
wrapped up in the SysUtils unit, insulate you code wouldn’t be platform-independent.
Some things will trip you up when you try to port VCL-based Kylix opens the door to cross-platform code, which raises the issue
applications to Kylix. CLX is modeled on the VCL and is similar to of compiler directives to ensure that the correct code is compiled
it, but rests on top of an entirely different framework (Qt). What for the correct platform. The Kylix compiler defines LINUX; the
is “standard behavior” for the VCL and the underlying Windows Delphi 6 compiler defines MSWINDOWS. Borland recommends
controls isn’t necessarily standard for CLX — on Linux or Windows. you use:
Borland has tried to make CLX as much like the VCL as possible, but
the two frameworks don’t behave exactly the same. Some differences {$IFDEF MSWINDOWS}
will be minor, some major, some resolvable, and some not. // Windows code here...
{$ENDIF}
For instance, some windows managers, such as KDE, create all their Guess what? Kylix won’t compile this. It will look for
windows as sizable. Thus, setting the form’s BorderStyle property to MyUnit.DFM and won’t find it, because the file is likely named
fbsDialog or fbsSingle will change its appearance, but the form will MyUnit.dfm.
still size on some of your users’ windows managers. You can force
a window not to size with its constraints property (or the fbsNone Here’s another situation: one of your units is named myconsts.pas,
property, although that’s usually a bit much), but it will always look and you have declared the unit name as:
like it’s sizable with the sizing border and grips (again, depending
on the windows manager you use). This means that as a developer, unit MyConsts;
you can’t count on a true dialog box, and you have to code for that.
Kylix developers should get familiar with the Anchors property of The compiler will object to this as well. New Kylix projects get
TForm to ensure their windows look right when resized. the file naming correct, but you may run into trouble when you
move code over from Delphi.
In addition, you can only paint on the client area of a window.
There’s no equivalent to WM_NCPAINT messages. For example, Conclusion
you can’t paint on a window’s title bar, because the windows While Kylix is often referred to as “Delphi for Linux,” that’s not
manager controls it, and X and Qt know nothing about it. exactly true. The IDEs behave similarly, and it’s easy to be con-
fused when looking at the two applications — they appear nearly
6. Not All Your Favorite Controls Are There identical. While Delphi 6 will include the ability to build CLX
While Borland tried to make the interface of CLX as close as applications, Delphi 5 users will find the CLX framework differ-
possible to that of the VCL, there are, of course, differences. As ent in some subtle but important ways from the VCL. Remember-
mentioned previously, all the components on the Standard page of ing a few key differences between CLX and the VCL will enable
the Component palette are in Kylix, but some of the controls on you to smoothly make the transition from Delphi and Windows
other palette pages are not. to Kylix and Linux. ∆
While Kylix does extend into the realm of the Apache Web server,
it only includes support for the basic WebBroker Web modules.
Internet Express, which relies on MIDAS, isn’t available in current
editions. It remains to be seen what Internet development support
will exist in the coming edition(s).
By Bill Todd
The first problem with this approach is that the The SQLConnection component is named
client application required a separate connection com- EmpConn, and its driver name property is set to
ponent for each remote data module in the applica- InterBase. Its Params property has been modified to
tion server. This increased system resource usage on include the correct path to the sample InterBase data-
both the client and server. A second problem is that base. If you download the sample application that
the remote data modules that belonged to a single accompanies this article, you’ll need to change this
client are completely unaware of each other. There’s path before you can run it. (The sample application is
no way, for example, for one remote data module to available for download; see end of article for details.)
call a method of another remote data module instance
that belongs to the same client. The SQLDataSet component, named EmpDs, has its
SQLConnection property set to the EmpConn con-
Delphi 6 solves these problems by allowing the client nection component. Its CommandText property is set
application to share a single connection to multiple to SELECT * FROM Employee. The DataSetProvider
remote data modules in the application server. Build- component has its DataSet property set to EmpDs.
ing an application that takes advantage of this new
technology isn’t difficult once you’ve done it. How- The next step is to add a second remote data module
ever, it’s not accomplished just by dropping compo- to the server project. The second remote data module
nents or running wizards. To build an application that is shown in Figure 2. This data module contains a
uses a shared connection, you have to add interfaces single SQLDataSet component with a CommandText
to the type library on the application server, and add property set to: SELECT * FROM Customer. The data
properties to the interfaces. module also contains a DataSetProvider connected to
the SQLDataSet.
The best way to understand this process is to walk
through building a sample application; that’s what Now you must add the necessary interfaces and
this article does. Before reading this article, however, properties to the application server’s type library.
you should know how to build a multi-tier applica- Figure 3 shows the Type Library editor with the
tion using the MIDAS components in Delphi 5. IEmployeeDm interface expanded. To use a single
shared connection to access multiple remote data
Building the Server modules, one of the remote data modules must be
To build this server, create a new project and designated as the master, and all the other remote
add a remote data module to it. The sample data modules will be children. In this example, the
application for this article uses the sample Inter- employee remote data module will be the master and
Base database, and connects to the database using the customer remote data module will be the child.
dbExpress. Figure 1 shows the completed data
module. The data module contains a SQLCon- You must add a read-only property to the
Figure 1: The employee data nection component, a SQLDataSet component, master remote data module’s interface for
module. and a DataSetProvider. each child data module. In Figure 3, the
Figure 5:
The client
application’s
Figure 2: The customer remote
data module.
data module.
EmployeeRdm, has been added to this interface with its type set
to IEmployeeDm. Next, the Parent interface of ICustomerDm has
been changed to IChildDm. By changing the parent interface, you
allow ICustomerDm to inherit the EmployeeRdm property from the
IChildDm interface. You need to make the same change to the
Parent interface property of every child remote data module.
After clicking the Refresh button and closing the Type Library editor,
it’s time to add the code to the getter and setter methods for the
properties you’ve added. Start by going to the initialization section
of the child remote data module’s unit. There you will see the code
shown in the comment labeled “Before:”
initialization
// Before:
// TComponentFactory.Create(ComServer, TCustomerDm,
// _CustomerDm, ciInternal, tmApartment);
// After:
Figure 3: The child remote data module property added to the CustRdmFactory := TComponentFactory.Create(
master’s interface. ComServer, TCustomerDm, Class_CustomerDm,
ciInternal, tmApartment);
var
CustRdmFactory: TComponentFactory;
function TCustomerDm.Get_EmployeeRdm: IEmployeeDm; Building the rest of the client application is no different than building
begin any other multi-tier client in Delphi. Just connect a DataSource compo-
Result := FEmployeeRdm;
nent to each ClientDataSet, and connect your user interface components
end;
to the DataSource components.
procedure TCustomerDm.Set_EmployeeRdm(
const Value: IEmployeeDm); Conclusion
begin The SharedConnection component is a very useful addition to Delphi
FEmployeeRdm := Value;
if you’re going to build a multi-tier application. You can now share
end;
a single connection to multiple remote data modules. And, multiple
remote data modules can be designed so they can easily interact with
Building the Client each other by accessing the master remote data module’s properties and
The sample client application consists of a single data module (shown calling its methods, if necessary. Since the master remote data module has
in Figure 5), and a single form (shown in Figure 6). The data module a property that contains an interface reference to each child remote data
contains a DCOMConnection component with its ServerName property module, the master can access any child’s properties and call its methods.
set to EmpSrvr.EmployeeDm to connect it to the master remote data You can even have child-to-child calls by having a child use the property
module in the server application. of the master that refers to the interface of another child.
Below the DCOMConnection component, EmpDcom, is a Connection- The ConnectionBroker component is also a useful addition, because it
Broker component. This component is new to Delphi 6 and lets makes it much easier to use different connections in the same client.
you add a level of indirection between multiple ClientDataSet com- One example would be using a DCOMConnection component when
ponents and the connection component they’re connected to. This you are running both the client and the application server program on
lets you change the connection component that your ClientDataSet the same machine during development and testing and easily switching
components use by changing the Connection property of the Connec- to a SocketConnection, WebConnection, or CorbaConnection to con-
tionBroker component, instead of having to change the RemoteServer nect to a remote application server. The only flaw in ConnectionBroker
property of every ClientDataSet. component is that you cannot connect a SharedConnection component
to a ConnectionBroker. ∆
In this application it makes no sense to use a ConnectionBroker,
because there’s only one ClientDataSet. I included the Connection- The sample application referenced in this article is available on the Delphi
Broker here just to show how it is used. To use a ConnectionBroker, Informant Magazine Complete Works CD located in INFORM\2001\
just drop it on your data module and set its Connection property to OCT\DI200110BT.
the connection component you want to use. When you add a Client-
DataSet, you’ll notice that it has a new property, ConnectionBroker.
Just set the ConnectionBroker property of the ClientDataSet and
leave its RemoteServer property blank. The EmpCds ClientDataSet Bill Todd is president of The Database Group, Inc., a database consulting and
component on the data module has its ConnectionBroker property set development firm based near Phoenix. He is co-author of four database program-
to the EmpBrkr ConnectionBroker component, and its ProviderName ming books, author of more than 80 articles, a Contributing Editor to Delphi
property set to EmpProv. Informant Magazine, and a member of Team Borland, providing technical support
on the Borland Internet newsgroups. Bill is also a nationally known trainer and
Before you can connect a ClientDataSet to the customer provider in has taught Delphi programming classes across the country and overseas, and is a
the child remote data module in the server application, you need frequent speaker at Borland Developer Conferences in the US and Europe. Bill can
to add a SharedConnection component to the client’s data module. be reached at [email protected].
The SharedConnection component (refer to Figure 5) is named
By Alex Fedorov
T his series examines basic data querying techniques that allow us to use Microsoft
SQL Server 2000 XML features from Delphi applications. In Microsoft SQL Server’s
Transact-SQL (or T-SQL) language, these XML features are implemented in great part by
adding a FOR XML clause to SELECT statements. This month, we’ll continue our exploration
of the FOR XML clause and its various modes, e.g. RAW, EXPLICIT, AUTO, etc. We’ll also learn
how to use XML templates to separate our code from XML-based queries.
SELECT * FROM Products AS Product This query results in the XML document shown
FOR XML AUTO in Figure 1:
produces the following XML document (par- SELECT * FROM Products AS Product
FOR XML AUTO
tially shown here):
Figure 2: The same query as shown in Figure 1, except that the SELECT Customers.CustomerID, Customers.ContactName,
ELEMENTS argument has been specified. Orders.OrderID
FROM Customers INNER JOIN Orders
ON Customers.CustomerID = Orders.CustomerID
The ShowOrders procedure gets one node from the XML docu-
Instead of using columns and their data as attributes of the record-based ment, and iterates all child nodes to show the order details in the
element, we can create subelements from it. Change the previous query to: ListView component. The code to do this is practically identical
to the code we used to fill the ListView component in the RAW
SELECT * FROM Products AS Product mode example in the previous article. The only difference is that
FOR XML AUTO, ELEMENTS
here we need to extract only one node, and clear the contents
of the ListView component before showing our data (see Figure
to obtain the results shown in Figure 2. 6). The resulting application is shown in Figure 7. (The example
the tag of the current element, and Parent is used to define the
tree structure of the resulting XML document.
procedure TForm1.Button1Click(Sender: TObject); Now, instead of elements, we have attributes of the <Customer>
var
element:
URL : string;
I : Integer;
begin <Northwind>
URL := 'http://terra/northwind/templates/CustList.xml'; <Customer CustomerID="ALFKI"
XMLDoc.Async := False; CompanyName="Alfreds Futterkiste"
XMLDoc.Load(URL); ContactName="Maria Anders"/>
Root := XMLDoc.DocumentElement; <Customer CustomerID="ANATR"
for I := 0 to Root.ChildNodes.Length-1 do ...
with Root.ChildNodes[I].Attributes do
ListBox1.Items.Add(GetNamedItem('CustomerID').Text); This ends our brief discussion of XML queries in SQL Server
ShowDetails('');
2000. Here’s a brief review of the limitations of the FOR
ListBox1.ItemIndex := 0;
end; XML clause:
It’s only valid in the SELECT statement.
Figure 13: Executing an XML template. It can’t be used with subselects, or COMPUTE BY or FOR
BROWSE clauses.
There’s no support for aggregate functions.
procedure TForm1.ShowDetails(CustID: string); It can’t be used with cursors.
var
URL : string;
For additional limitations, see the complete list in SQL Server
begin
URL := 'http://terra/northwind/templates/CustDetail.xml'; Books Online, currently found at http://msdn.microsoft.com/
if CustID <> '' then library/default.asp?URL=/library/psdk/sql/portal_7ap1.htm.
URL := URL + '?CustID=' + CustID;
XMLDoc.Async := False;
XMLDoc.Load(URL);
Using XML Templates
Root := XMLDoc.DocumentElement;
So far we’ve seen how to use XML queries that were hard-coded
// Fill in edit boxes. in our application’s code. To provide more separation between
with Root.FirstChild.Attributes do begin the code and T-SQL queries, we can use XML templates. In
Edit1.Text := GetNamedItem('CompanyName').Text; general, an XML template is an XML document with a predefined
Edit2.Text := GetNamedItem('ContactName').Text;
structure. The following example shows a simple XML template:
Edit3.Text := GetNamedItem('ContactTitle').Text;
Edit4.Text := GetNamedItem('Address').Text;
Edit5.Text := GetNamedItem('City').Text; <ROOT xmlns:sql="urn:schemas-microsoft-com:xml-sql">
Edit6.Text := GetNamedItem('PostalCode').Text; <sql:query>
Edit7.Text := GetNamedItem('Country').Text; SELECT * FROM Customers FOR XML AUTO
Edit8.Text := GetNamedItem('Phone').Text; </sql:query>
end; </ROOT>
end;
Note that this XML document consists of two parts: the defini-
Figure 14: The ShowDetails procedure. tion of the root node and appropriate namespace, and a SQL
Let’s create an example that uses two XML templates: the first one
will return a list of Customer IDs, while the second will retrieve
details for the specified customer. Execute the first template in the
OnClick event handler for the button, as shown in Figure 13.
The ShowDetails procedure executes the second template, which
extracts the details for the selected client (see Figure 14). The
results are shown in Figure 15. Alex Fedorov is a Chief Technology Officer for Netface SA, based in Lausanne,
Switzerland (http://www.netface.ch). He was one of the co-authors of Professional
The important thing to note is that because we have separated the Active Server Pages 2.0 [Wrox, 1998] and ASP Programmer’s Reference [Wrox,
Delphi code from the SQL queries, through the use of XML templates 1998], as well as Advanced Delphi Developer’s Guide to ADO [Wordware, 2000].
we can easily change the queries without touching the code.
By Mike Edenfield
Console Applications
Part III: Message Queues, Threads, and More
O ver the last two months, we’ve discussed the details of creating and using a
console in your application. Now we’re ready to start using consoles in more
complex and useful ways. This article will describe ways to add consoles to your appli-
cation, or to enhance your console-only applications, using the techniques discussed
previously. In addition, it will demonstrate how to most effectively combine the console
API with other Win32 programming features — and how to avoid some potential pitfalls
along the way.
Message Queues
Most Delphi programmers are aware that each messages to a windowless message queue. Instead,
windowed application has a message queue, and Windows provides the PostThreadMessage function
each of its windows has a window procedure to for this purpose:
handle messages from that queue. Since typical
console applications don’t have windows, many function PostThreadMessage(idThread: DWORD;
people assume there’s no way to send messages to Msg: UINT; wParam: WPARAM; lParam:
the application, but this is simply not true. That LPARAM): BOOL; stdcall;
message queue can be used to receive messages even
when there’s no GUI window present. Other than taking a thread ID as opposed
to a window handle, PostThreadMessage
Figure 1 lists a small console application that functions exactly like PostMessage. We used
posts itself four messages. Windows will auto- GetCurrentThreadId to get our own thread’s thread
matically create one for us the first time we ID, but most often it will come from the
call any GDI or User function. The call to dwThreadID field of a PROCESS_INFO record, as
PeekMessage, one of the User functions, causes populated by CreateProcess.
Windows to create our message queue. The mes-
sage loop shown here is artificially simplified, Consoles and GUI Windows
since there’s no need to translate or dispatch the While console applications are — in many ways
messages once we receive them. Note that the — just as powerful and useful as GUI applica-
calls to PeekMessage and GetMessage both were tions, the simple fact is that Windows users like
passed an hWnd parameter of HWND_NULL graphics. The GUI is the most important aspect
(a constant defined in the .dpr file as 0). This of Windows, and of Windows programming.
causes all messages in the thread’s queue to be Fortunately, you don’t need to choose between
returned, regardless of what window (if any) they writing a console application or writing a GUI
were sent to. application. You can very easily mix and match
the two in the same application, to take full
Since PostMessage and SendMessage both take an advantage of all the features Windows (and
hWnd parameter, they’re no good for sending Delphi) provides.
Figure 1: This console application processes Windows messages. Once the child process is created, we can reset our own standard
output handle, and begin using our end of the pipe to read our
child process’ output. The API calls that we haven’t discussed yet,
which are used in this process, are:
More importantly, the VCL already contains much of the code
needed to use its objects in a console application. This means not function CreatePipe(var hReadPipe, hWritePipe: THandle;
lpPipeAttributes: PSecurityAttributes; nSize: DWORD):
only forms, but non-visual VCL objects, such as threads, synchro-
BOOL; stdcall;
nization objects, sockets — even the database components. The function DuplicateHandle(hSourceProcessHandle,
power of the entire VCL is available to you. hSourceHandle, hTargetProcessHandle: THandle;
lpTargetHandle: PHandle; dwDesiredAccess: DWORD;
To demonstrate, the sample program in Listing One (on page bInheritHandle: BOOL; dwOptions: DWORD): BOOL; stdcall;
function CloseHandle(hObject: THandle): BOOL; stdcall;
25) will create two TForm descendants and display them. By
including the Forms unit in our console application, we gain all
of the required Windows code to create forms and process their The parent process waits for the child process to finish before
messages. We use the VCL objects, such as Application, exactly trying to read its output and close any handles. This makes the
as we would in a forms-based application. TApplication even has parent process a bit simpler to program, but is by no means
an event, OnMessage, which permits us to handle thread messages required. The parent is free to read data from the anonymous pipe
not directed at a specific window (similar to the message loop in at any time. This is especially useful when redirecting standard
Listing One). input, as input can be sent interactively to the child process.
procedure TForm1.SetChildHandles; Mike Edenfield is an applications developer for Sylvan Learning Systems in
var
sa: TSecurityAttributes;
Baltimore, MD, and an MCSD. He has five years experience with Delphi and Visual
begin Basic, and specializes in Microsoft SQL Server development. He can be contacted at
{ We need this to force the user of our inherited [email protected].
stdout handle. }
AllocConsole;
{ Save the old standard handle first. }
hStdOutput := GetStdHandle(STD_OUTPUT_HANDLE);
{ Create the pipe, marking it inheritable. Then create a Begin Listing One — Creating Windows, Handling
non-inheritable copy of the read end of the pipe, so Messages
the child only inherits the write end. } {$APPTYPE CONSOLE}
sa.nLength := SizeOf(sa); program Listing2;
sa.bInheritHandle := True;
sa.lpSecurityDescriptor := nil; uses
Windows, Messages, Forms, SysUtils,
CreatePipe(hParentTemp, hChildStdOut, @sa, 0); Dialog1 in 'Dialog1.pas' {frmDialog1},
DuplicateHandle(GetCurrentProcess, hParentTemp, Dialog2 in 'Dialog2.pas' {frmDialog2};
GetCurrentProcess, @hParentStdout, 0, False,
DUPLICATE_SAME_ACCESS + DUPLICATE_CLOSE_SOURCE); var
{ The write end of the pipe becomes stdout. The other end hInput: THandle;
gets attached to our stream object for input later. } inRec: TInputRecord;
SetStdHandle(STD_OUTPUT_HANDLE, hChildStdOut); dwCount: DWORD;
fsPipe := THandleStream.Create(hParentStdout);
end; begin
{ Create two forms in the usual manner. The Forms unit
Figure 5: Creating a console, and an anonymous pipe, and ensures that the Application object is around to "own"
attaching one end of the pipe to standard output. the forms. }
Write('Creating the first dialog box...');
frmDialog1 := TfrmDialog1.Create(Application);
frmDialog1.Show;
{$APPTYPE CONSOLE} Writeln(' done.');
program Listing4;
Write('Creating the second dialog box...');
uses frmDialog2 := TfrmDialog2.Create(Application);
Windows, Forms, SysUtils, frmDialog2.Show;
Dialog3 in 'Dialog3.pas' {frmDialog3}, Writeln(' done.');
Dialog4 in 'Dialog4.pas' {frmDialog4}, Writeln('Press 1, 2, or 3 to change the dialog box. ' +
ConsoleThread in 'ConsoleThread.pas'; ' Press CTRL-C to exit.');
{ Handle the console input until the user cancels. In a
var real app, we would want to install a control handler to
thrInput: TConsoleThread; clean up our forms and such before exiting. }
hInput := GetStdHandle(STD_INPUT_HANDLE);
begin while True do begin
Application.CreateForm(TfrmDialog3, frmDialog3); { Avoid blocking on user input, so the forms have a
Application.CreateForm(TfrmDialog4, frmDialog4); chance to operate as normal. If we had a message
frmDialog4.Show; queue present, this would be a normal message
{ Hook thread's OnTerminate event, so we know when dispatch loop. }
to close. } Application.ProcessMessages;
thrInput := TConsoleThread.Create(False); if WaitForSingleObject(hInput, 0) = WAIT_OBJECT_0 then
thrInput.OnTerminate := frmDialog1.ConsoleOnTerminate; begin
Application.Run; ReadConsoleInput(hInput, inRec, 1, dwCount);
end. if (inRec.EventType = KEY_EVENT) and
inRec.Event.KeyEvent.bKeyDown then
begin
Figure 6: Placing the console input code into a separate case inRec.Event.KeyEvent.AsciiChar of
thread eliminates the need to call WaitForSingleObject or '1': begin
Application.ProcessMessages. Writeln('-> 1');
frmDialog2.opt1.Checked := True;
end;
'2': begin
By Bill Todd
GTSizer
Scales Forms to Fit and Look Good
I f you want your Delphi forms to automatically scale to any screen resolution, and
still look good, you need GTSizer.
You need a 17” monitor to get the same image monitor at 640x480. You need a 21” monitor
size at 800x600 resolution that you see on a 14” to get the same image size at 1024x768. Why
users don’t understand that I do not know, but
they don’t. Users seem to think that your appli-
cation should look the same on a 14” monitor
at 1024x768 as at 640x480, but it doesn’t work
that way.
GTSizer lets you override its automatic font size changes on a US Phone: (800) GENOTEX
component-by-component basis using its IgnoreTag property. Set International Phone: (602) 438-8647
the IgnoreTag property to any integer value, then set the Tag E-Mail: [email protected]
property of a component to the same value, and that component’s Web Site: http://www.genotechs.com
font size will not be changed. This is particularly useful with grids Price: GTSizer, US$124.95; upgrades, US$39.95 each.
or memo components in which you would rather let the user
see more data than the same data in a larger font when the user
increases the size of the form.
the GTForm component, however, you will have to assign the
The best news about GTSizer is that it is incredibly easy to handler to the GTForm component. If you do need to add, move,
use; just drop the GTForm component on each of your forms or resize components after you have dropped GTSizer on your
and you’re done. If you want to make it difficult for users to form, just set the GTUpdate property to True. GTSizer will store
make your forms look bad, you can set the GTForm component’s the new component information, and set the GTUpdate property
SizeBalancing property to True at run time. With SizeBalancing back to False automatically.
enabled, the height and width of the form retain their original
proportions as the user changes its size. Figure 1 shows a test GTSizer comes with an excellent online help file that covers every
form containing a frame at two different sizes with all components aspect of using the component. If you want to try before you buy,
visible and its proportions maintained. The GTForm component you can download a trial version of GTSizer from the GenoTechs
also provides a FormRestore method that you can call to restore the Web site. ∆
form to its original design size and shape.
There are only two things you need to keep in mind when design-
ing a form that you plan to use with GTSizer. The first is to set Bill Todd is president of The Database Group, Inc., a database consulting and
the Align property of all components to alNone. GTSizer cannot development firm based near Phoenix. He is co-author of four database program-
properly resize a form that has components with other Align set- ming books, author of more than 80 articles, a Contributing Editor to Delphi
tings. The second thing is to add the GTForm component to Informant Magazine, and a member of Team Borland, providing technical support
your form after the form is finished and tested. The GTForm on the Borland Internet newsgroups. Bill is also a nationally known trainer and
component needs to reassign the form level event handlers to has taught Delphi programming classes across the country and overseas, and is a
itself. This happens automatically when you add the GTForm frequent speaker at Borland Developer Conferences in the US and Europe. Bill can
component to a form for all of the event handlers that have been be reached at [email protected].
defined. If you add another event handler to the form after adding
By Ron Loewy
ExpressPrinting System 2
Create Hard-copy Output Based on Your UI
D elphi ships with a set of reporting components named QuickReport, and plenty
of third-party solutions are available, such as ReportBuilder, Shazam Report
Wizard, and ACE Reporter. As a casual user of some of these tools, I know you can
create beautiful reports, sometimes using great-looking visual tools that minimize
the coding. However, once you allow users to customize them, you’ll need to write
another user interface. This is often different from the user interface you supply users
to select, filter, or highlight records or items, so the users need to learn another
interface. Often, to save time and money, most of the selection and preparation
options you provide in the main code don’t find their way into the printing code.
In previous reviews I mentioned my satisfaction clicked on it to display the component editor. Using
with several Developer Express user interface prod- the Add button, I added a link to one of the grids
ucts: ExpressQuantumGrid, ExpressInspector, and on my form (a list of books from a database) and
ExpressBars. I recently had the chance to try named it BookPrinter. All I needed then, to provide
the newly introduced ExpressPrinting System printing of the books grid, was a simple call to the
(EPS), a set of VCL components that create hard- component’s Preview method passing two param-
copy output, and complement these and other eters: True to display the preview window as a
Developer Express products. Support for other modal dialog box, and the name of the report link
common Delphi components such as StringGrid I created (BookPrinter). I ran my program, clicked
and TeeChart is also provided, and if you own the the button, and there was my grid, including all
source code, creating new Report Links to other filtering information I assigned, shown in a preview
user interface components is possible. dialog box.
A key feature of other Developer Express products The same customization options I got during the
like ExpressBars and ExpressQuantumGrid is “design” of the report are available at run time,
easy-to-use customization. This design philosophy including definitions specific to the grid print order,
makes it easy for a developer to get started with a a definition of the titles, headers, footers, and fonts,
component in a hurry with a minimum of code. and a comprehensive page setup dialog box. I spent
Fortunately, the same visual customization tools are 20 minutes experimenting with header and footer
also available at run time, and allow the users to definitions, colors, fonts, and minimal customiza-
customize the product to their liking. This philoso- tion, and finished with an application that could
phy is still apparent in EPS. print attractive reports of the six different grids that
form the application’s user interface. It’s amazing that
My introduction to the product was brief. I in such a short amount of time an application can be
dropped a TdxComponentPrinter component on the enhanced with almost no learning curve (see Figures
form that houses my grid controls, and double- 1 and 2). And the product offers a lot more.
The Components
Installing the ExpressPrinting System adds five components to
Delphi’s Component palette:
Unlike most reviews, I started this one with a product I don’t already
own or know. As a result of my experience with ExpressPrinting
The ExpressPrinting System component publishing system brings System, my company purchased a copy for our use. Two of our
your user interface to print. Its ReportLink renders and prints applications already incorporate the printing capabilities, connected
visual controls, such as the ExpressQuantumGrid and Express- to the application’s grids.
MasterView, and other standard VCL controls like StringGrid and
RichEdit. Its design-time capabilities, and set of run-time design- If you already own QuantumGrid or MasterView, adding Express-
ers, help reduce the time you spend developing custom reporting Printing System should be an easy decision. If you don’t, it will offer
solutions. another advantage to differentiate it from the other grids and user
interface options available on the market. It’s definitely worth your
Developer Express consideration. ∆
6340 McLeod Dr., Suite 1
Las Vegas, NV 89120
Evil Numbers
I n a previous column, “Be Resourceful,” I covered why it’s good to avoid sprinkling string literals throughout your
code (in the July 2000 issue of Delphi Informant Magazine). As I made clear in that column, there are many
reasons to separate string literals out to a resourcestring section in a separate unit. But what about hard-coded
numbers? Should you avoid them, also? Yes, for two reasons:
1) Business rules can and usually do, eventually, change; and You would have a problem, because at least one of the array boundar-
2) The number of elements in an enumerated type, array or other ies would no longer match the code. A run-time error — or worse, a
range of values can change. In fact, almost any number you logic error without a run-time error — would be the result. To avoid
might use in your code is subject to change. that, it’s better to use Delphi’s Low and High functions when dealing
with range boundaries:
Even if a number doesn’t change throughout the software’s lifetime, you
make your code more readable by declaring a constant that makes the procedure Whatever;
number’s purpose clear. For example, instead of writing code like this: var
List1: array[0..3] of Double;
X: Word;
TransmissionDueDate := IncBusinessDays(Date, 2); begin
for X := Low(List1) to High(List1) do
It will be much clearer to the reader and maintainer of your code if ...
you declare a constant (preferably in a separate constants unit, named
YourProjectNameConst.pas): Referring to literal numbers in code, Steve McConnell wrote in his
classic book Code Complete, “A good rule of thumb is that the only
const literals that should occur in the body of a program are 0 and 1.”
... The literal number 0 is reasonably allowed because the index of the
BUSINESS_DAYS_IN_ADVANCE_TO_TRANSMIT_NACHA_FILES = 2;
first in a list of items likely will never change. For example, if you
have a string list named sl, the following code is acceptable:
Then replace the hard-coded number with the constant:
for i := 0 to sl.Count-1 do begin
TransmissionDueDate := IncBusinessDays(Date,
BUSINESS_DAYS_IN_ADVANCE_TO_TRANSMIT_NACHA_FILES); The literal number 1 is allowed for similar purposes. The same
example as shown above illustrates this. Starting from the index 0,
You could end up maintaining your code several months — even you don’t want to read past the last element in a string list, so you
years — from now, when the business rules and underlying logic of define Count-1 as being the upper limit of the loop.
your code may not be as obvious to you as they were when you wrote
it. Besides making the code more readable and thus maintainable, However, even the number 1 can normally be avoided. You can
this practice also is advantageous in that, when the business rules do do this by using Delphi’s Pred (predecessor) and Succ (successor)
change, you can simply modify the value of the constant in one place functions. Here’s an example of using Pred instead of -1:
(i.e. YourProjectNameConsts.pas), rather than search the entire body
of code, changing n instances of the hard-coded value. for i := 0 to Pred(sl.Count) do begin
...
In the following example, what would happen if you changed the
declaration of the array, but failed to change the lower and upper and of using Succ instead of +1:
bounds of the for loop?
if AnsiPos('^', s) > 0 then begin
XRefPortion := Copy(s, Succ(AnsiPos('^', s)), Length(s));
procedure Whatever; ...
var
List1: array[0..3] of Double;
X: Word;
begin McConnell also said in Code Complete, “Use named constants instead.
for X := List1[0] to List1[3] do Be a fanatic about rooting out literals in your code. Use a text editor to
... search for 2, 3, 4, 5, 6, 7, 8, 9, “, and ‘ to make sure you haven’t used
Example 2:
Clay Shannon is a certified Delphi 5 developer and the author of Tomes of Delphi:
strgrd.Canvas.TextOut(Rect.Left+StartPoint, Rect.Top+4, s); Developer’s Guide to Troubleshooting (Wordware, 2001). He also wrote the soon-to-
be-published novel Twisted Roads (whose protagonist is a Delphi developer). Readers
which is better as: may reach him at [email protected].
E ach year’s Borland Conference begins with a keynote speech featuring a theatrical event followed by a showcase
of the company’s development arsenal. This year, the theme was the Stanley Kubrick movie that includes the
current year in its title. Not surprising, but I don’t think anyone was quite prepared for Borland’s specific treatment of
the movie, borrowing ideas from its beginning and end.
Imagine this: While Richard Strauss’ powerful Also Sprach Zara- ered both Delphi and Kylix. Although a number of developers
thustra plays in the background, large, sculpted letters spelling wish the initial release of Kylix were more feature-rich, everyone
INPRISE become majestically illuminated. Suddenly, an ape-like appreciated that Borland fulfilled its promise to provide a truly
creature approaches these letters with a bone weapon in hand and cross-platform development solution.
smashes them all, eliciting tumultuous cheers from the audience.
Then, we see larger letters further back, proclaiming BORLAND. Now let’s turn our attention to a session I attended that had little
Powerful symbolism! educational value, but which will be among the most memorable
for those who attended it: Mark Miller’s “Fun with Delphi: Step
Continuing, we see the famous monolith from the movie and the Over to the Dark Side.” At this outrageous excursion into the
red mechanical eye of a computer, this one named DALE 9000. bizarre, our unique presenter gave out Dark Side T-shirts and other
The voice of the computer is (can you guess?) the company’s prizes, but didn’t do any of his frenetic coding improvisations.
CEO, Dale Fuller. The man in the gorilla suit is, of course, Instead, he treated us to several scandalous new applications:
David Intersimone. Following the impressive demonstration of The “Dr. Bob Hat Remover” searched a hard drive, found
Borland’s current products in one integrated e-commerce system, every utility by Bob Swart, located the graphic of Dr. Bob
Fuller heads into the audience to field questions. It’s a great begin- with his legendary hat, then cut off the top of that hat. The
ning to a great conference. companion “Dr. Bob Hat Replacer” then replaced the missing
hat with outlandish substitutes.
As always, the Borland Conference provided an opportunity to An unscrupulous new component, TShipItNow, designed to
renew friendships, make acquaintances, share development tri- distribute a bug-ridden application prematurely while disguis-
umphs and frustrations, sharpen technological prowess, and have ing its faults in a clever way. In the “demo,” instead of generat-
a great time. I had the opportunity to attend many informative ing the usual divide-by-zero exception message, TShipItNow
sessions as well as one unique presentation. brought up a dialog box that blamed the error on Microsoft
Word, Excel, etc.
Ray Konopka conducted a valuable vendor showcase called My wife’s favorite, and one to bring cheer to many of us in the
“Advanced Debugging with CodeSite,” which gave me several new United States next April, “Receipt Maker Pro 2000;” it asks the
ideas for using this wonderful tool. Konopka also presented two user to specify the amount of the refund he or she would like
excellent sessions related to Delphi’s ActionLists. Based on what I to receive for their income taxes and then generates the proper
learned, I may finally try using them in my own applications. Charlie receipts to make it happen.
Calvert’s session on “Graphics and Multimedia” was spellbinding and
demonstrated, again, his skill in presenting complex topics (such as Miller also shared a new online magazine called BYTE ME and its
OpenGL and related technologies) in a clear and entertaining way. first uninstall issue featuring an article called, “How to Reformat
Your Hard Drive.” Can you guess what operating system this was
I also attended several Kylix sessions, including one with Danny ridiculing? I cannot go into any details about Miller’s DotSuck
Thorpe that explored BaseCLX, the foundation of CLX (Compo- Domain extensions without sinking to a level of bad taste unchar-
nent Library for Cross Platform). Some sessions, such as Ray acteristic of this magazine. You’ll just have to ask around to find
Lischner’s insightful exploration of multitasking and threads, cov- out about that. The cleverest part of Miller’s presentation was a
CodeRush plug-in that used a fierce-looking Microsoft agent that ect JEDI. While hardly the most active, I was honored to be included
watched as Miller entered code, found and pointed out syntax errors, in that group. I cannot overstate the extent to which members of
and then made insulting comments to him. I want a copy of that! Project JEDI are excited and gratified by this recognition.
The high point was a special cartoon Miller created, called The There’s more to discuss: Delphi 6, Kylix, new directions in tech-
Borlands (based on The Simpsons) in which a former CEO of nology, and Project JEDI. I will explore all of these in future
Borland returns to take it over again and do all kinds of mischief. columns. For now, I will close with the slogan we have adopted in
At one point, we see a developer who looks a lot like Bart Simpson Project JEDI: May the source be with you. ∆
write over and over on the blackboard, “I will not hack the IDE.”
Miller even was able to incorporate material from the previous For an exciting visual tour of the conference, please see
night’s keynote speech. At the end, the audience was exhausted Marco Cantù’s excellent article on his Web site at http://
from laughing nearly continuously for an hour. Miller received www.marcocantu.com/development/borcon2001.
a standing ovation, something I have never seen at one of these
conferences in the past. — Alan C. Moore, Ph.D.