2004
Volume 10 Number 2
INSIDE
ON THE COVER
First Look
Delphi Informant
www.DelphiZine.com
FEATURES
Columns & Rows
ET
ADO.N
jects
hi 8 &
e Ob
r
o
C
Delp
e
is
r
p
s
r
Ente
Table s
Hash
ie
.NET Dictionar
ail
&
Hotm r
m
o
t
s
Cu oxy Serve
Pr
On the Net
13
.NET Developer
16
Lifecycle Management
21
REVIEWS
29 Doc-O-Matic Professional 3.4
Product Review by Robert Leahey
33
1
FIBPlus
D E PA R T M E N T S
2 Toolbox
37 File | New by Alan C. Moore, Ph.D.
T O O L B O X
DDC Offers Alternative Solution for Standard Delphi DataSnap/MIDAS
Server Environment
Decathlon Development Corporation
(DDC) announced the availability of
DDCAppService, an alternative solution for the standard Borland Delphi
DataSnap/MIDAS server environment. Using the latest Win32 server
technologies, DDCAppService offers a
scalable, reliable, and extendable environment for building n-tier database
applications.
DDCAppService improves on the
DataSnap architecture in 10 major areas:
DDCAppService is a Win32 service that runs 24x7. This enables
efficiencies such as database connection pooling without the need
for MTS/COM+ or ADO technologies. Several projects accessing
multiple databases can be hosted
in a single AppService, sharing
system resources.
At the I/O level, DDCAppService
is an IOCP-based socket server.
IOCP (I/O Completion Port) is an
advanced Win32 server technology
essential to effectively scaling any
server application.
An application layer IOCP-based
thread pool(s) runs separately
from the I/O thread pool. Communication between the two layers is done asynchronously for
a non-blocking optimized server
environment.
At the application level,
DDCAppService uses external
XML documents to define projects and their accompanying
DataSnap providers. Provider SQL
statements can be kept in these
documents so that as the SQL
changes, or as new providers are
added, no application needs to
be recompiled. Simply update the
XML document(s) and refresh the
AppService. For complicated business logic, DDCAppService Extensions are Delphi DLLs that plug-in
and enable one to link a provider
with a DLL function. With the
Extension class suite one can easily perform transactions, build and
execute custom SQL statements,
build and return custom standalone datasets, or complete any
task that the Delphi environment
enables. Accessing these Extension functions is done by simply
T O O L B O X
are background bitmaps, user-definable multi-row indentation, and scrollable tabs. Scrollable tabs offer three
button styles, which can even use
custom images. SftTabs/DLL 4.5 supports development of Wizard-style
dialog boxes and similar multi-page
dialog boxes, which can be switched
in response to other controls.
SftTabs/DLL 4.5 supports ANSI
and UNICODE character sets on all
supported platforms, Windows 95
through Windows Server 2003, allow-
The C# Programming
Language
Pervasive Software
Contact: info@pervasive.com
Web Site: www.pervasive.com
Errata
In my article Creating Multi-threaded Windows Services,
which appeared in the November 2003 issue of Delphi Informant Magazine, I made an embarrassing mistake and said
that you could protect multiple functions with a single critical section without having the critical section block access
to all the functions if one thread had entered one function.
This is incorrect. Using a single TCriticalSection object to
protect multiple functions has the effect of serializing access
to all of the functions. This is the way you want it. A critical
sections job is to protect data that is shared among multiple
functions. If it didnt serialize access youd be in big trouble.
Thank you to Aart Molenaar for pointing this out.
Matthew Hess
F I R S T
DELPHI 8
L O O K
First
Look
sional) those features appear, see the feature matrix available at www.Borland.com/Delphi.
A Familiar
New IDE
The most obvious new feature in Delphi 8 for .NET is
its IDE, shown in Figure 1. This IDE, which made its
first appearance in Borlands C#Builder, appears similar
to the IDE of Microsofts Visual Studio for .NET. It has
a nice, clean layout, and is highly configurable, giving
you a wide range of options with respect to where your
various tools float or
are pinned. (Pinning
is what many Borland
developers would refer
to as docking. In the
.NET world docking
refers to alignment.)
A closer inspection,
however, reveals its
unmistakable Borland
heritage. Anyone familiar with the Delphi 7
IDE will quickly find
themselves at home.
The tools, options, and
dialog boxes are generally accessed through
the same or similar
menu selections. For
example, the Run menu,
shown in Figure 2, is
almost indistinguishable from that found in
Delphi 7.
First
Look
handlers, such as a Button, Delphi adds the class reference to the WebForm class in the code-behind file, the file
that contains the Delphi statements that will be executed
on the server. An example of a WebForms in the designer
is shown in Figure 9.
Full .NET Support
Although there are many welcome similarities between the
Delphi 7 and Delphi 8 IDEs, there can be no doubt about
First
Look
First
Look
C O L U M N S
ADO.NET
&
R O W S
By Bill Todd
Columns
&
Rows
ADO.NET
Component
Transaction
DataAdapter
TDataSetProvider
Command
Parameter
DataReader
DataSet
DataTable
TClientDataSet
DataRow
Row in a TClientDataSet
DataColumn
Constraint
DataRelation
DataView
base. You can send the updates from one or more tables in
your DataSet object to stored procedures, and let the stored
procedures update the data tables if you want.
On the other hand, DataSnap does a lot of things for you
that ADO.NET doesnt. For example, when you have a master-detail relationship using nested datasets in DataSnap and
you call ApplyUpdates, the provider automatically applies the
changes in the correct order. It does the inserts to the master
table first and the detail table second, but it does the deletes
from the detail table first and the master second. ADO.NET
is not this smart. Its up to you to apply the updates in the
correct order, and you must write code to control the order in
which inserts, updates, and deletes are applied.
In brief, ADO.NET is more flexible but the flexibility
comes at a price. The price is that you have to write a lot
more code.
Creating a Connection
As with most things, the best way to learn the ADO.NET
objects is to use them. So Ill describe each component as
I use it in a demonstration application (the application is
available for download; see end of article for details). The
first step is to connect to a database; the database well use
is the sample EMPLOYEE.GDB InterBase database. Start Delphi 8 for the Microsoft .NET Framework, and select File | New
| Windows Forms Application from the menu.
Click the Data Explorer tab. Right-click InterBase in the list of
providers and choose Add New Connection. Enter Employee for
the Connection Name. Right-click the Employee connection and
choose Modify Connection to display the Connections Editor
dialog box (shown in Figure 4). Set the Database property to
the name of the computer where InterBase is running, followed by a colon, followed by the path to EMPLOYEE.GDB.
If InterBase is running on the same computer as Delphi, the
connection string might look like this:
localhost:c:\program files\borland\interbase\examples\
database\employee.gdb
DataSnap Components
Connection
Columns
&
Rows
database=DellInsp8000:c:\program files\borland\interbase\
examples\database\employee.gdb;assembly=Borland.Data.
Interbase,Version=1.1.0.0,Culture=neutral,PublicKeyToken=
91d62ebb5b0d1b1b;vendorclient=gds32.dll;provider=
Interbase;username=sysdba;password=masterkey
As you can see, the format is similar to an ODBC connection string. The string consists of several Name=Value
pairs separated by semicolons. The order of the
Name=Value pairs is unimportant. The Database property
gives the name of the database. For an InterBase database
this is the InterBase connection string, which is the server
name followed by a colon, followed by the path to the database file. Assembly provides the name, version, and public
key for the assembly that contains the BDP driver for the
database. For InterBase this is Borland.Data.InterBase.
VendorClient is the database vendors client library, in this
case, gds32.dll. Provider specifies the name of the BDP provider. Username and password are obvious.
Because the connection string is complex and differs from
one database to another, the easiest way to create one is
to create a connection in the Data Explorer and set all the
properties there. Even if you need to change the connection
string at run time, you can create a connection in the Data
Explorer, test it, then copy the connection string into your
code and add the logic to modify it at run time.
The Database property is a read only property that
returns the value of the Database parameter in the
connection string. It exists to provide an easy way to
get the database name. The ConnectionTimeout property
isnt currently supported. When implemented it will
let you specify how long to wait for a connection to
open before an exception is raised. The BdpConnection
component also has a run time read-only property
named State that returns either ConnectionState.Open or
ConnectionState.Closed.
Connection Pooling
The BdpConnection component doesnt support
connection pooling, but the Microsoft SqlConnection and
OleDbConnection components do. Pooling connections
lets many clients reuse each open connection. Opening a
connection is a relatively expensive operation, so pooling
connections can improve performance in the middle
tier of a multi-tier application. This reduces the number
of times a connection has to be opened and improves
performance.
Connection pooling is enabled by default for both the
SqlConnection and OleDbConnection components; however,
there is no reason to use connection pooling in a two-tier
application. Add the following attribute to the connection
string if you want to disable connection pooling in the
SqlConnection component:
Pooling=False
11
Columns
&
Rows
procedure ConnMainForm.EmployeeConn_StateChange(
Sender: System.Object;
e: System.Data.StateChangeEventArgs);
var
NewState: string;
begin
if (e.CurrentState = ConnectionState.Open) then
NewState := 'open'
else
NewState := 'closed';
StatusBar.Text := 'Connection ' + NewState;
end;
GetMetadata method
returns an ISQLMetadata
interface. The methods of
the ISQLMetadata interface
are listed in Figure 7. See
the online help for more
information about these
methods.
GetColumns
GetIndices
GetObjectList
GetProcedureParams
GetProcedures
GetProperty
GetSchemaTable
GetTables
Returning to the OnClick
event handler shown in
SetProperty
Figure 6, the code calls the
Figure 7: ISQLMetadata methods.
GetTables method for the
ISQLMetadata interface
returned by the BdpConnection. GetTables creates a
DataTable object and returns a reference to the DataTable.
Ill describe the DataTable object in detail in a later article
in this series. For now, just accept that a DataTable is an
12
O N
T H E
N E T
HOTMAIL
DELPHI 4-7
By Alfred Mirzagitov
On
the
Net
PROPFIND
PROPPATCH
BDELETE
MOVE
The client starts a login sequence to our POP server by sending the username and password. THotPopConnection forms
and sends a series of WebDAV commands to hotmail.com.
The first command contains the username and password
because Hotmail requires authentication. The rest of the
WebDAV commands dont have to pass the username and
password, since hotmail.com server keeps track of the clients.
Notice that we still need to introduce our own so-called
e-mail session for storing some basic client information and
Hotmail outbox URL. This information will be used later for
sending e-mails.
The first WebDAV command after a successful authentication retrieves Hotmail folder locations. There are more than
three folders, but all we need to know is the URLs of three
folders (or mailboxes): the incoming mail folder (inbox), the
outgoing mail folder (outbox), and the trash folder.
Once it knows the inbox location, THotPopConnection is ready
to retrieve e-mail statistics and download e-mail messages following the commands issued by an e-mail client application
talking to our POP server. The client commands are parsed by
the POP server, and translated into WebDAV commands. The
results of the WebDAV commands are translated to POP protocol responses and passed back to the client.
THotPopConnection keeps track of all the changes during
the transaction state, but there are no permanent changes to
Hotmail folders. When the client finally issues a QUIT command, all the changes are made permanent.
The SMTP communications are handled similarly, except
for authentication. When an e-mail client initiates an SMTP
connection, it doesnt provide a user ID or password. Hotmail, on the other hand, requires authentication. If we initiated, or if we just finished, a POP session for this specific
client, we can access the client outbox without authentication. But the question is how to identify the client. When
the client tries to send an e-mail, he provides his return
address. THotSmtpConnection checks if there is a valid email session for this return address and for this specific
IP address. If theres no active e-mail session, then SMTP
returns an error. For the SMTP session to work, therefore, it
On
the
Net
Username: john_doe
Password: **********
. N E T
.NET FRAMEWORK
D E V E L O P E R
.NET COLLECTIONS
By Xavier Pacheco
HashTable Methods/Properties
HashTable has several overloaded constructors. These constructors allow you to specify certain attributes, such as
the default capacity, load factor, hash code provider, and
comparer. Additionally, you can specify another IDictionary
instance from which to copy elements and serialization/
streaming objects for streaming customization. HashTables
load factor is the maximum ratio of elements it stores to
buckets. The default is 1. The capacity is the number of
buckets to allocate, based on this load factor. The comparer
is a class that implements the IComparer interface and is
used to determine the equality of keys.
The table in Figure 1 lists the various properties of HashTable.
The table in Figure 2 lists some key methods of HashTable.
Property
Description
Count
IsFixedSize
IsReadOnly
IsSynchronized
Item
Keys
SyncRoot
Values
.NET
Developer
A HashTable Example
Listing One (on page 18) illustrates the use of HashTable.
In this example, I store customer information, which is
indexed by a Customer ID, a string field. The customer
information is contained in the class TCustomer (lines 916). The TCustomer constructor (lines 37-45) simply populates the customer members with the information passed
in as parameters.
Line 49 creates a HashTable class instance. Lines 50-61 populate HashTable with information about various customer
contacts. Note that the first parameter to the Add method is
the Key by which items in HashTable will be located.
Lines 63-66 call the PrintCustomer procedure, which illustrates how to extract an entity from HashTable. PrintCustomer
takes a string parameter, the key to the entity to extract
and print.
Creating a Strongly Typed Collection
With my last article and this one, you have seen the various
collections that are available through the .NET Framework.
Each of these collections can hold a list of heterogeneous types.
You may have a need to contain a single type; in this case, it
would be advantageous to use a strongly typed collection.
There are two routes you can take to create a strongly typed
collection: the easy way, and the easier way. The easy way
is to create a class that implements the various required
interfaces: IList, ICollection, and IEnumerable. The easier
way is to use a class that already does this, CollectionBase.
Here are the steps required to create a descendant from the
CollectionBase class:
1) Create an indexed property to retrieve items in Collection.
2) Override type-checking events (OnInsert, OnSet,
OnValidate) and add code to raise an exception given
an invalid type (a type that is not a TStateInfo class).
3) Implement methods for working with the collection,
using the same standard names as IList and ArrayList.
Listing Two (beginning on page 18) illustrates a strongly
typed collection based on the TCustomer class example from
Listing One. In line 19, you see that TCustomerCollection
descends from the CollectionBase class. To follow Step 1) Ive
created Customer, which is an indexed property and uses
the getter and setter methods GetCustomer and SetCustomer,
respectively. The next step is to override the type checking
methods OnInsert, OnSet, and OnValidate. I simply wrote a
method, VerifyType, that ensures that the object being evaluated is of the type TCustomer.
The remaining methods are methods which must be overridden to actually manipulate the collection list. The collection list is an internal member, List, which is a class that
implements the IList interface. That is basically all there is
to defining a strongly typed collection. To use this collection
and add an item, simply execute code similar to this:
custDemo := TCustomerCollection.Create();
custDemo.Add(TCustomer.Create('Xavier Pacheco',
'Xapware Inc.', '719-573-4049', EncodeDate(2003,12,14)));
17
Method
Description
Add
Clear
Close
Contains
ContainsKey
ContainsValue
CopyTo
Remove
Synchronized
.NET
Developer
program d4dnHashTable;
{$APPTYPE CONSOLE}
uses
System.Collections, Borland.Vcl.SysUtils;
type
// Define a class to store in the HashTable.
TCustomer = class
ContactName: string;
Company: string;
PhoneNumber: string;
DateOfContact: TDateTime;
constructor Create(aContactName, aCompany,
aPhoneNumber: string; aDateOfContact: TDateTime);
end;
var
htDemo: HashTable;
procedure PrintCustomer(aKey: string);
const
fmtStr = 'ContactName: { 0 }, Company: { 1 }, ' +
'PhoneNumber: { 2 }, Date: { 3 }';
var
si: TCustomer;
begin
// First retrieve TCustomer item from the HashTable.
si := htDemo[aKey] as TCustomer;
// Print out its contents.
Console.WriteLine(System.string.Format(fmtStr,
[si.ContactName, si.Company, si.PhoneNumber,
si.DateOfContact]));
end;
{ TCustomer }
constructor TCustomer.Create(aContactName, aCompany,
aPhoneNumber: string; aDateOfContact: TDateTime);
begin
inherited Create;
ContactName
:= aContactName;
Company
:= aCompany;
PhoneNumber
:= aPhoneNumber;
DateOfContact := aDateOfContact;
end;
begin
// Initialize and populate the HashTable
htDemo := HashTable.Create();
htDemo.Add('SBEEBE',TCustomer.Create('Steven Beebe',
'Xapware Inc.',
'719-573-4049', EncodeDate(2003, 12, 14)));
htDemo.Add('MBRYANT',TCustomer.Create('Mark Bryant',
'Acme Inc.',
'123-456-7890', EncodeDate(2002, 3, 18)));
htDemo.Add('SADAMS',TCustomer.Create('Susan Adams',
'XYZ Corp', '456-234-8736',
EncodeDate(2003, 7, 22)));
htDemo.Add('PYOUNG', TCustomer.Create('Paul Young',
'ABC Associates', '987-654-3210',
EncodeDate(2000, 8, 1)));
// Print the HashTable items by Key.
PrintCustomer('SBEEBE');
PrintCustomer('MBRYANT');
PrintCustomer('SADAMS');
PrintCustomer('PYOUNG');
System.Console.ReadLine;
end.
18
unit CustColl;
interface
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76
77:
78:
79:
80:
81:
82:
83:
uses System.Collections;
type
// Define a class to store in the Collection.
TCustomer = class
ContactName: string;
Company: string;
PhoneNumber: string;
DateOfContact: TDateTime;
constructor Create(aContactName, aCompany,
aPhoneNumber: string; aDateOfContact: TDateTime);
end;
// Define the strongly typed collection.
TCustomerCollection = class(CollectionBase)
private
function GetCustomer(Index: Integer): TCustomer;
procedure SetCustomer(Index: Integer;
const Value: TCustomer);
procedure VerifyType(Value: TObject);
strict protected
// Type checking events.
procedure OnInsert(index: Integer;
value: TObject); override;
procedure OnSet(index: Integer; oldValue: TObject;
newValue: TObject); override;
procedure OnValidate(value: TObject); override;
public
constructor Create;
function Add(value: TCustomer): Integer;
function IndexOf(value: TCustomer): Integer;
procedure Insert(index: Integer;value: TCustomer);
procedure Remove(value: TCustomer);
function Contains(value: TCustomer): Boolean;
procedure PrintItems;
property Customer[Index: Longint]: TCustomer
read GetCustomer write SetCustomer;
end;
implementation
{ TCustomer }
constructor TCustomer.Create(aContactName, aCompany,
aPhoneNumber: string; aDateOfContact: TDateTime);
begin
inherited Create;
ContactName
:= aContactName;
Company
:= aCompany;
PhoneNumber
:= aPhoneNumber;
DateOfContact := aDateOfContact;
end;
{ TCustomerCollection }
constructor TCustomerCollection.Create;
begin
inherited Create;
end;
function TCustomerCollection.GetCustomer(
Index: Integer): TCustomer;
begin
Result := List[Index] as TCustomer;
end;
procedure TCustomerCollection.SetCustomer(
Index: Integer; const Value: TCustomer);
begin
List[Index] := Value;
end;
procedure TCustomerCollection.OnInsert(Index: Integer;
Value: TObject);
begin
VerifyType(Value);
end;
procedure TCustomerCollection.OnSet(Index: Integer;
OldValue: TObject; NewValue: TObject);
begin
.NET
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
Developer
VerifyType(NewValue);
end;
procedure TCustomerCollection.OnValidate(
Value: TObject);
begin
VerifyType(Value);
end;
function TCustomerCollection.Add(Value: TCustomer):
Integer;
begin
Result := List.Add(Value);
end;
function TCustomerCollection.IndexOf(
Value: TCustomer): Integer;
begin
Result := List.IndexOf(Value);
end;
procedure TCustomerCollection.Insert(Index: Integer;
Value: TCustomer);
begin
List.Insert(Index, Value);
end;
procedure TCustomerCollection.Remove(
Value: TCustomer);
begin
List.Remove(Value);
end;
function TCustomerCollection.Contains(
Value: TCustomer): Boolean;
begin
result := List.Contains(Value);
end;
procedure TCustomerCollection.PrintItems;
const
fmtStr = 'ContactName: { 0 }, Company: { 1 }, ' +
'PhoneNumber: { 2 }, Date: { 3 }';
var
i: Integer;
cust: TCustomer;
begin
for i := 0 to Count -1 do begin
cust := TCustomer(List[i]);
Console.WriteLine(System.string.Format(fmtStr,
[cust.ContactName, cust.Company,
cust.PhoneNumber, cust.DateOfContact]));
end;
end;
procedure TCustomerCollection.VerifyType(
Value: TObject);
begin
if not (Value is TCustomer) then
raise ArgumentException.Create('Invalid Type');
end;
end.
19
unit custdict;
interface
uses System.Collections;
type
// Define a class to store in the Collection.
TCustomer = class
ContactName: string;
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
Company: string;
PhoneNumber: string;
DateOfContact: TDateTime;
constructor Create(aContactName, aCompany,
aPhoneNumber: string;
aDateOfContact: TDateTime);
end;
// Define the strongly typed dictionary.
TCustomerDictionary = class(DictionaryBase)
private
function GetCustomer(key: string): TCustomer;
procedure SetCustomer(key: string;
const Value: TCustomer);
function GetKeys: ICollection;
function GetValues: ICollection;
procedure VerifyType(Value: TObject);
strict protected
// Type checking events.
procedure OnInsert(key: TObject; value: TObject);
override;
procedure OnSet(Key: TObject; OldValue: TObject;
NewValue: TObject); override;
procedure OnValidate(key: TObject;
value: TObject); override;
public
constructor Create;
procedure Add(key: string; value: TCustomer);
procedure Remove(key: string);
function Contains(key: string): Boolean;
procedure PrintItems;
property Customer[key: string]: TCustomer
read GetCustomer write SetCustomer;
property Keys: ICollection read GetKeys;
property Values: ICollection read GetValues;
end;
implementation
{ TCustomer }
constructor TCustomer.Create(aContactName, aCompany,
aPhoneNumber: string; aDateOfContact: TDateTime);
begin
inherited Create;
ContactName
:= aContactName;
Company
:= aCompany;
PhoneNumber
:= aPhoneNumber;
DateOfContact := aDateOfContact;
end;
{ TCustomerDictionary }
constructor TCustomerDictionary.Create;
begin
inherited Create;
end;
procedure TCustomerDictionary.OnInsert(key: TObject;
value: TObject);
begin
VerifyType(Value);
end;
procedure TCustomerDictionary.OnSet(Key: TObject;
OldValue: TObject; NewValue: TObject);
begin
VerifyType(NewValue);
end;
procedure TCustomerDictionary.OnValidate(key: TObject;
Value: TObject);
begin
VerifyType(Value);
end;
procedure TCustomerDictionary.Add(key: string;
value: TCustomer);
begin
.NET
Developer
90:
Dictionary.Add(key, value);
91: end;
92:
93: procedure TCustomerDictionary.Remove(key: string);
94: begin
95:
Dictionary.Remove(key);
96: end;
97:
98: function TCustomerDictionary.Contains(
99:
key: string): Boolean;
100: begin
101:
result := Dictionary.Contains(key);
102: end;
103:
104: procedure TCustomerDictionary.PrintItems;
105: const
106:
fmtStr = 'ContactName: { 0 }, Company: { 1 }, ' +
107:
'PhoneNumber: { 2 }, Date: { 3 }';
108: var
109:
cust: TCustomer;
110:
enum: IDictionaryEnumerator;
111:
KeyColl: ICollection;
112:
KeyArray: array of string;
113:
i: Integer;
114: begin
115:
KeyColl := GetKeys;
116:
SetLength(KeyArray, KeyColl.Count);
117:
KeyColl.CopyTo(KeyArray, 0);
118:
for i := Low(KeyArray) to High(KeyArray) do begin
119:
cust := Customer[KeyArray[i]];
120:
System.Console.WriteLine(System.String.Format(fmt
Str,
121:
[cust.ContactName, cust.Company,
cust.PhoneNumber,
122:
cust.DateOfContact]));
123:
end;
124: end;
125:
126: function TCustomerDictionary.GetCustomer(key: string):
127:
TCustomer;
128: begin
129:
Result := Dictionary[key] as TCustomer;
130: end;
131:
132: procedure TCustomerDictionary.SetCustomer(key: string;
133:
const Value: TCustomer);
134: begin
135:
Dictionary[key] := Value;
136: end;
137:
138: function TCustomerDictionary.GetKeys: ICollection;
139: begin
140:
Result := Dictionary.Keys;
141: end;
142:
143: function TCustomerDictionary.GetValues: ICollection;
144: begin
145:
Result := Dictionary.Values;
146: end;
147:
148: procedure TCustomerDictionary.VerifyType(
149
Value: TObject);
150: begin
151:
if not (Value is TCustomer) then
152:
raise ArgumentException.Create(Invalid Type');
153: end;
154:
155: end.
20
LIFECYCLE MANAGEMENT
MDA
ECO
By Glenn Stephens
Model-driven architecture redefines the way you as a developer will build an application. In todays programming
world you may be getting the requirements from business
analysts for an application. You may start designing the
structure of the application, whether its using Unified Modeling Language (UML) or another method. Once you have
designed your application, you can then go on to the development stage. At best, if youve been using UML and
your modeling tool supports it you may be able to generate code that represents your design.
21
So what is a model?
Basically, a model is
a preliminary work
or design that is the
basis for a final product. If you are familiar
with technologies
such as UML, you will
undoubtedly know
what a model is. If
you dont have a clue,
then think about the
term model for a minute. What you will
find yourself thinking
about are things such
as model airplanes,
model cars, small
designs of houses, and
other pieces of construction. In essence,
a model is a representation of something
that youre trying to
build. For software
development, the
model is an abstract
representation of what
you are trying to build
in your application.
Lifecycle
Management
Productivity
Boost
The main benefit of
MDA is productivity. As the model
generates a vast
amount of code
your application
will use, you spend
more time focusing
on ensuring that
your model is correct, and less time
programming the
plumbing aspects
of your application.
By spending more
time working on
the model, in most
cases your model
will reflect the actual system its trying
to represent. Many
companies that
have adopted MDA
in their development arsenal have
noticed productivity
improvements of
between 40 and 80
percent. Ive re-written applications that
I developed against
Figure 2: ECO model properties available
while designing a model.
databases, and have
converted them over
to MDA. The development of the system took 35 percent
of the time it took to build the original version.
Not only is the development of the application more productive, but because there are validation steps in building
the model, there are also improvements in testing that the
model is valid. As a result, the quality of your end application will be improved.
Reduced Cost
Applying MDA dramatically changes the way that applications are built and maintained, reducing not only the
cost of development, but also the cost of maintenance. A
common phrase you hear now in the post dot-com bubble
days is that as developers we have to do more with less.
By adopting model-driven development and MDA, you
will obviously be able to save time and money by developing the application faster. There are other ways that
adopting ECO will help to reduce cost.
Primarily, its in the maintenance of an application developed with ECO that is most likely to deliver significant
cost savings. In many cases, developers are so crunched
for time when building the application, that little documentation is generated. The documentation of the system
may be the initial project designs or it may consist of
comments in the code. Either way, there is insufficient
22
Lifecycle
Management
Lifecycle
Management
write in database applications is for managing the associations between entities. In the class diagram designer
you can select the association item from the Tool Palette
(again, see Figure 6), and draw a line between the classes
you want associated.
You will then have an association defined between the
two classes. By default, the association will be one-to-one
on both sides of the association. In the case of our model,
there will be one category to many products listed, so
you should change the multiplicity for the Products from
0..1 to 0..*, and change the names of the associations to
reflect the multiplicity values (see Figure 9).
Now we have a simple model that we can use in our
application, which has the appropriate properties and a one-to-many
association. You are now ready to
build the presentation layer for the
application.
Lifecycle
Management
procedure TWinForm.Button1_Click(Sender:
System.Object;
e: System.EventArgs);
begin
Category.Create(EcoSpace);
end;
25
Lifecycle
Management
Figure 11: Using the OCL Expression property editor to enter the OCL expression.
You should now be able to run the application, and create categories, and the products for the related categories.
You may notice that the DataGrid for your objects displays all the properties and associations for the classes
(see Figure 15), so you may want to modify the layout of
the DataGrid by modifying the TableStyles property to make
the forms look more presentable.
Making the Changes Persistent
Now that we have the application working, we need to
define how we will store our objects. The ECO framework
makes this extremely simple for us, and provides three
components for persisting the instances of our modeled
objects. These components are PersistenceMapperXML,
PersistenceMapperSqlServer, and PersistenceMapperBdp.
These components allow us to save our objects to an XML
file, a Microsoft SQL Server Database, and a Borland Data
Provider database, respectively.
26
Lifecycle
Management
Glenn Stephens
Lifecycle
Management
Glenn Stephens designs and develops applications for various platforms, and
has been programming for more than 15 years. He a Borland Certified Consultant, a Microsoft Certified Solution Developer, and is the author of The Tomes
of Kylix: The Linux API. Living in sunny Australia, Glenn spends his spare
time marathon swimming and playing the piano way too loud for his neighbors. Feel free to contact Glenn at glenn@glennstephens.com.au.
28
N E W
&
U S E D
By Robert Leahey
{ --------------------------------------------------------TtsUnitTestingClass.CompareFiles
Summary
Compares two files and logs the result.
Description
Call CompareFiles to use TestComplete's Files object to
perform a comparison. <p> CompareFiles calls Files.Compare
using aFileName1 and aFileName2 (see the TestComplete
online help for more information on the Files object.) If
Files.Compare returns false, CompareFiles gets the latest
error message from the Files object and posts it as part
of the error message sent to the log.
Note
The aMsg parameter is optional. If it is empty,
CurrentTestMessage is used instead, allowing this test to
be part of a test set (see StartTestSet). The test index is
only incremented if aMsg is empty and CurrentTestMessage is
used as part of the logged message.
Parameters
aFileName1 - a file to be compared; this file can either be
on disk on in the <i>Files</i> storage.
aFileName2 - a 2nd file to be compared.
aMsg - a message to be sent to the TestComplete Log object.
See Also
StartTestSet }
procedure TtsUnitTestingClass.CompareFiles(
const aFileName1, aFileName2, aMsg: string);
begin
...
end;
New
&
Used
Figure 2: HTML Help output of help topic generated from source code comments in Figure 1. Notice the
automatically generated hot links, and the way the different sections (Parameters, Summary, and Description) are presented.
At the time I reviewed Doc-O-Matic 1.0, I made the distinction that the product was not so much a documentation
authoring tool as it was a documentation generation tool.
This was based on the fact that version 1.0 of Doc-O-Matic
read code and comments from which it generated documentation output. If you wanted to incorporate topics that were not
part of your source code comments, you had to do so using a
hand-written text file containing markup tags. This is no longer the case; Doc-O-Matic is now a full-fledged help-authoring
package. It still features the same ability to generate help topics from code and comments, but it now also offers a built-in
topic editor, which allows you to manage and edit topics
from a variety of sources, including your source code, which
results in a sort of two-way documentation tool.
Before continuing my description of Doc-O-Matic, let me
first explain how Im using the product. My duties at AutomatedQA include writing technical white papers on how
to get more out of our products. The first paper I wrote,
published in Adobes PDF format, is an example of how
best intentions can go astray. As a long-time page layout
designer, I felt strongly enough about how my paper should
be laid-out that I chose to forgo any HTML output; I published only to PDF. Although PDF is an appropriate medium
for Web publication of print materials, the reality is that if
I want my papers to be read, I need an HTML version that
can be viewed online, a PDF version that can be printed,
and possibly even an HTML Help (or WinHelp) version that
can be downloaded.
30
Proof Positive
My white papers typically include example code or working classes, and Doc-O-Matics automation capabilities
would enable me to automatically generate the documentation for those classes. It would also automatically link
from my handwritten topics to the appropriate generated
source code symbol topics. As my body of work expands,
I can create standalone papers and a comprehensive tome
containing all of my papers. Each time I regenerate this
comprehensive work, including new material, all possible
relevant links will get added to the output, even in the
older papers that I havent changed. Pretty slick, huh?
One of the great features of Doc-O-Matic is that it automatically generates topics from source code. Doc-O-Matic
will parse any source code files you add to your documentation project and generate all the topics necessary to
fully document all the code symbols it finds. If you add a
Delphi project file (dpr or dpk), Doc-O-Matic will parse all
the units in that project. For instance, if it finds a class, it
will generate a topic for the class, as well as topics for all
the class members. It will create a listing of all the various
class members and generate links to the member topics. If
Doc-O-Matic finds comments that youve associated with
the code symbols, it will attempt to make these comments
the text of the topics it generates (more on this later).
Deciphering Comments
There are other products that serve to generate class
New
&
Used
New
&
Used
32
N E W
&
U S E D
By Bill Todd
FIBPlus
Native Components for InterBase and Firebird
33
New
&
Used
FIBPlus
which means that if you need to change the SQL statement you
must be very careful to keep the items that must be replaced
on the same line or your code will not work. FIBPlus solves
this problem by letting you write statements such as this:
SELECT * FROM @TABLE_NAME WHERE DEPT = :DEPT
New
&
Used
FIBPlus
Component
Description
pFIBDatabase
A connection to a database.
pFIBDataSet
A dataset in a database.
pFIBTransaction
A transaction.
pFIBQuery
pFIBStoredProc
pFIBUpdateObject
DataSetsContainer
pFIBErrorHandler
pFIBStatistic
SibFIBEventAlerter
FIBSQLMonitor
If you need to change the WHERE clause of your SQL statement at run time, use the FIBDataSets Conditions property.
The property editor lets you enter as many conditions as
you wish, with a name for each condition. At run time, just
set the conditions Enabled property to True to use it in the
WHERE clause. You can enable more than one condition
and the conditions will be logically anded together.
FIBPlus includes two unique components that let you centralize code. The first is DataSetsContainer. The FIBDataSet has
a Container property that can be set to a DataSetsContainer
component. When it is, all the FIBDataSets events are sent to
the DataSetsContainer component first. After the code in the
DataSetsContainers OnDataSetEvent event handler executes,
the event handler of the FIBDataSet component will execute.
The DataSetsContainers OnDataSetEvent event handler gets
two parameters. The first is the dataset that triggered the
event and the second identifies the event. If you need to
handle several events for each dataset, you will want to code
the OnDataSetEvent handler as a switch statement that calls
a custom method to handle each event type. Otherwise, the
OnDataSetEvent event handler would become very long.
The FIBErrorHandler component provides the same
functionality for database errors. It lets you write an
OnFIBErrorEvent handler that handles all exceptions
that arent caught by try..except blocks in your code.
Although InterBase supports array fields, Borland has
never produced a set of data access components that provide access to array field values. FIBPlus provides complete support for array fields by treating them as a variant
array of variants. Just assign the Value property of the
array field object to a variant variable, and manipulate it
as you would any variant array. Figure 5 shows a code
35
Component
Description
pFIBServerProperties
pFIBConfigService
pFIBLicensingService
pFIBLogService
pFIBStatisticalService
pFIBBackupService
Back up a database.
pFIBRestoreService
Restore a database.
pFIBValidationService
pFIBSecurityService
pFIBInstall
pFIBUninstall
New
&
Used
FIBPlus
F I L E
N E W
I especially like the manner in which the Sites page is organized according to programming language.
With over 1,300 Delphi/Kylix projects, SourceForge
(http://sourceforge.net) has become an essential source
of information and downloads for those who use opensource materials. This is the place where youll find most
of the projects under Project JEDI, all of the libraries
that TurboPower open sourced, GExperts, and more. See
my last column on open source (in the September 2003
issue) for more details, especially regarding TurboPower.
Youll find many projects related to common programming
areas, such as Web, database, and game development. To
quickly find projects in specific areas of interest, I recommend searching on Delphi and the particular topic.
Old, new, and improved. While the sites we just reviewed
are essential, there are many others old and new
worth visiting. In particular is Richeys Delphi-Box
(http://inner-smile.com/delphi5.phtml). The book review
section is better than most Ive seen, and I especially recommend the Resources page. I found several useful items that I
downloaded and about which you may be hearing more from
me in the coming months.
Many once venerable Delphi sites have languished
into obsolescence. One example is the City Zoo
(www.mindspring.com/~cityzoo/cityzoo.html) which
hasnt been updated since 1996 and is mainly limited to
information on Delphi 1. In contrast, www.delphi32.com
is up to date and strong on educational resources, with
articles, bug reports, a comprehensive series of book
reviews, and excellent Kylix coverage.
Another growing site with more current information is the
Delphi section of About.com that I mentioned last month
(http://delphi.about.com). One of the richest Delphi sites Ive
seen, it includes many interesting articles, including some on
usual topics. With its tutorial, this site is a must for newer
Delphi developers. There is a wonderful reference on the RTL
File
New
(run-time library), with convenient links to the main subcategories such as arithmetic routines, character manipulation
routines, date/time routines, various file-related categories,
string categories, and much more.
If I had to pick a most improved Delphi site, it would
be Marco Cantus at www.marcocantu.com. Youll find
all the code for Mastering Delphi 7 (see my review in
the July 2003 issue of this magazine) and many of his
other books. There are a couple of free Delphi books you
can download as PDF files, and chapters from some of
his older books, including Delphi Developers Handbook.
Chapter 15 of that important work (which is no longer in
print), Other Delphi Extensions, includes discussions of
such advanced topics as Delphi Notifications, the Tools
API, and Hacking the Delphi Environment. Although
some of the information in this book is now out of date,
much is still useful; I hope that Marco will find some way
to make more of this information available in the future.
One page on the site deserves special mention. If youre like
me and sometimes work on a project for many hours in a
row and need to take a break, go to Marcos Fun, Info and
Humor page. There youll find an excellent history of Delphi
from one of the gurus who was there at the beginning. You
can revisit Marcos legendary Fun Side of Delphi by downloading code that demonstrates Writing Useless Components
in Delphi. There are also links to other humor pages, such
as the article How to Write Unmaintainable Code!
Speaking of Delphi gurus, Bob Swarts Delphi 7 Clinic
(www.drbob42.com/delphi7) is also worth a regular visit.
Youll find interesting information here on the latest technologies and Delphi personalities. Another guru, Ray Lis-
38