Beruflich Dokumente
Kultur Dokumente
2003
Volume 9 Number 3
INSIDE
O N
T H E
C O V E R
.NET Tech
Delphi Informant
www.DelphiZine.com
F E A T U R E S
Greater Delphi
16
Page 8
Page 16
A Component to
$5.99 US
$8.99 CAN
Replace BatchMove
Page 24
Page 34
Replacing BatchMove
Foundation Class
25
30
R E V I E W S
33 DbAltGrid
36
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
PASS Engineering Announces PASSOLO 4.0
PASS Engineering announced PASSOLO 4.0, a professional localization tool.
PASSOLO optimizes software localization
and ensures easy compilation, exchange,
and processing of translation data. PASSOLO requires neither time-consuming
and expensive training nor programming
experience. Many errors that occur during localization are avoided or automatically recognized by PASSOLO.
Software generally contains repetitive text. PASSOLO allows you to reuse
existing translations. Even text from programs that havent been translated with
PASSOLO can be used for the semi-automatic translation of new projects.
PASSOLO 4.0 handles binaries with
Windows standard resources, as well
as Delphi, C++Builder, Microsoft
.NET, Visual Basic 6, Java, and XML
files, and 16-bit programs. It comes
with an integrated menu, dialog and
bitmap editors, fuzzy matching, glossaries, and checking functions.
New features in PASSOLO 4.0 include
an optional file parser add-in for Delphi
and C++Builder files, an optional file
parser add-in for Microsoft .NET files,
an integrated file parser add-in for ID-
Altova Releases
New Product Line
of XML Tools
Altova announced a new product
line, consisting of xmlspy 5, authentic
5, and stylevision 5, to facilitate and
advance the adoption of XML technologies. xmlspy 5 is an XML development
environment for designing, editing, and
debugging enterprise-class applications
that involve XML, XML Schema, XSL/
XSLT, SOAP, WSDL, and Web Services.
authentic 5 is a standards-based, browser-enabled document editor that allows
business users to capture thoughts and
ideas seamlessly, directly in XML format. stylevision 5 is a new XML tool for
Web developers that provides powerful
conversion utilities for migrating traditional HTML Web sites to advanced
XML-based sites in full compliance with
new Internet standards such as XML,
XSLT, and XML Schema.
Altova Inc.
Price: See Web site for pricing.
Contact: sales@altova.com
Web Site: www.altova.com
T O O L B O X
Add Imaging Capabilities with ImgX Controls
Atalasoft has released ImgX Controls
6.2, a collection of controls that lets
developers add imaging functionality to
their applications. ImgX Controls can be
used in any development environment
that supports ActiveX, including Delphi,
Visual Basic, .NET, Visual C++, and
FoxPro. This suite of controls lets you
add basic imaging functions such as capturing, scanning, and printing images,
as well as high-end image viewing and
manipulation capabilities.
ImgX will read and write most popular
graphics file formats, including JPEG,
PNG, TIFF, TIFF G3/G4 Fax, PCX, TGA,
BMP, PSD, PCD, GIF, EXIF, and WMF/
EMF. ImgX uses a low-level C++ imaging library with an ActiveX front end.
ImgX Controls provides more than
100 built-in filters, transforms, and
effects. You can draw lines, rectangles,
ellipses, and add text with a textured
background and antialiasing. You can
C# in a Nutshell
A Programmers Guide to
ADO.NET in C#
C# Essentials,
Second Edition
Mahesh Chand
Apress
ISBN: 1-893115-39-9
Cover Price: US$44.95
(711 pages)
www.apress.com
Andrew Troelsen
Apress
ISBN: 1-893115-59-3
Cover Price: US$59.95
(970 pages)
www.apress.com
. N E T
ADO.NET
T E C H
his is the third installment of our series devoted to the Delphi for .NET Compiler Preview
that ships with Delphi 7 Studio. In previous
OleDbDataReader
xxxDataAdapter
SqlDataAdapter
OleDbDataAdapter
Figure 1: Mapping ADO classes to ADO.NET classes.
.NET
Te c h
Namespace
Description
Class
Description
System.Data
xxxConnection
xxxCommand
xxxDataReader
System.Data.Common
System.Data.SqlClient
System.Data.OleDb
System.Data.SqlTypes
System.Xml
Data-related Namespaces
As mentioned, ADO.NET is implemented in the System.Data
namespace. Some parts of ADO.NET reside in the System.Xml
namespace, e.g. the XmlDataDocument class. The data-related
namespaces are shown in Figure 2. In general, the ADO.NET
object model consists of two major parts: the DataSet classes
and the .NET data provider classes. Well look at both of them
in the following sections.
The DataSet Classes
The fundamental concept of the ADO.NET disconnected
architecture is implemented in the DataSet class an
in-memory cache of data tables, relationships, and
constraints. We can populate a dataset from a SQL query,
an XML document, or by creating tables, relations, and
constraints programmatically.
ADO itself contains a set of libraries (DLLs) that implements the ADO object model, and that can be used in
client applications. ADO is a very popular way to access
data, since it comes
with Windows 2000
Datasets usually contain
In reality, ADO.NET is a marketing
and Windows XP,
a subset of the data in
term that covers the classes in the
as well as with such
a central database, or
popular products as
in a general case
System.Data namespace.
Microsoft Office and
in several databases.
Microsoft Internet
We can manipulate a
Explorer. Readers can find further information in the book dataset in a disconnected application. We can also write
Advanced Delphi Developers Guide to ADO (published by
out the contents of a dataset in XML format, and pass it
Wordware, 2000).
to other applications and services. Developers acquainted
with dbExpress may find some ADO.NET features
ADO.NET
familiar.
ADO.NET is the next step in the evolution of ADO. Although
ADO.NET doesnt have the same programming model as ADO,
If we dont need to cache data (for example, when we
it does shares much of its functionality. In contrast to ADO,
create a report), we can use the DataReader classes
ADO.NET provides extensive support for building disconnected
(either SqlDataReader or OleDbDataReader), which
applications, which are the basis of modern distributed sysprovide connected access to a forward-only dataset. This
tems. A typical example of a modern distributed application is
functionality is also well known to dbExpress users.
one for a mobile employee who downloads a private copy of
data from the database in the morning, works with this data
Managed Providers
during the day, and posts the changes back to the database at
The .NET Framework includes two data providers: the
the end of the day. Another example is an XML Web Service
SQL Server .NET Data Provider, and the OLE DB .NET
that returns an XML dataset according to a client request.
Data Provider. The SQL Server .NET Data Provider allows
optimized access to SQL Server 2000 and SQL Server
In reality, ADO.NET is a marketing term that covers the classes
7.0 databases. The OLE DB .NET Data Provider provides
in the System.Data namespace. The classes of this namespace,
access to SQL Server 6.5 (and earlier), Oracle, Sybase,
mapped to their similar ADO objects, are shown in Figure 1.
DB2, Microsoft Access, and other databases. In addition,
.NET
Te c h
{$APPTYPE CONSOLE}
// Using the DataReader class.
program ReaderDemo;
uses
System.Data.SqlClient; // SQL Server .NET Data Provider.
var
Conn : SqlConnection;
Cmd : SqlCommand;
DR
: SqlDataReader;
begin
Conn := SqlConnection.Create(
'server=localhost;database=Northwind;uid=sa;pwd=');
Conn.Open;
Cmd := SqlCommand.Create('SELECT * FROM Employees',Conn);
DR := Cmd.ExecuteReader;
while DR.Read() <> False do begin
Console.WriteLine(DR['FirstName'].ToString + ' ' +
DR['LastName'].ToString);
end;
Conn.Close;
end.
.NET
Te c h
{$APPTYPE CONSOLE}
// Using the SqlCommand class.
program CommandDemo;
uses
System.Data.SqlClient; // SQL Server .NET Data Provider.
var
Conn : SqlConnection;
Cmd : SqlCommand;
I
: Integer;
begin
Conn := SqlConnection.Create(
'server=localhost;database=Northwind;uid=sa;pwd=');
Conn.Open;
Cmd := SqlCommand.Create('INSERT INTO Customers ' +
'(CustomerID, CompanyName) +
'VALUES (''XYZSS'', ''XYZ SuperShop'')', Conn);
I := Cmd.ExecuteNonQuery;
Console.WriteLine('Rows affected: ' + IntToStr(I));
Conn.Close;
end.
Class
Description
xxxDataAdapter
DataSet
.NET
Te c h
{$APPTYPE CONSOLE}
// Using the DataSet class.
program DatasetDemo;
uses
// SQL Server .NET Data Provider.
System.Data.SqlClient, System.Data;
var
DA
DS
DR
CB
DT
CN
I
:
:
:
:
:
:
:
SqlDataAdapter;
DataSet;
DataRow;
SqlCommandBuilder;
DataTable;
SqlConnection;
Integer;
begin
CN := SqlConnection.Create(
'server=localhost;database=Northwind;uid=sa;pwd=;');
CN.Open;
DS := DataSet.Create;
DA := SqlDataAdapter.Create(
'SELECT * FROM Customers', CN);
CB := SqlCommandBuilder.Create(DA);
DA.Fill(DS, 'Customers');
CN.Close;
DT := DS.Tables['Customers'];
if DT.Rows.Count > 91 then
begin
Console.WriteLine('Row to delete: ');
Console.WriteLine(DT.Rows[91].ItemArray[0].ToString);
DT.Rows.Item[91].Delete;
CN.Open;
DA.Update(DS, 'Customers') ;
Console.WriteLine ('After deleting a row: ');
for I := 85 to DT.Rows.Count-1 do begin
DR:=DT.Rows[i];
Console.WriteLine(DR.ItemArray[0].ToString + ' ' +
DR.ItemArray[1].ToString);
end;
CN.Close;
end
else
Console.WriteLine(
'Record to be deleted cannot be found');
end.
The code for this example is shown in Figure 10, and the
result of executing this application is shown in Figure 11.
Note that by using the SqlDataAdapter.Fill method we dont
need to explicitly open or close the database connection. In
the example above, the Connection object and its Open and
Close methods are used only for illustrative purposes.
Figure 11: The result of running the code from Figure 10.
G R E A T E R
MICROSOFT MESSAGE QUEUE
COM
D E L P H I
DELPHI 4-6
By Matthew Hess
to install
Microsoft Message Queuing Services.
Depending on your environment,
the installation may try to find an
Active Directory domain controller
for managing public queues. For the
examples in this article, however, you
only need support for private queues.
Remove Windows Components
User saves
item in
Outlook
Raise
Exchange
store event
Update
database
Figure 1: A sample problem: update a database based on changes in Outlook. Heres what the initial, synchronous solution looks like.
Greater
Delphi
User saves
item in
Outlook
Read
message
from queue
Raise
Exchange
store event
Write
message to
queue
Update
database
Figure 2: A messaging architecture lets us process the update asynchronously, which keeps the Outlook ses-
Greater
Delphi
11
The second approach, using a byte array, lets you get a bit
fancy. First you need to define the parameters of your method call as a record:
TBodyRec = record
StrParam: string[50];
IntParam: Integer;
BoolParam: Boolean;
end;
Greater
Delphi
message
from queue
Update
database
Figure 10: The case for using transactions: what if the database is off line?
Database
off line
now what?
Greater
Delphi
procedure ReadFromTransactionalQueue;
var
pQ: MSMQQueue;
pQInfo: MSMQQueueInfo;
pQMsg: MSMQMessage; vTimeOut: OleVariant;
pQTransDispenser: MSMQTransactionDispenser;
pQTrans: MSMQTransaction;
vQTrans: OLEVariant;
begin
try
// Initialize COM objects.
pQ := CoMSMQQueue.Create;
pQInfo := CoMSMQQueueInfo.Create;
pQTransDispenser := CoMSMQTransactionDispenser.Create;
pQTrans := CoMSMQTransaction.Create;
vTimeOut := 0;
// Start a transaction.
pQTrans := pQTransDispenser.BeginTransaction;
vQTrans := pQTrans;
// Open the queue.
pQInfo.PathName :=
'.\Private$\Test_Queue_Transactional';
pQ := pQInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE);
// Receive the first message in the queue.
pQMsg := pQ.ReceiveCurrent(vQTrans, EmptyParam,
EmptyParam, vTimeOut, EmptyParam);
// Commit or abort as needed.
if pQMsg <> nil then
if UpdateDatabase(VarToStr(pQMsg.Body)) then
// Remove from queue.
pqTrans.Commit(EmptyParam, EmptyParam, EmptyParam)
else
// Leave in queue.
pQTrans.Abort(EmptyParam, EmptyParam);
finally
vQTrans := Unassigned;
pQTrans := nil;
pQTransDispenser := nil;
// etc.
end;
end;
Greater
Delphi
14
Greater
Delphi
// Defined in MSMQ_TLB.pas.
implementation
uses
ComServ, SysUtils;
procedure TEventBasedMSMQReader.Start;
// This function starts the queue reader. Somewhere in your
// application you need a procedure that creates a
// TEventBasedMSMQReader and calls Start. This should only
// happen once.
var
pQInfo: MSMQQueueInfo;
vTimeOut: OleVariant;
begin
if Assigned(oFEvent) then
Exit; // Don't re-register the event sink.
pQInfo := CoMSMQQueueInfo.Create;
FQueue := CoMSMQQueue.Create;
FEvent := TMSMQEvent.Create(nil);
FEvent.OnArrived := Arrived;
FEvent.OnArrivedError := ArrivedErr;
pQInfo.PathName := '.\Private$\Test_Queue_Transactional';
vTimeOut := -1;
FQueue := pQInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE);
FQueue.EnableNotification(
15
C O L U M N S
BORLAND DATABASE ENGINE
&
BATCHMOVE
R O W S
DELPHI 2-7
By Bill Todd
Replacing BatchMove
Use These Classes, Now That the BDE Is Gone
16
Columns
&
Rows
Replacing BatchMove
procedure TDataCopy.Execute;
begin
// Ensure Source and Destination have been assigned.
if (not Assigned(FSource)) then
raise Exception.Create('Source dataset not assigned.');
if (not Assigned(FDestination)) then
raise Exception.Create(
'Destination dataset not assigned.');
// Make sure the Source and Destination are open.
if (not FSource.Active) then
FSource.Open;
if (not FDestination.Active) then
FDestination.Open;
// Start with the first record in Source.
FSource.First;
// If Mappings property is empty, copy all columns from
// Source to Destination; otherwise, copy only the
// mapped columns.
if (FMappings.Count = 0) then
CopyAllColumns
else
CopyMappedColumns;
end;
procedure TDataCopy.CopyAllColumns;
// Copies all columns based on the column count of the
// Source dataset. Source and Destination must have
// compatible columns in the same order.
var
I:
Integer;
Count: Integer;
begin
Count := 0;
while (not FSource.EOF) do begin
// If the RecordCount property is > zero and the number
// of records copied equals the RecordCount property
// do not copy any more records.
if (FRecordCount > 0) and (Count >= FRecordCount) then
Exit;
// Copy the record to the destination dataset.
FDestination.Append;
for I := 0 to FSource.FieldCount - 1 do
FDestination.Fields[I].Assign(FSource.Fields[I]);
FDestination.Post;
// Increment the count and move to the next record in
// the source dataset.
Inc(Count);
FSource.Next;
end;
FRecordsCopied := Count;
end;
procedure TDataCopy.CopyMappedColumns;
// Copies columns based on the column names in the Mappings
// property. Mappings contains one entry for each column to
// be copied in the form DestColumnName=SourceColumnName.
// Values are copied by column name, not column order.
var
I:
Integer;
Count: Integer;
begin
Count := 0;
while (not FSource.EOF) do begin
// If the RecordCount property is > zero and the number
// of records copied equals the RecordCount property
// do not copy any more records.
if (FRecordCount > 0) and (Count >= FRecordCount) then
Exit;
FDestination.Append;
for I := 0 to FMappings.Count - 1 do
FDestination.FieldByName(Mappings.Names[I]).Assign(
FSource.FieldByName(Mappings.Values[
Mappings.Names[I]]));
FDestination.Post;
Inc(Count);
FSource.Next;
end;
FRecordsCopied := Count;
end;
Columns
&
Rows
Replacing BatchMove
type
TDelimitedExport = class(TComponent)
private
FSource: TDataSet;
FFileName: string;
FDelimiter: Char;
FQuoteChar: Char;
public
constructor Create(AOwner: TComponent); override;
procedure Execute;
published
property Delimiter: Char
read FDelimiter write FDelimiter;
property FileName: string
read FFileName write FFileName;
property QuoteChar: Char
read FQuoteChar write FQuoteChar;
property Source: TDataSet read FSource write FSource;
end;
procedure TDelimitedExport.Execute;
var
AsciiFile:
TextFile;
I, LastField: Integer;
begin
// Make sure the user specified a file name.
if FileName = '' then
raise Exception.Create('FileName is blank.');
// Make sure the DataSet property is set, the dataset is
// open, and the current record is the first record.
if Source = nil then
raise Exception.Create(
'Source dataset is not assigned.');
if (not Source.Active) then
Source.Open;
Source.First;
// Open the delimited file.
AssignFile(AsciiFile, FileName);
Rewrite(AsciiFile);
LastField := Source.FieldCount - 1;
while (not Source.EOF) do begin
for I := 0 to LastField do begin
// If the field is a BLOB skip it.
if Source.Fields[I].DataType in [ftBlob, ftGraphic,
ftMemo, ftOraBlob, ftOraClob, ftADT,
ftInterface, ftIDispatch, ftArray, ftReference,
ftParadoxOle, ftDbaseOle, ftFmtMemo] then
Continue;
// If this isn't first field, write Delimiter.
if (I > 0) then
Write(AsciiFile, Delimiter);
// If field isn't numeric, write opening quote
// character.
if (Source.Fields[I].DataType in [ftString,
ftFixedChar, ftWideString]) then
Write(AsciiFile, QuoteChar);
// Write the field value.
Write(AsciiFile, Source.Fields[I].AsString);
// If field type isn't numeric, write closing
// delimiter character.
if (Source.Fields[I].DataType in [ftString,
ftFixedChar, ftWideString]) then
Write(AsciiFile, QuoteChar);
end;
// Write carriage return/line feed at end of record.
Writeln(AsciiFile, '');
Source.Next;
end;
CloseFile(AsciiFile);
end;
18
type
TDelimitedImport = class(TComponent)
private
FFieldList: TStringList;
FFileName: string;
FImportFile: TextFile;
FMappings: TStringList;
FDestination: TDataSet;
procedure SetDelimiter(const Value: Char);
procedure SetQuoteChar(const Value: Char);
function GetDelimiter: Char;
function GetQuoteChar: Char;
protected
procedure AddRecordToDataSet;
function GetNextRecord: Boolean;
procedure OpenTextFile;
procedure CloseTextFile;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Execute;
published
property Destination: TDataSet
read FDestination write FDestination;
property Mappings: TStringList read FMappings;
property FileName: string
read FFileName write FFileName;
property Delimiter: Char
read GetDelimiter write SetDelimiter;
property QuoteChar: Char
read GetQuoteChar write SetQuoteChar;
end;
The while loop exports all the records in the source dataset.
The for loop iterates through all the field objects in the source
record and exports each one. The first if statement skips any
field types, such as BLOBs, that cannot be exported to ASCII.
The second if statement writes the delimiter character if at
least one field value has been written to the ASCII file. The
third if statement writes the open quote character if the field
is a string. Next, the field value is written, then another if
statement writes the closing quote character if the field is a
string. Finally, the carriage return/line feed that terminates the
record is written to the file, and the source dataset is positioned
to the next record. The complete code for the DelimitedExport
component appears in Listing Two (beginning on page 22).
Importing Delimited Data
The delimited import components class declaration is shown
in Figure 8. The Destination property is the destination dataset.
The FileName property is the path to the delimited text file.
The Delimiter and QuoteChar properties are identical to those
in the DelimitedExport component. The Mappings property
determines which field in the delimited text file is placed in
which field of the Destination dataset and contains entries in
the form DestinationFieldName=DelimitedFileFieldNumber.
DelimitedFileFieldNumber is the zero-based sequential number
of the fields in the delimited text file.
The overridden constructor creates the FFieldList and
FMappings StringLists, and assigns the default values to
the Delimiter and QuoteChar properties. Note that in this
component the Delimiter and QuoteChar properties have
getter and setter methods that store the values of these
Columns
&
Rows
Replacing BatchMove
procedure TDelimitedImport.Execute;
begin
if (FMappings.Count = 0) then
raise Exception.Create('No field mappings specified.');
OpenTextFile;
while GetNextRecord do
AddRecordToDataSet;
CloseTextFile;
end;
procedure TDelimitedImport.AddRecordToDataSet;
// Add record in FFieldList StringList to Destination
// dataset using information in FMappings StringList. Each
// entry in FMappings has the form DestFieldName=9
// where 9 is zero-based number of field in file.
var
I, ImportFldNum: Integer;
FldName:
string;
begin
with Destination do begin
Append;
for I := 0 to FMappings.Count - 1 do begin
FldName := Mappings.Names[I];
ImportFldNum := StrToInt(Mappings.Values[FldName]);
FieldByName(FldName).AsString :=
FFieldList[ImportFldNum];
end;
Post;
end;
end;
the right with spaces. Its then written to the file. After all
the fields in the current record have been written, the call
to Writeln writes a carriage return/line feed to terminate the
record. The complete listing for this component is shown in
Listing Three (beginning on page 23).
Importing Fixed Length ASCII
The TFixedImport component also resembles its delimited
cousin, except that it lacks the Delimiter and QuoteChar
properties. The Execute method is also different in that it
opens the ASCII file and calls the ImportData method (see
Figure 13), which does all of the work. This component
works with fixed-length text files only. Each record in the
ASCII file must end with a carriage return/line feed.
Columns
&
Rows
Replacing BatchMove
procedure TFixedExport.Execute;
var
AsciiFile:
TextFile;
I, LastField: Integer;
FldName:
string;
FldSize:
Integer;
FldValue:
string;
begin
// Make sure the user specified a file name.
if FileName = '' then
raise Exception.Create('FileName is blank.');
// Make sure DataSet property is set, dataset is open,
// and the current record is the first record.
if Source = nil then
raise Exception.Create(
'Source dataset is not assigned.');
if (not Source.Active) then
Source.Open;
Source.First;
// Open the ASCII file.
AssignFile(AsciiFile, FileName);
Rewrite(AsciiFile);
LastField := Source.FieldCount - 1;
while (not Source.EOF) do begin
for I := 0 to FMappings.Count - 1 do begin
FldName := FMappings.Names[I];
FldSize := StrToInt(FMappings.Values[FldName]);
// If the field is a BLOB skip it.
if Source.FieldByName(FldName).DataType in [ftBlob,
ftGraphic, ftMemo, ftOraBlob, ftOraClob, ftADT,
ftInterface, ftIDispatch, ftArray, ftReference,
ftParadoxOle, ftDbaseOle, ftFmtMemo] then
Continue;
// Get the field value as a string.
FldValue := Source.FieldByName(FldName).AsString;
// If value is longer than size of export field,
// raise an exception.
if (Length(FldValue) > FldSize) then
raise Exception.Create(
'Field value greater than export field size.');
// If field value is smaller than export field size,
// pad it with spaces.
if (Length(FldValue) < FldSize) then
FldValue := FldValue +
StringOfChar(' ', FldSize - Length(FldValue));
// Write the field value.
Write(AsciiFile, FldValue);
end;
// Write carriage return/line feed at end of record.
Writeln(AsciiFile, '');
Source.Next;
end;
CloseFile(AsciiFile);
end;
procedure TFixedImport.ImportData;
// Add record in FFieldList StringList to Destination
// dataset using information in the FMappings StringList.
// Each entry in FMappings has the form:
// DestFieldName=StartPosition,Length
var
I, FldStart: Integer;
FldLength:
Integer;
FldName:
string;
S, Rec:
string;
begin
while (not EOF(FImportFile)) do begin
Readln(FImportFile, Rec);
with Destination do begin
Append;
for I := 0 to FMappings.Count - 1 do begin
FldName := Mappings.Names[I];
S := Mappings.Values[FldName];
FFieldList.Clear;
FFieldList.DelimitedText := S;
FldStart := StrToInt(FFieldList[0]);
FldLength := StrToInt(FFieldList[1]);
SetLength(S, FldLength);
Move(Rec[FldStart], S[1], FldLength);
FieldByName(FldName).AsString := Trim(S);
end;
Post;
end;
end;
end;
Columns
&
Rows
Replacing BatchMove
21
type
TDataCopy = class(TComponent)
private
FSource: TDataSet;
FDestination: TDataSet;
FMappings: TStringList;
FRecordCount: Integer;
FRecordsCopied: Integer;
protected
procedure CopyAllColumns;
procedure CopyMappedColumns;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Execute;
published
property Source: TDataSet read FSource write FSource;
property Destination: TDataSet
read FDestination write FDestination;
property Mappings: TStringList read FMappings;
property RecordCount: Integer
read FRecordCount write FRecordCount;
property RecordsCopied: Integer read FRecordsCopied;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('DI', [TDataCopy]);
end;
procedure TDataCopy.CopyAllColumns;
// Copies all columns based on the column count of the
// Source dataset. Source and Destination must have
// compatible columns in the same order.
var
I:
Integer;
Count: Integer;
begin
Count := 0;
while (not FSource.EOF) do begin
// If RecordCount is > zero and number of records
// copied equals RecordCount, don't copy any more
// records.
if (FRecordCount > 0) and (Count >= FRecordCount) then
Exit;
// Copy record to destination dataset.
FDestination.Append;
for I := 0 to FSource.FieldCount - 1 do
FDestination.Fields[I].Assign(FSource.Fields[I]);
FDestination.Post;
// Increment count; move to next record in source
// dataset.
Inc(Count);
FSource.Next;
end;
FRecordsCopied := Count;
end;
procedure TDataCopy.CopyMappedColumns;
// Copies columns based on column names in Mappings.
// Mappings contains one entry for each column to be copied
// in the form DestColumnName=SourceColumnName. Values are
// copied by column name, not column order.
Columns
&
Rows
Replacing BatchMove
var
I, Count: Integer;
begin
Count := 0;
while (not FSource.EOF) do begin
// If RecordCount is > 0 and number of records copied
// equals RecordCount, don't copy any more records.
if (FRecordCount > 0) and (Count >= FRecordCount) then
Exit;
FDestination.Append;
for I := 0 to FMappings.Count - 1 do
FDestination.FieldByName(Mappings.Names[I]).Assign(
FSource.FieldByName(Mappings.Values[
Mappings.Names[I]]));
FDestination.Post;
Inc(Count);
FSource.Next;
end; // while
FRecordsCopied := Count;
end;
constructor TDataCopy.Create(AOwner: TComponent);
begin
inherited;
FMappings := TStringList.Create;
end;
destructor TDataCopy.Destroy;
begin
FMappings.Free;
inherited;
end;
procedure TDataCopy.Execute;
begin
// Ensure Source and Destination have been assigned.
if (not Assigned(FSource)) then
raise Exception.Create('Source dataset not assigned.');
if (not Assigned(FDestination)) then
raise Exception.Create(
'Destination dataset not assigned.');
// Make sure Source and Destination are open.
if (not FSource.Active) then
FSource.Open;
if (not FDestination.Active) then
FDestination.Open;
// Start with the first record in Source.
FSource.First;
// If Mappings is empty, copy all columns from Source to
// Destination; otherwise, copy only the mapped columns.
if (FMappings.Count = 0) then
CopyAllColumns
else
CopyMappedColumns;
end;
end.
22
FDelimiter: Char;
FQuoteChar: Char;
public
constructor Create(AOwner: TComponent); override;
procedure Execute;
published
property Delimiter: Char
read FDelimiter write FDelimiter;
property FileName: string
read FFileName write FFileName;
property QuoteChar: Char
read FQuoteChar write FQuoteChar;
property Source: TDataSet read FSource write FSource;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('DI', [TDelimitedExport]);
end;
constructor TDelimitedExport.Create(AOwner: TComponent);
begin
inherited;
FDelimiter := ',';
FQuoteChar := '"';
end;
procedure TDelimitedExport.Execute;
var
AsciiFile:
TextFile;
I, LastField: Integer;
begin
// Make sure the user specified a file name.
if FileName = '' then
raise Exception.Create('FileName is blank.');
// Make sure DataSet is set, the dataset is open, and
current record is the first record.
if Source = nil then
raise Exception.Create(
'Source dataset is not assigned.');
if (not Source.Active) then
Source.Open;
Source.First;
// Open the delimited file.
AssignFile(AsciiFile, FileName);
Rewrite(AsciiFile);
LastField := Source.FieldCount - 1;
while (not Source.EOF) do begin
for I := 0 to LastField do begin
// If the field is a BLOB skip it.
if Source.Fields[I].DataType in [ftBlob, ftGraphic,
ftMemo, ftOraBlob, ftOraClob, ftADT,
ftInterface, ftIDispatch, ftArray, ftReference,
ftParadoxOle, ftDbaseOle, ftFmtMemo] then
Continue;
// If this is not first field, write Delimiter
character.
if (I > 0) then
Write(AsciiFile, Delimiter);
// If the field is not numeric write the opening
quote character.
if (Source.Fields[I].DataType in [ftString,
ftFixedChar, ftWideString]) then
Write(AsciiFile, QuoteChar);
// Write the field value.
Write(AsciiFile, Source.Fields[I].AsString);
// If the field type is not numeric write the closing
delimiter character.
if (Source.Fields[I].DataType in [ftString,
ftFixedChar, ftWideString]) then
Write(AsciiFile, QuoteChar);
end; // for
Columns
&
Rows
Replacing BatchMove
procedure Register;
implementation
end.
end.
procedure Register;
begin
RegisterComponents('DI', [TFixedExport]);
end;
// TFixedExport
constructor TFixedExport.Create(AOwner: TComponent);
begin
inherited;
FMappings := TStringList.Create;
end;
destructor TFixedExport.Destroy;
begin
FMappings.Free;
inherited;
end;
procedure TFixedExport.Execute;
var
AsciiFile: TextFile;
I:
Integer;
LastField: Integer;
FldName:
string;
FldSize:
Integer;
FldValue: string;
begin
// Make sure the user specified a file name.
if FileName = '' then
raise Exception.Create('FileName is blank.');
// Make sure DataSet property is set, dataset is open,
// and current record is the first record. if Source =
nil then
raise Exception.Create(
'Source dataset is not assigned.');
23
Columns
&
Rows
Replacing BatchMove
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('DI', [TFixedImport]);
end;
// TFixedImport
procedure TFixedImport.ImportData;
// Adds record that's in FFieldList StringList to the
// Destination dataset using information in the FMappings
// StringList. Each entry in FMappings has the form:
// DestFieldName=9 where 9 is the zero-based number of the
// field in the delimited text file.
var
I:
Integer;
FldStart: Integer;
FldLength: Integer;
FldName:
string;
S:
string;
Rec:
string;
begin
while (not EOF(FImportFile)) do begin
Readln(FImportFile, Rec);
with Destination do begin
Append;
for I := 0 to FMappings.Count - 1 do begin
FldName := Mappings.Names[I];
S := Mappings.Values[FldName];
FFieldList.Clear;
FFieldList.DelimitedText := S;
FldStart := StrToInt(FFieldList[0]);
FldLength := StrToInt(FFieldList[1]);
SetLength(S, FldLength);
Move(Rec[FldStart], S[1], FldLength);
FieldByName(FldName).AsString := Trim(S);
end;
Post;
end;
end;
end;
constructor TFixedImport.Create(AOwner: TComponent);
begin
inherited;
FMappings := TStringList.Create;
FFieldList := TStringList.Create;
end;
destructor TFixedImport.Destroy;
begin
FMappings.Free;
FFieldList.Free;
inherited;
end;
procedure TFixedImport.Execute;
begin
if (FMappings.Count = 0) then
raise Exception.Create('No field mappings specified.');
if (FFileName = '') then
raise Exception.Create('No file name specified.');
AssignFile(FImportFile, FFileName);
Reset(FImportFile);
ImportData;
CloseFile(FImportFile);
end;
end.
24
F O U N D A T I O N
MICROSOFT .NET FRAMEWORK
C L A S S
25
Foundation
Class
Foundation
Class
Foundation
Class
Foundation
Class
29
T O W A R D
HUMOR
T H E
L I G H T
DELPHI 1-7
By Loren Scott
30
To w a r d
the
Light
Customer Types
There are several different types of customers that youll
come across in tech support. Being able to recognize these
different types of customers will help you adjust your conversation to meet their personal needs.
To w a r d
the
Light
YOU: You say that youre using version 4.3 of our product?
THEM: Thats right. Four point three! Its been working fine
for me for the past five years under Windows 95. Now, all I
do is completely change out the operating system on the PC
and KA-BOOM!!! No workie.
YOU: (Quickly racing back to the desk with your freshlyfilled cup of coffee and releasing the speaker-phone button)
Yes! Yes, Im still here.
THEM: So, uhhhh...You dont have any tools like this?
YOU: No, sir. We still dont.
The Complainer. The Complainer doesnt call to get a solution to a problem if there really is a problem. He just
wants to vent. A typical conversation with a Complainer
might go something like this:
YOU: Sorry?
THEM: Oh, sure! Ive gotta call you back if it doesnt work!
Spend my money calling you back if it doesnt work. Why
dont you call me back if it doesnt work?
YOU: Huh?
THEM: Nevermind. Whats the URL for the Web site again?
Things a Tech Support Person
Should Never Say
One. If a customer (or potential customer) asks, Does your
current release have any known bugs? a good tech support
person should never answer, Uhhh...youre not planning to
use this in conjunction with systems for pacemakers or air
traffic control radar, are you?
YOU: But...
THEM: Hang on there, sport! Theres more. Now all of a
sudden my programs that used to run just fine on Windows
95 are locking up Windows XP. I mean locking up all of
Windows XP. Not just one task. Noooo! The whole damn
thing. Locked right up!
YOU: Yessir; I see, but...
THEM: Do you know how hard it is to lock up all of Windows XP?
N E W
&
U S E D
By Warren Rachele
DbAltGrid
Powerful, Flexible DBGrid Replacement
ne of the first additions a database programmer often makes to Delphis standard set of
VCL components is a replacement for that
New
&
Used
DbAltGrid
Total Control
The display of different types of data
within the DbAltGrid component is
controlled by templates. Quasidata
developed templates for the display and
manipulation of date and time, numeric
data, Boolean data, memo types, hyperlinks, and image data. The application
shown in Figure 4 has templates embedded in the Date, Time, and Ticket Price
fields for use in editing the data. The
templates are activated through buttons embedded in the cell which appear
when the user moves the cell selector to
that part of the grid. The dagCalendar
component is used for easier editing of
the event dates (see Figure 5).
The Portfolio application shown in Figure 6 puts more
features of DbAltGrid to work. The DbAltGrid footer
panel is used in this application at the bottom of the grid.
This panel can be enabled and disabled at will, and is
designed to contain summary data from the table. The
Sum of the Shares column is displayed in the Portfolio.
This is a handy tool because it stays in place during scroll
and other navigational operations.
In addition to summarizing the data, a user can sort
the SYMBOL column by clicking on the title header.
Programmers should be aware that the summarization
and sorting functionality must be coded by hand. The
DbAltGrid component is a data display control, not a data
manipulation tool. In Figure 6, there are a series of SQL
statements and Query controls that perform the actual
SUM and ORDER BY manipulations.
The dagBackup control has further utility beyond the conversion process. All of the properties applicable to the grid
can be written to a text file, a resource file, or the registry,
and then read back later. For example, one grid in your project can be configured with all of the desired options and the
New
&
Used
DbAltGrid
properties saved to a file. Duplicate grids can then be created by dropping a default grid on the form, associating the
dagBackup control with them, and then selecting Load from
the context menu. This is also a fast way of offering a user
variable views of the grid. During the development process,
you can lay out the grid in a variety of configurations, saving each one to a separate resource file. In the final distribution, clicking a button could then change a users default
table view to a more sophisticated record-type view.
The Price to Pay
All of this new functionality is wonderful, if you can figure out how to use it. However, DbAltGrid isnt shipped
with a manual or a users guide for getting started with
the product. An excellent help file is included with the
installation, and 1 provides you with immediate access
to the details of all properties and methods.
Where the documentation leaves the developer hanging is in
the area of learning to use the components within a project.
There are no examples embedded in the documentation, so
your only source of guidance is contained in the demonstrations that ship with the package. These program examples
work to your advantage if you can clearly determine what the
author is trying to demonstrate. Unfortunately, Quasidata was
35
T E X T F I L E
Mike Riley
F I L E
N E W
File
New
38
Alan Moore is a professor at Kentucky State University, where he teaches music theory and humanities.
He was named Distinguished Professor for 2001-2002. He has been named the Project JEDI Director for
2002-2003. He has developed education-related applications with the Borland languages for more than 15
years. Hes the author of The Tomes of Delphi: Win32 Multimedia API (Wordware Publishing, 2000) and
co-author (with John C. Penman) of The Tomes of Delphi: Basic 32-Bit Communications Programming
(Wordware Publishing, 2003). He also has published a number of articles in various technical journals.
Using Delphi, he specializes in writing custom components and implementing multimedia capabilities in
applications, particularly sound and music. You can reach Alan on the Internet at acmdoc@aol.com.