Delphi Informant Magazine (1995-2001)
Delphi Informant Magazine (1995-2001)
DCOM Streaming
Passing Delphi Objects to a DCOM Server
ON THE COVER
6 DCOM Streaming David Body 29 Sights & Sounds
Automatic marshaling is terrific, but what if you need to pass data that Multimedia Buttons Christopher Coppola
isn’t automation-compatible? Mr Body shares his slick technique for Is the standard Button component ill suited to your way-cool inter-
using Delphi’s component streaming mechanism to pass entire objects face? Mr Coppola shares his techniques for creating buttons that
along the DCOM trail — and still not have to play marshal yourself. blend with the environment — and make a little noise.
By David W. Body
DCOM Streaming
Using Delphi’s Component-Streaming Mechanism
to Pass Objects through DCOM
Figure 2: A text listing of a form’s attributes. Figure 3: The interface of a TSystemStatus component.
the ability to report the system status of its host. From the File
Figure 7: The Get_SystemStatus function.
menu, choose New; then select the Automation Object icon from
the ActiveX tab, as shown in Figure 5. Enter a class name of
SystemStatus. Delphi will automatically create a type library for for a function named Get_SystemStatus, which you should
you, and display the Type Library Editor (see Figure 6). Note complete as shown in Figure 7.
that Delphi has created a COM interface named ISystemStatus.
This function simply creates an instance of TSystemStatus, calls
Click the Property icon to create a new property for the the GetSystemStatus method, then converts the TSystemStatus
ISystemStatus interface. Name this property SystemStatus, component into an OleVariant that can be returned as the func-
change its data type to OleVariant, and change the Property tion’s result. You will need to add the CompStream unit (again,
Type to Read Only. (Our TSystemStatus component is for see Listing One) to your uses clause before compiling the server
monitoring system status, not for modifying it.) Finally, click application. After the server application is complete, be sure to
the Refresh icon. Delphi will automatically create a skeleton run it once with the /REGSERVER parameter to register it.
8 March 1998 Delphi Informant
On the Cover
Practical Application
I have successfully used this technique to allow multiple users to
monitor and control complex calculation processes running on a
server. For example, the Iowa Legislative Fiscal Bureau uses a
Delphi application to project aggregate payroll costs for the
State of Iowa, using assumptions supplied by users. Multiple
users can simultaneously perform multiple projections. Each
projection uses actual payroll data from a database containing
approximately one million records. Depending on the size of the
specified population and the number of years being projected, a
single projection can take up to approximately 15 minutes to
complete. (Note: The server is a dual-processor 166MHz
Pentium Pro machine with 128MB of RAM running Windows
NT 4.0 and Microsoft SQL Server 6.5.)
Figure 8: The client application at run time.
The calculations required for each projection are performed
by a separate process running on the server, and are moni-
procedure TForm1.Button1Click(Sender: TObject); tored and controlled by a “calculator manager” application
var also running on the server. The calculator manager communi-
SS: ISystemStatus;
SystemStatus: TSystemStatus; cates with client applications running on user workstations
begin employing the technique described in this article. Performing
SS := CoSystemStatusAuto.CreateRemote(Edit1.Text); the calculations on the server in this manner improves perfor-
SystemStatus := _
VariantToComponent(SS.SystemStatus) as TSystemStatus; mance, decreases network traffic, and prevents sensitive pay-
try roll data from leaving the server during the calculations.
SystemStatus.DisplaySystemStatus(ListBox1.Items);
finally
SystemStatus.Free; Conclusion
end; Using the technique discussed in this article, you can easily
end; pass instances of any object derived from TComponent across
process or machine boundaries. The technique uses Delphi’s
Figure 9: The client application uses this code to create, use,
and release the DCOM server. built-in component-streaming support to convert a compo-
nent instance in one process into a data type that can be
automatically marshaled by COM/DCOM automation. We
Client Application pass the streamed component to the other process and con-
Now all we need is a client application that can retrieve and vert it back into a component instance, again using Delphi’s
display the system status from our server application. Our built-in streaming support. ∆
client application will have a screen layout like that shown
in Figure 8. Clicking the button will execute the code in The files referenced in this article are available on the Delphi
Figure 9. You’ll need to import the type library from our Informant Works CD located in INFORM\98\MAR\DI9803DB.
server application, and add the SystemStatus and
CompStream units to your uses clause before compiling.
uses Classes;
Finally, the client displays the system status of the remote
machine by calling DisplaySystemStatus, and frees the function ComponentToString(Component: TComponent): string;
TSystemStatus component. That’s all there is to using the function StringToComponent(Value: string): TComponent;
function ComponentToVariant(Component: TComponent):
TSystemStatus component to monitor the system status on a
Variant;
remote machine.
9 March 1998 Delphi Informant
On the Cover
By Dan Miser
L ast month, we offered a handy introduction of different ways to create and use
ActiveX controls within Delphi. This month, we’ll demonstrate how to deploy
those controls to a Web site, as well as how to debug your ActiveX controls.
Figure 2: Sample .INF file for an ActiveX control built with Using ActiveX in HTML
run-time packages. HTML syntax. ActiveX controls are frequently used in
HTML pages. The <OBJECT> tag is used to identify the con-
tions of all the files needed to make your ActiveX installation trol to the browser. In addition, several attributes are used for
complete. .CAB files allow multiple files to be distributed over this tag to help the browser display the proper ActiveX con-
the Internet in compressed form, much like a .ZIP file. If you trol to the client:
distribute many files, or if the file sizes are large, you may want CLASSID: CLSID registered to the ActiveX control
to distribute the files in compressed mode via the .CAB file. CODEBASE: URL used by the browser to download the
control to the client
Borland has code-signed some of their run-time packages and
placed them in .CAB files on their Web site (http://www.- For more information on this tag, see the HTML working
borland.com). Code-signing these packages proves that the draft at http://www.w3.org/pub/WWW/TR/-
run-time package files the end user receives are the same ones WD-object.html.
that Borland developed (see Figure 2).
Version control. If you deploy your ActiveX control with ver-
Code-signing. ActiveX controls pose a potential security risk sion information enabled, the browser will automatically
to clients who download them. For this reason, Microsoft has detect whether it needs to update the control. The syntax of
proposed a technology called Authenticode. Authenticode the HTML to take advantage of version control is to append
assures end users of the ActiveX control’s origin, and that it the version number to the CODEBASE attribute. For example,
hasn’t been tampered with since the control has been signed. the following HTML code will only download the control if
While this is not foolproof, it does provide some degree of the version number of the control is later than 1.1:
accountability for the majority of cases.
<OBJECT
CLASSID="clsid:7FD22F02-C0E1-11D0-9BB9-00A024604D21"
To obtain certification for your ActiveX control, you must CODEBASE=
contact an authorized Certification Authority (one such "http://dmiser.comps.com/calendarx.ocx#version=1,1,0,0"
>
company is VeriSign; http://www.verisign.com). You can
apply for a certificate; upon acceptance, the authorized
Certification Authority will return a certificate file you can Once the ActiveX control is deployed to a client, there will be
use to sign a control. two glaring features that need to be addressed:
1) How to use property names in HTML
For any serious ActiveX development, you must obtain the 2) Safety warnings when using the ActiveX control in
ActiveX SDK from Microsoft. In the ActiveX SDK you’ll find HTML
documentation, online resources, and a suite of programs to
help develop ActiveX content. For example, the program Assigning property names. Delphi-created ActiveX controls
MAKECER generates test certificates you can use for local implement the saving and restoring of property values
testing. For more information, visit http://www.microsoft.- through a standard persistence model. This provides a way for
com/activex. the ActiveX control to tell others about its properties. For
example, the HTML listing in Figure 3 is the result of adding
Deploy. When all the options have been properly set, select a Delphi-created ActiveX control to an HTML page using
Project | Web Deploy to execute the deployment. This will
Microsoft ActiveX Control Pad.
copy the ActiveX control, an .INF file and a .CAB file if <OBJECT ID="CalendarX1" WIDTH=320 HEIGHT=120
appropriate, and a skeleton HTML file to the directory speci- CLASSID="CLSID:7FD22F05-C0E1-11D0-9BB9-00A024604D21"
DATA="DATA:application/x-oleobject;BASE64,BS/Sf+HA0BGbuQCgJ
fied. If your Web site isn’t hosted locally, you can still deploy
GBNIVRQRjAJVENhbGVuZGFyAARMZWZ0AgADVG9wAgAFV2lkdGgDQAEGSGVp
all the files to a local directory and use the same method of Z2h0AngLU3RhcnRPZldlZWsCAAAASA=">
moving the files to your host directory after deployment. </OBJECT>
By Rod Stephens
Sounds Gud to Me
Soundex Encoding in Delphi
W hen you give your name at a restaurant, it doesn’t matter whether the
host spells it correctly. As long as the name can be pronounced to tell you
when your table is ready, it can be spelled any number of ways. In a large data-
base of names, however, spelling is critical. If you tell customer support that your
name is MacCauslin, it matters a great deal whether they type “MacCauslun,”
“McCawslin,” or “Mack Awzlin.” If the database is large, finding the correct
record by trial-and-error may be difficult. Even seemingly ordinary names like
Smith can have many different spellings: Smith, Smithe, Smyth, Smythe, etc.
This problem is common in large name- For example, suppose you want to encode
database programs such as customer-support the name STEPHENS. In step 1 you remove
hotlines, company phone directories, and the vowels, along with H, W, and Y, to get
census-data files. Manually searching for a STPNS. In step 2 you encode the letters to
record slows the operator, and increases the get 23152. This value has no adjacent dupli-
length of the call. Eventually the operator cates, so step 3 leaves the result unchanged.
may need to ask the customer for the correct The final code is S315.
spelling, further delaying the process — and
possibly annoying the customer. Compare this to the encoding of the other
common spelling of this name: STEVENS.
This article describes soundex, a method for Step 1 changes the name to STVNS.
encoding names based on how they sound, Encoding the letters in step 2 yields 23152.
rather than how they are spelled. Adding This contains no adjacent duplicates, so step
soundex to a database application can make 3 has no effect. The final encoding is S315
record retrieval faster and less frustrating for — the same value as the encoding for
both the operator and the customer. STEPHENS. This is the important property
of soundex encodings: Names that sound
A Sound Beginning similar have similar encodings.
Soundex methods come in several variations.
The United States Census uses one of the For another example, consider the name
simplest. The census represents a name using MacCauslin. Removing the vowels in step 1
a letter followed by three numbers. For leaves MCCSLN. Encoding the remaining
example, STEPHENS is represented by letters in step 2 yields 522245. Removing
S315. The rules for creating a census-style duplicates in step 3 leaves 5245, so the final
0 A, E, I, O, U, H, W, Y soundex encoding are: soundex encoding is M245. You can verify
1 B, F, P, V 1) Remove vowels and H, W, and Y, except that this is the same as the encodings for
2 C, G, J, K, Q, S, X, Z as the first letter. MacCauslun, McCawslin, and Mack Awzlin.
3 D, T 2) Encode the character according to the
4 L key in Figure 1. Figure 2 shows a Delphi function that
5 M, N 3) Remove adjacent duplicate digits. For returns a census-style soundex encoding for
6 R example, change 552331 to 5231. a string. The Soundex program available on
Figure 1: US Census Bureau 4) The encoding is the first letter followed this month’s Companion Disk uses this
soundex character encoding by the second, third, and fourth code to create census-style soundex encod-
key. numeric codes. ings (see end of article for download
15 March 1998 Delphi Informant
In Development
// Calculate a normal soundex encoding. // Calculate a numeric soundex encoding.
function Soundex(in_str: string) : string; function NumericSoundex(in_str: string) : Smallint;
var var
no_vowels, coded, out_str: string; value: Integer;
ch: Char; begin
i : Integer; // Calculate the normal soundex encoding.
begin in_str := Soundex(in_str);
// Make upper case; remove leading and trailing spaces.
in_str := Trim(UpperCase(in_str)); // Convert this to a numeric value.
value := (Ord(in_str[1]) - Ord('A')) * 1000;
// Remove vowels, spaces, H, W, and Y, except as the if (Length(in_str) > 1) then
// first character. value := value + StrToInt(Copy(in_str, 2,
no_vowels := in_str[1]; Length(in_str) - 1));
for i := 2 to Length(in_str) do begin NumericSoundex := value;
ch := in_str[i]; end;
case ch of Figure 3: Converting a string encoding into a numeric encoding.
'A', 'E', 'I', 'O', 'U', ' ', 'H', 'W', 'Y':
; // Do nothing.
else database. If you use a relational database, make the soundex
no_vowels := no_vowels + ch;
encoding a key, because you’ll often search for the encoding.
end;
end;
To find a name, the program searches the database for the
// Encode the characters.
name’s soundex code. If it finds more than one match, the pro-
for i := 1 to Length(no_vowels) do begin
ch := no_vowels[i]; gram can present a list of names for the user to choose from.
case ch of The program could even arrange the names so the most likely
'B', 'F', 'P', 'V':
match comes first. For example, suppose the user enters
ch := '1';
'C', 'G', 'J', 'K', 'Q', 'S', 'X', 'Z': STEVENS, ROD, and the best matches are STEVENS, MIKE
ch := '2'; and STEPHENS, ROD. If the program checks the soundex
'D', 'T': encoding of the first names as well as the last names, it can
ch := '3';
'L': conclude that the second entry is probably the right one.
ch := '4';
'M', 'N': Soundex is useful for tasks other than name finding. For
ch := '5';
'R': example, it’s sometimes used in address-matching software.
ch := '6'; Given a street address that may have been misspelled, the pro-
else // Vowels, H, W, and Y as the first letter. gram can use soundex to find possible correct street names.
ch := '0';
end; // End case ch. This type of software is usually customized, so it knows about
coded := coded + ch; the most common mistakes in street addressing. For example,
end; // End for i := 1 to Length(no_vowels) MAIN AVE N and N MAIN AVE may be the same street.
// Use the first letter.
out_str := no_vowels[1]; Soundex is also used in some spelling checkers. If the user
incorrectly types CRL, the program can ask if this should be
// Find three non-repeating codes.
for i := 2 to Length(no_vowels) do begin CARL, CAROL, CURL, CORAL, or CHORAL, because
// Look for a non-repeating code. these words have soundex codes similar to the code for CRL.
if (coded[i] <> coded[i - 1]) then
begin
// This one works. Optimization
out_str := out_str + coded[i]; Census-style soundex codes are four-character strings. You can
if (Length(out_str) >= 4) then make operations faster using integer codes instead of strings. You
Break;
end; can convert a soundex encoding into an integer by using the
end; code shown in Figure 3, which converts a soundex string encod-
ing into a numeric code. The example program named Soundex
Soundex := out_str;
end; uses this function to display numeric soundex encodings.
same code. For example, if your database has 600,000 records, // Replace instances of fr_str with to_str in str.
each code will correspond to more than 100 records (on the procedure ReplaceString(var str : string;
average). Because names are not evenly distributed (e.g. more fr_str, to_str : string);
var
names start with S than with Q), most codes correspond to fr_len, i : Integer;
even more names. Finding the correct name in a list of more begin
than 100 can be hard. fr_len := Length(fr_str);
i := Pos(fr_str, str);
while (i > 0) do begin
Short codes also mean the system cannot distinguish between str := Copy(str, 1, i - 1) + to_str +
long names that start the same way. For example, STEFFAN Copy(str, i + fr_len,
Length(str) - i - fr_len + 1);
and STEVENOWSKI both have codes S513 — even though i := Pos(fr_str, str);
only their beginnings sound alike. The program will group end;
these names together, while a human can easily tell they are end;
For similar reasons, this scheme sometimes assigns very differ- // Remove internal spaces.
ReplaceString(in_str, ' ', '');
ent encodings for names that sound similar. For example, the
code for PHISHMAN is P25, while the code for FISHMAN // Convert CHR to CR.
ReplaceString(in_str, 'CHR', 'CR');
is F25. It would be difficult for a program to realize that P25
and F25 represented names with the same pronunciation. // Convert PH to F.
ReplaceString(in_str, 'PH', 'F');
Rod Stephens is the author of several books, including Visual Basic Algorithms
from John Wiley & Sons, Inc. He writes an algorithm column in Visual Basic
Developer; some of the material presented here has appeared there in Visual
Basic form. Reach him at RodStephens@compuserve.com or see what else he’s
doing at http://www.wiley.com/compbooks/stephens.
P op quiz: Does Delphi’s Code Editor support keystroke recording and play-
back? If you answered “No,” you’re missing at least one feature that can
save you a great deal of effort. The fact is, very few Delphi developers use the
Code Editor to its full capacity.
Last month’s “DBNavigator” contained an in- Default, Classic, Brief, and Epsilon — define
depth look at Code Insight, but there’s a lot which features are available, and how you
more to coding productivity than that offered access them. The Default keystroke mapping
by Code Insight. Delphi’s editor has a wealth provides keystroke mapping that is CUA-
of features; so many, in fact, that I cannot fit compliant. Most Windows applications use
a detailed discussion of them all into this arti- these keystrokes, making this setting a good
cle. Instead, I’m going to highlight some of choice if you don’t already have a preference.
my favorite techniques, leaving you to discov-
er the remaining capabilities. The other mappings are provided to permit
you to customize the editor to emulate an
General Editor Issues editor you’re already familiar with. For exam-
Before we start to explore some of its fea- ple, the Classic keystroke mapping emulates
tures, a general comment about Delphi’s the editor-key combinations from Borland
Code Editor is in order. The editor has four Pascal. Likewise, Brief and Epsilon modes
keystroke mappings. These mappings — emulate the keystrokes of those editors.
Bookmarks are temporary; they are lost when you close the The Find matching delimiter feature of the Code Editor per-
file in which they are set. mits you to quickly locate the parenthesis, brace, or bracket
that corresponds to one you have chosen. For example, if you
Incremental Search place your cursor to the right of an open parenthesis, the Find
Delphi provides a wide variety of search options. Of these, the matching delimiter command will move your cursor to the
least used, though arguably the most interesting, is the incre- right of the corresponding close parenthesis. This feature is
mental search. What makes the incremental search so nice is particularly valuable when working with nested matching
that it’s quite easy to use. delimiters, such as function calls that serve as parameters to
other function calls. Using Find matching delimiter can help
When you initiate an incremental search, Delphi monitors you verify that your parentheses are placed correctly.
your keystrokes, and positions your cursor at the first char-
acter sequence within the unit that matches the characters To find the matching close delimiter, place your cursor to the
you have entered. You initiate an incremental search by right of the open delimiter and press CQ]. Likewise, to
pressing CE, or by selecting Search | Incremental Search find the matching open delimiter, place your cursor to the
from the menu. You conclude an incremental search by right of the close delimiter and press CQ[.
pressing E, or by navigating from the located string.
Column Operations
While you are performing an incremental search, Delphi Typically, block operations are based on rows. For example,
displays Searching for: in the status bar. As you type the earlier in this article you learned how to indent and unin-
characters of your search string, these too are
displayed in the status bar. If Delphi finds a
match to your entered string, but not what
you’re looking for, you can press 3 to ask
Delphi to search for the next occurrence of
the entered string.
Figure 5: By selecting a column block, it’s possible to delete the string “New”
from multiple lines, quickly converting these constant declarations.
Conclusion
The Delphi Code Editor provides a wealth of features that
support your code-writing activities. This article has dis-
cussed only a few of these features. By taking a few min-
utes to read Delphi’s online Help, you are sure to learn
additional tricks that can improve your productivity. ∆
By Chu Moy
The emerging technology of pure object the structure of object databases is orga-
databases promises dramatic improvements nized around the complexity of the data
in speed and development time. Some itself. An object schema can reflect real-
benchmarks have clocked ODBMSes per- world processes through inheritance, poly-
forming up to 2,000 times faster than their morphism, and encapsulation. Objects
relational counterparts. Once the exclusive have unique characteristics — such as iden-
province of C++ and Smalltalk programmers, tity and behavior — that have no equiva-
ODBMSes have lacked connectivity options lents in traditional relational theory.
for the RAD market. ODBMS vendors have Object-relational adapters add support for
addressed these issues by releasing GUI- multimedia data types to RDBMSes, and
based development tools and ActiveX and may also provide an object-like program-
ODBC interfaces to their products. ming interface; but the database engine still
operates with tables. Object databases
Delphi, with its fully object-oriented architec- extend the OOP paradigm from the pro-
ture and support for ActiveX type libraries (in gramming language to the database engine.
Delphi 3), is an excellent language for RAD
object database development. This article exam- In an ODBMS, data and procedures are
ines methods and tools for Delphi developers to grouped together as object properties and
design and access object databases. The code methods. It’s possible to view object data-
examples use POET ODBMS (http://www.- bases in relational terms (see Figure 1), but
poet.com). However, other ODBMSes, such as only at a superficial level. Broadly speaking,
Object Design’s ObjectStore (http://www.- the basic elements of an RDBMS have cor-
odi.com), and Computer Associates’ Jasmine responding elements in an ODBMS. Thus,
(http://www.cai.com), have similar connec- a table named Employee may contain
tivity options. records with columns such as Name,
Address, Empl_id, and Dept_id. An
The Object-Database Schema Employee object is an instance of an
Whereas relational databases store data in Employee class, and may have attributes
fixed tabular formats of rows and columns, such as Name, Address, and Empl_id.
23 March 1998 Delphi Informant
Columns & Rows
Object Term Relational Comment
Term
Database Database Object databases store objects and their relationships, with support for encapsulation,
inheritance, and polymorphism. Relational databases store record data in two-
dimensional tables. Relationships between tables are constructed with queries.
Class/Extent Table Classes are templates for objects. A class can have definitions for attributes and
methods. Tables use a row-and-column structure to hold data. An extent is the set
of all objects of a particular class.
Object Record An object is an instance of a class. It encapsulates data and behavior. A record is
a row of column data.
Object ID Row ID (RID) OIDs and RIDs are system-generated values that uniquely identify an object in an
(OID) ODBMS, or a row in an RDBMS. However, traditional RDBMS theory does not
require RIDs; some RDBMSes do not create them.
Attribute / Column Object-data attributes can map to table columns. Object relationships can be
Relationship mapped as foreign-key values to columns.
Method Stored Methods are associated with objects, and have the capabilities of the
Procedure programming language. Stored procedures are precompiled SQL code.
Event Triggers An ODBMS will usually have an object-management mechanism that can trigger
Management the execution of methods based on the occurrence of object events. RDBMSes use
triggers to execute SQL procedures on the occurrence of data events.
Index Index Object indices are conceptually similar to relational indices. The ODMG ODL
includes syntax to specify multiple keys for a class.
Figure 1: Object-relational structure mappings.
Object schemas generally support two types of relation- The first may sometimes be more flexible. Those databases in
ships: inheritance and associations. Inheritance simplifies compliance with the Object Database Management Group
schema design by reusing the design of the parent class. An (ODMG) standard may support its Object Definition
association is represented as a pointer to another object. Language (ODL) for defining schemas. Other products may
Thus, instead of a Dept_id attribute, an Employee object require the schema be defined according to programming lan-
would have a pointer to a Department object. In a relation- guage syntax such as C++.
al database, the relationship between the Employee and
Department tables would be constructed in a query that An ODL class declaration has two basic sections: the heading
uses Dept_id as a foreign key. An Employee object, however, and the body. The heading specifies the class inheritance, the
can find its associated Department object by going to the name of the extent (the set of all objects of that class), and
referenced memory address. The improvement in response keys for indexing. The body lists the class attributes and
time compared to an RDBMS is especially dramatic with methods. A script for defining an Employee class using ODL
complex data. might appear as follows:
OQL and SQL3 queries navigate object trees by specifying The Get/SetName and Get/SetAddress methods use the
the traversal path, and have more compact and efficient COM object PtObj to access an Employee object in the
query syntax. An OQL query operates on object sets. Because database, while the RequestSalaryIncrease method con-
the SalesRep class has two sets (a SalesReps extent and an tributes to the object behavior. When the application
Accounts set), an OQL query that traverses both sets must instantiates a PtEmployee object, it initializes the object by
specify them. The following OQL query is functionally iden- assigning the COM object to PtObj. PtObj already has its
tical to the previous SQL query: own accessor methods, but defining separate Get/Set meth-
26 March 1998 Delphi Informant
Columns & Rows
Programming with an ActiveX Interface The object schema of the sample application (see Figure 9)
The ODMG specification indicates that objects can be stored in shows how extents and object sets work together. In this
a database as named objects and/or extents. A named object con- scenario, each SalesRep is assigned one SalesMgr, and is
sists of a root object (which is assigned a name) and any associat- responsible for several accounts. Each account can be owned
ed objects. Once the root object is created, any objects referenced by several SalesReps. Each SalesMgr supervises several
by the root are automatically stored in the database. To retrieve SalesReps. The SalesRep class has a 1:1 relationship with the
an object, an application first retrieves the root, then navigates SalesMgr class, and a 1:n relationship with the Account class.
the object tree to reach the desired object. However, large num- The SalesMgr class has a 1:n relationship with the SalesRep
bers of named objects can make programming difficult, and slow class. The relationship between SalesReps and Accounts is
down the database. The alternative is to use extents. n:n, and is implemented as two 1:n relationships.
SalesRep.Account.SalesRep
Conclusion
Object databases are a natural fit for Delphi’s object-based
development paradigm. This article has examined only a few
object-database features available to Delphi, but has empha-
sized RAD connectivity techniques. From the simple begin-
nings discussed here, developers can build complex ODBMS
applications that enjoy the same advantages of speed and flex-
ibility as C++ or Smalltalk-based systems, yet can be designed
and coded in a fraction of the time. ∆
By Christopher D. Coppola
Multimedia Buttons
Creating Special Buttons for Special Interfaces
In this article, we’ll develop a TMMButton is to use the available interface components
component (see Listing Two, beginning on page — buttons, text boxes, menus, etc. — in a
33) that has all the functionality of a standard way the user can intuitively understand.
button, but integrates seamlessly with a creative,
high-resolution interface. The TMMButton One key to designing an intuitive interface is
component will also provide more feedback to to adhere to functionality standards. Interface
the user than a standard button. metaphors, such as the button, have been
employed for so long now that users intu-
Standards and Intuitiveness itively know how to operate a button.
When we design a user interface, our primary Therefore, when developing a button compo-
goals should be to make the interface func- nent, carefully consider every aspect of how a
tional, make the user comfortable, and final- button works.
ly, to make the interface aesthetically pleas-
ing. One of the fundamental challenges of The widely accepted standards for the func-
creating an interface with these characteristics tional operation of a button assert that the
OnClick event is only fired if the mouse but-
ton is released when the mouse pointer is
over the button. In other words, if I press a
button and (with the mouse button still
depressed) move the pointer away from the
component, the component should visually
return to its “normal” state, and shouldn’t fire
an event if I release the mouse button. A
good example of button functionality is the X
button that closes a window in Windows
95/NT. Figure 2 illustrates, in terms of
mouse events, how a button component
should function.
run time. At design time the logic is simple: If the “nor- Figure 4: The published interface of TMMButton.
Highlight
Chris Coppola is one of the founding principals of Advanced Creative
Technologies III, Inc., a multimedia and Internet development company.
Chris has written about multimedia development in Delphi 2 Multimedia
Adventure Set [Coriolis Group Books, 1996], Director 5 Wizardry [Coriolis
Pushed Group Books, 1996], and Visual Basic 5 Multimedia & Web Adventure Set
[Coriolis Group Books, 1997]. You can reach him at coppola@act-3.com or
http://www.act-3.com.
Disabled
Begin Listing Two — TMMButton Component
unit MMButton;
Figure 6: The four bitmaps of the sample button.
interface
end.
uses
Forms,
TestMain in 'TestMain.pas' {frmMain};
{$R *.RES}
{$R sounds.res}
begin
Application.Initialize;
Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end.
unit TestMain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, MMButton, ExtCtrls;
type
TfrmMain = class(TForm)
imgBack: TImage;
MMButton1: TMMButton;
procedure FormCreate(Sender: TObject);
procedure MMButton1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.DFM}
By John Ayres
Restoring Animation
Delphi Apps Can Exhibit Standard Minimize and Restore
D elphi does a great job of encapsulating the Windows API, insulating develop-
ers from some nasty and mundane requirements of Windows programming.
Due to this encapsulation, however, Delphi programs exhibit certain anomalies not
present in Windows applications written in other development environments; for
example, the lack of animation when a Delphi program is minimized or restored.
When a window is minimized, it displays a The OnMinimize and OnRestore events of the
series of animated rectangles, decreasing in size Application object seem like a good place to
and moving toward the task bar until the win- start. However, both of these events fire after
dow is fully minimized. The reverse occurs the Application object has been minimized or
when a window is restored from a minimized restored. If the Application object is mini-
state. Unfortunately, the way Delphi encapsu- mized before the main form is minimized,
lates a Windows application prevents this from the main form will simply vanish, and the
occurring with Delphi-created applications. minimizing animation will never appear.
Therefore, the OnMinimize event will not
When a Delphi application is created, there are work for our purposes. Conversely, the
two windows present: the main window, and a Application object must be restored before the
hidden application window. It is the hidden main form is restored, so the main form will
application window that contains the entry and be visible and the restoring animation will
exit points for the main function of the occur. We can then use the OnRestore event to
Windows application. When the main window run code that will restore the main form.
of an application is minimized, the “minimize” Another Application object event to consider is
message sent (i.e. SC_MINIMIZE) is inter- the OnMessage event. This event fires before
cepted by this hidden application window. the application is minimized, thus allowing us
Instead of minimizing the main window, it’s the to send the SC_MINIMIZE message to the
hidden application window that minimizes; the main form before it disappears.
main form and all other displayed forms are
hidden. When an application is restored, it’s the Therefore, we will create event handlers for the
application window that’s restored, and the Application object’s OnMessage and OnRestore
main form and all other forms are returned to a events to route the appropriate messages to the
visible state. Because the main window is never main form. We’ll also need a message handler
truly minimized, the minimizing animation for the WM_SYSCOMMAND message. Thus,
never fires. our TForm class declaration should appear as:
To correct this, we must re-route specific mes- TForm1 = class(TForm)
sages from the Application object to the main procedure FormCreate(Sender: TObject);
private
form, and provide additional processing to
{ Private declarations }
minimize and restore. The first step is to identi- public
fy where code must be placed to intercept and { Public declarations }
procedure AppRestore(Sender: TObject);
handle the appropriate messages. We will be
procedure AppOnMessage(var Msg: TMsg;
dealing with the WM_SYSCOMMAND mes- var Handled:
sage, which is sent when the user chooses Boolean);
procedure WMSysCommand(var Msg:
Minimize or Restore from the system menu, or
TWMSysCommand);
presses the appropriate button in the upper- message WM_SYSCOMMAND;
right corner of a window. end;
{ The Application.OnRestore event fires after the appli- { We must now minimize the application object. This
cation has been restored. This is fine for our purposes actually minimizes the application, hiding the
but the application doesn't send the Restore message to minimized form. }
the form. Our main form will reappear, but it will be Application.Minimize;
minimized in one of the lower corners behind the task
bar. Thus, we must provide a handler for the OnRestore { Indicate that the message was handled. }
event so the Restore message can be sent to the form. } Msg.Result := 0;
Application.OnRestore := AppRestore; end
end; else
{ For any other message, we must send it to the
inherited message handler. Because the form is
When the Application object gets WM_SYSCOMMAND (indi- already minimized, the Restore animation will work
cating a minimize or restore action), the message is handled by correctly. }
inherited;
the Application object, then destroyed (the main form never sees end;
the message). The OnMessage event of the Application object will
fire before the message has been handled and destroyed, allowing This should cause animation to appear when minimizing the
us to send the message to the main form before it’s hidden. In form. When the application is restored, the main form will reap-
the OnMessage event handler, determine if the message is pear, but it will remain minimized unless it’s specifically told to
WM_SYSCOMMAND, and if it indicates the application is restore. To do this, we post the WM_SYSCOMMAND message,
being minimized. If so, call the SendMessage API function to pass specifying the restore command (SC_RESTORE) to the main
this message to the main form: form from the application’s OnRestore event handler. No other
processing is required; Windows takes care of the rest:
procedure TForm1.AppOnMessage(var Msg: TMsg;
var Handled: Boolean);
begin procedure TForm1.AppRestore(Sender: TObject);
{ The OnMessage event fires before the OnMinimize event, begin
so we must put code here to route the Minimize message { When the minimized application is restored, the appli-
to the form. If the message is coming from the cation object simply sets the Visible property of its
system menu... } forms to True; the forms don't actually receive the
if Msg.Message = WM_SYSCOMMAND then Restore message. Therefore, when the application object
{ ...and it is specifically the Minimize command... } receives the Restore message, we must specifically send
if (Msg.wParam and $FFF0) = SC_MINIMIZE then it to the main form. }
{ ...send the Minimize message to the form. } PostMessage(Handle, WM_SYSCOMMAND, SC_RESTORE, 0);
SendMessage(Handle, WM_SYSCOMMAND, SC_MINIMIZE, 0); end;
end;
Your Delphi apps will now exhibit the minimizing and restoring
This message will be intercepted by our WM_SYSCOMMAND animation common to Windows apps. But this method isn’t per-
message handler. We must check the incoming message type fect; the animation will fall to one side of the screen instead of
to determine if it’s SC_MINIMIZE. This handler will receive animating toward the icon on the task bar. However, this tech-
all system command messages; check for and handle only the nique does provide a more traditional Windows look and feel. ∆
appropriate command. All other commands must be sent to
the inherited message handler so the standard functionality of The files referenced in this article are available on the Delphi
a window won’t be compromised. When checking the Informant Works CD located in INFORM\98\MAR\DI9803JA.
CmdType member of the TWMSysCommand structure passed
to our handler, combine it with the value $FFF0 using the
Boolean and operator. The least significant four bits of this
member are used internally by Windows, and we must mask
John Ayres is a consultant for Ensemble Systems Consulting in Dallas, using
them out to determine the appropriate message type. When
Delphi to produce high-end client/server applications for various Fortune 500
we’ve received SC_MINIMIZE, pass the message information companies. With over eight years of programming experience, he’s worked for a
to the Def WindowProc API function, which performs the variety of companies, producing a broad range of software, from third-party
default behavior of the specified message on the window add-in utilities to games. He keeps himself busy by co-authoring The Tomes of
whose handle is passed to the function in its first parameter. Delphi 3: Win32 Core API (ISBN 1-55622-556-3) [WordWare, 1998] and
This function causes the form to truly minimize, and the other Windows programming books for Delphi. For more information, visit
minimizing animation to appear. Then, call the Minimize http://www.WordWare.com.
method of the Application object, which minimizes the
By Warren Rachele
Report1.Execute;
Run the sample program and you see the form window
with a button. Clicking the Report button starts the report-
generation process. The engine loads quickly (without a
splash screen) and is printed to the screen for preview by
default. The Crystal Reports Engine window automatically
provides the user with full preview capabilities: zoom, page Figure 4: Selecting categories in the Create Report Expert
by page, top of report, and end of report. A button sends dialog box.
Seagate Toolbox
Seagate provides tools to make end-user
access to your data stores easier and
more secure. The Crystal Dictionary
system provides the database profes-
sional with the ability to make their
data more accessible to users by format-
ting the data elements into more
friendly terms while limiting access to
the level needed by the individual.
Users are often unable to access a data-
base directly because of their lack of
understanding of the relational model.
A database made up of multiple inter-
Figure 6: The report is configured in a WYSIWYG window. related tables is easily understood by
the database designer, but the relation-
ships aren’t always clear to the end user.
By Peter Hyde
H ave you ever noticed the way users encounter a new way of doing things,
grow to like it, and suddenly it has to be in every application you create?
Of late, full-text searches, like those popularized by sites such as AltaVista
(http://altavista.digital.com), have entered the ranks of the “must have.”
This is no mere peccadillo on the part of the There is a trade off, of course. Such an
user. For many classes of applications, there engine depends on generating, maintaining,
are clear benefits to using a full-text database and using indexed word tables for each
search, rather than (or as well as) a more database — a job that can be very resource
structured and formal SQL-style one. Users hungry in memory, disk and network space,
find keyword-based searching easy to learn CPU terms, and time. Having gone down
and master because they can dig into the con- this track more than once, I’ve often wished
tent of BLOB fields containing entire docu- for a component set that provides peak
ments and, above all, a well-implemented search performance and flexibility while
search can provide unsurpassed performance. keeping a close eye on resource usage.
Nor is there anything to prevent a savvy
developer from implementing hybrid search- Enter Rubicon from Tamarack Associates. I
es, i.e. those that use keywords to quickly first saw it in action at a pre-launch demon-
narrow the target range, then apply more tra- stration of the Delphi resource site at
ditional comparisons on fields such as dates http://developers.href.com (see Figure 1),
or prices to produce the final result set. where it was performing searches on 300MB
InterBase tables in under half a second.
(Now that the site has closer to a gigabyte of
information, you can go and check its speed
for yourself.) Tamarack claims speed
improvements of up to 5,000 times com-
pared to SQL queries — and the larger the
database, the better it gets.
Flexibility
Rubicon gets full marks for speed and economy.
What about flexibility? Well, power users won’t
be disappointed. In addition to words, phrases,
and simple Boolean expressions, Rubicon also
supports expressions such as “search near
Delphi” or “like angle”, and reasonably complex
combinations such as “like deficit or like loss”.
Figure 2: Rubicon located four of my articles — in a Paradox database — Critical issues such as lookup fields have not
across a LAN in just over 1/100th of a second. been overlooked: Rubicon lets you define table
links so words coming from the lookup tables
Economy and Speed can be indexed, not just words from the main table.
Rubicon does better than this: Instead of storing record Additional features such as ranking, word hints, search
numbers, it simply maintains a bitmapped array of record narrowing, and on-the-fly results mean that applications
markers. If a word is present in a record, its bit is turned using Rubicon will have a friendly and finished feel. None
on; if not, it’s turned off. Immediately, the maximum of these features would be worth having if Rubicon were
BLOB size in each word-table record is reduced from weak in the critical area of index creation and mainte-
roughly 400KB to 12.5KB. Not only that, but smart bit- nance. No database or Web application would survive long
style operators are very fast and easy to use when perform- in production if it had to be shut down for long periods so
ing “and” and “or” operations in multi-word searches. In the index could be regenerated. Fortunately, again,
effect, each Rubicon word ends up with a sparse array of Rubicon shines.
bits, which leads to another optimization: The best thing to
do when storing sparse arrays is to compress them. Rubicon Highly Scalable
will typically compress that 12.5KB to under 1KB, option- To begin with, Rubicon provides not one, but two index-
ally in memory as well as on disk. The result is a search creation components. TMakeDictionary is used for initial
tool that’s skimpy on resources without sacrificing speed. index-table creation and also (desirably) whenever extensive
To test this, I ran its demonstration program against a changes have been made to the database or external files
2,000-article database (approximately 6MB of data). being indexed. It is typically run in batch mode, perhaps
Rubicon took a minute to create an index table of 2MB, overnight for very large jobs. For minor edits, updates, or
then performed multi-word searches that located matching deletes, the TUpdateDictionary component is much quicker
articles in under 1/50th of a second (see Figure 2). Only as it works by amending the index rather than rebuilding it.
the optional creation of a result table of matching records (A list of key components is shown in Figure 3.)
Class Description
TMakeDictionary Used to scan the records of the source table, and create or recreate a dictionary of all the
words used in the selected fields and their record locations in the table. Network, threaded, and
huge table versions come with Workgroup and Professional editions.
TUpdateDictionary Keeps a dictionary synchronized with changes in the source table. Network, threaded, and huge
table versions come with Workgroup and Professional editions.
TSearchDictionary Performs word searches using the dictionary. A huge table version comes with the
Professional Edition.
TMakeProgress A drop-in form that will automatically configure itself to display the progress of TMakeDictionary.
TUpdateStats A drop-in form that will automatically configure and display itself when TUpdateDictionary is
used. It’s primarily designed for monitoring the update process during development.
TUpdateTable A descendant of TTable that has several key TUpdateDictionary interface methods built in.
TUpdateDictionary checks to see if its DataSource.DataSet is a TUpdateTable, and, if so, will auto-
matically connect the appropriate methods.
TRubiconRichEdit A read-only control that can display text or RTF files with matching words highlighted. The behavior
of the control is similar to a TDBMemo or TDBRichEdit, but is only available on 32-bit platforms.
TSearchHints A TCustomGrid descendant that displays words that match or nearly match the words the user
has entered into an edit control. The words displayed may be updated as the user types, thus
giving the user instant feedback.
TSearchController Performs a search on multiple tables (Professional Edition only).
Figure 3: Key Rubicon components.
Help
Rubicon’s documentation includes an excellent introduc-
tion to the components and the way they should be used,
a “reference” section that alphabetically lists the compo-
nents, properties, and methods with clear examples, and
information on advanced topics, such as huge tables,
linked lookup fields, working with TQuery, memory
management, threading, and much more. In contrast,
the online Help is a little disappointing. It’s a straight
facsimile of the manual, meaning the layout and hyper-
links are somewhat idiosyncratic. Most frustrating of all is
that it’s not context sensitive in Delphi 3. (The other
compilers are fine.)
Conclusion
Full-text searching is a very useful and oft-requested search
strategy, yet not at all easy to implement efficiently. Tamarack
Associates has tackled the job with intelligence, flair, and
thoroughness. Search no further. ∆
44 March 1998 Delphi Informant
TextFile
I n an editorial in the December, 1997 issue of Delphi Informant, I discussed the need to make new tech-
nologies and their APIs available in Delphi and the Jedi movement that is attempting to do just that. But
what about the standard APIs? Since Delphi has implemented a large part of the Windows operating sys-
tem, do we need to know the gory details of Windows functions and messages? In everyday programming,
probably not. However, if we’re extending components or accessing functionality not supported by Delphi,
we might need detailed information. Where can we find it?
In the bad old days BD (Before new areas include threads and the reg- low-level API function calls used when
Delphi), if you were programming for istry. I found one of the last chapters working with audio files (WAV and
Windows in C or Pascal, you needed to particularly interesting: “File MIDI), AVI files, and the Media Control
work at the API level to a greater extent. Decompression and Installation.” If Interface (MCI). While not indicated in
In those 16-bit days, a very popular ref- you’re interested in building a set of the title, this volume also discusses the
erence work was Windows API Bible by installation components as part of a Telephony Application Programming
James L. Conger [Waite Group Press, setup-generating system, you’ll find Interface (TAPI).
1992]. This single volume contained much of what you need here: functions
the essential information of the time: to check version information, functions If you’re planning to do a significant
the message system, the various stan- to manipulate compressed files, and amount of low-level work at the API
dard controls, input/output, and even functions to determine what files level, either in application programming
multimedia and communications. (DLLs) are installed on a user’s system. or component creation, you should con-
Today, the existing APIs have grown sider adding one or more of these vol-
considerably, and new ones have been Windows 95 Common Controls and umes to your Delphi library. These ref-
added to the standard package. To keep Messages API Bible presents the controls erences include numerous code exam-
up, the Waite Group’s Bible, written inherited from Windows 3.x and the ples to show how to access particular
mainly by Richard J. Simon with help new ones added with Windows 95. It functions. While the code examples in
from Michael Gouker and Brian Barnes, also includes a complete reference to this series are in C, you should be able
jumped to three volumes between 1996 Windows messages. If you want to learn to learn something from them. ∆
and 1997. Let’s examine each. more about the new controls, such as
toolbars, status bars, tree views, and — Alan C. Moore, Ph.D.
Windows NT Win32 API SuperBible rich-text edit controls, and extend their
provides an excellent introduction to functionality, you’ll find this volume Alan Moore is a Professor of Music at
Windows 32-bit programming — both particularly helpful. Kentucky State University, specializing in
Windows 95 and NT 4.0 — and music composition and music theory. He has
includes an explanation of how an The final volume, Windows 95 been developing education-related applica-
application qualifies for the Windows Multimedia and ODBC API Bible, pre- tions with the Borland languages for more
95 logo. This first volume includes a sents Windows’ current support for than 10 years. He has published a number
discussion of windows and dialog boxes, Multimedia and ODBC. The latter topic of articles in various technical journals.
menus and other basic interface objects, is presented first and provides informa- Using Delphi, he specializes in writing cus-
message systems (messages themselves tion on connecting to various DBMSes, tom components and implementing multi-
are in the second volume), memory executing SQL statements, converting media capabilities in applications, particu-
management, input/output, graphics, data, and more. The multimedia section larly sound and music. You can reach Alan
and system information. Some of the provides a detailed account of high- and via e-mail at acmdoc@aol.com.
46 March 1998 Delphi Informant