From Database To Browser: November 1996, Volume 2, Number 11
From Database To Browser: November 1996, Volume 2, Number 11
From Database To Browser: November 1996, Volume 2, Number 11
From Database
to Browser
Generating HTML from Table Data
ON THE COVER
6
From the Database to the Browser Keith Wood
Mr Wood combines his THTMLWriter component with TDataSource to
create a dynamic, data-aware component for your Delphi Web projects.
FEATURES
11 Informant Spotlight Robert Vivrette
Fingerpainting for Adults: Mr Vivrette shows us how easy it is to
manipulate colors with Delphi and builds a custom color property
editor to prove it.
REVIEWS
DEPARTMENTS
52
2
5
53
Delphi Tools
Newsline
File | New by Richard Wagner
Delphi
T O O L S
New Products
and Solutions
New Delphi
Components
Raize Software Solutions,
Inc. of Naperville, IL has
announced Raize Components
for Delphi, a collection of
more than 40 native Delphi
components designed for both
16- and 32-bit development.
Featured components
include: RzSplitter for creating
Microsoft Explorer-style applications; RzTrackBar for
adding standard thumb styles,
owner-draw tick marks, and
custom thumbs; and
RzToolbar which adjusts its
style according to the operating system, and its component
editor adds buttons (including
bitmaps) to the toolbar from a
palette of 45 standard buttons.
Raize Components also features status components and
RzSendMessage for creating
Windows 95 logo-compliant
applications that satisfy the email requirement. This
MAPI-compliant control
Delphi
T O O L S
New Products
and Solutions
ISBN: 1-57619-026-3
Price: US$49.99
(831 pages, CD-ROM)
Phone: (415) 924-2575
to TcsEZForm. Enhanced
navigation can be enabled
or disabled on a form-byform basis, or for all
TcsEZForm forms.
Other components in the
Classic Component Set
include: TcsNotebook,
TcsFormPanel, TcsGrid,
TcsSculptButton,
TcsHiResTimer,
TcsProperEdit,
TcsDBProperEdit,
TcsRankListBox, and, in
Delphi 1 only,
TcsAutoDefaults.
A free trial version is
available from Classic
Delphi
T O O L S
New Products
and Solutions
Seagate Softwares
Information Management
Group has launched Crystal
Reports 5.0. This version
adds new report types, database drivers, object-oriented
report design control, additional developer features, and
News
L
November 1996
Borland to Co-Sponsor
Developers Conference
Desktop Associates Ltd. and
Dunston Thomas Ltd., in
conjunction with Borland, will
host the Borland Developers
Conference in London from
April 20-24, 1997.
This year, the Borland
Developers Conference will be
divided into four major tracks,
and multiple categories within
those tracks. The tracks are:
Delphi, Internet and intranet,
client/server and databases, and
a general track that includes sessions covering business solutions, operating systems, and the
use of companion products to
extend your Borland applications
and development environments.
For more information,
e-mail Chris Read at
cread@dtuk.demon.co.uk, or
call Borland at (408) 431-1000.
Micromodule Systems.
Previously, Emery held positions as vice president of
finance and administration,
and acting vice president of
marketing for Megatest Corp.;
CEO for System Industries;
senior vice president of Santa
Cruz Operation; CEO for
Airmac Technology Systems;
and CFO for Xynetics.
Additionally, Emery served as
a senior manager for IBM,
and a finance manager for
Ford Motor Co.
three configurations.
Designed for small workgroups and low-volume
intranet applications, the
Standard version includes
Netscape Navigator Gold, and
Borlands Personal Web Server
with access to Paradox,
FoxPro, dBASE, and Access
data. The Standard version is
priced at US$99.95.
IntraBuilder Professional version includes Netscape
FastTrack Web server for
Windows NT and the
Netscape Navigator Gold
browser, and is designed for
higher-volume intranets. It
includes support for desktop
database formats, as well as
remote data access to
Microsoft SQL Server, and
InterBase Windows NT
Server. It also supports the
leading Web server APIs,
including NSAPI, ISAPI, and
On the Cover
Delphi 1 / Delphi 2 / Object Pascal / HTML
By Keith Wood
THTMLWriter
To recap, the THTMLWriter component
provides numerous methods that either
format text into HTML code as strings, or
write HTML to a file for viewing. This
article is about those methods related to
HTML tables.
On the Cover
Procedures
Functions
TableStartParams
FormatTableStartParams
TableStart
FormatTableStart
TableEnd
FormatTableEnd
TableRowStartParams
FormatTableRowStartParams
TableRowStart
FormatTableRowStart
TableRowEnd
FormatTableRowEnd
TableHeadingStartParams
FormatTableHeadingStartParams
TableHeadingStart
FormatTableHeadingStart
TableHeadingEnd
FormatTableHeadingEnd
TableHeadingParams
FormatTableHeadingParams
TableHeading
FormatTableHeading
TableCellStartParams
FormatTableCellStartParams
TableCellStart
FormatTableCellStart
TableCellEnd
FormatTableCellEnd
TableCellParams
FormatTableCellParams
TableCell
FormatTableCell
On the Cover
To provide greater control over the appearance of the
table, two events have been defined: OnRowShow and
OnCellShow. The former is triggered at the start of every
row, and the latter is triggered for each individual cell as
it is formatted. Neither event is called for the header row
if its displayed. These events allow the alignment and colors for that row or cell to be altered as required. The type
declaration for the OnRowShow property is shown here:
THTMLRowEvent = procedure(Sender: TObject;
var ahAlignHoriz: THTMLAlignHoriz;
var avAlignVert: THTMLAlignVert;
var clrBackground, clrBorder, clrBorderLight,
clrBorderDark: TColor) of object;
Exceptions
Several error conditions can occur during the generation
of the HTML table. These include having no
HTMLWriter or DataSet assigned; specifying only one of
the LinkField and LinkTarget fields; and the LinkField
not being visible, having no visible fields, and having no
records selected from the database. As usual, these are
raised as exceptions for the calling routine to handle.
To simplify the processing of any errors, the exception
used in this component is derived from the EHTMLError
exception belonging to the THTMLWriter component.
This means all HTML-related errors can be trapped and
dealt with together by looking for the common ancestor.
Demonstration Project
The demonstration project included with this article
shows some of what the THTMLDataSource component
can do. In each case, the HTML is generated into the file
HTMLData.HTM, which can then be loaded into your
browser. The data source is a Paradox table: WebSites.DB.
The first page (see Figure 3) extracts data from the
Parts.DB table that ships with Delphi. Access is provided through either a TTable or TQuery component, each
On the Cover
The THTMLWriter component and demonstration project referenced in this article are available on the Delphi Informant
Works CD located in INFORM\96\NOV\DI9611KW. Note:
The THTMLWriter component featured in this download is
an update to that provided with the previous article. The
change involves moving the open HTML file from the Create
method to Initialise. This allows multiple pages with the same
name to be generated consecutively.
ponent are set to the Web Site and URL fields respectively. When generated (see Figure 6), the contents of
the Web Site column become a link, with the destination coming from the URL field.
The demonstration project is best run outside of
Delphi. Otherwise, exceptions that are trapped internally may appear (if Break on Exceptions is on) and disrupt the flow of the program.
Conclusion
This extension to the THTMLWriter component enables
us to display data as HTML from any data source available to Delphi. The quantity and appearance of the data
is controlled in the same manner as showing it on a
form in Delphi. Combine this with a CGI program to
allow users to specify the data they are interested in, and
you have a dynamic, up-to-date Web site.
Windows platforms are becoming more common as Web
servers. By leveraging our knowledge and abilities in
Delphi, we can provide more responsive applications to
run on them, and move into new areas of endeavor.
On the Cover
{ Dump table to HTML }
with HTMLWriter do begin
TableStartParams(Border, Width, CellSpacing,
CellPadding, ColourBackground, ColourBorder,
ColourBorderLight, ColourBorderDark, Caption,
CaptionAlignHoriz, CaptionAlignVert);
clrBorder
clrBorderLight
clrBorderDark
{ Write headers }
if Headers then
begin
TableRowStartParams(AlignHoriz, AlignVert,
HeaderBackground,HeaderBorder,HeaderBorderLight,
HeaderBorderDark);
for i := 0 to FieldCount - 1 do
if Fields[i].Visible then
begin
{$IFDEF WIN32}
sCell := Fields[i].DisplayName;
{$ELSE}
if Assigned(FOnCellShow) then
OnCellShow(Self,Fields[i],ahAlignHoriz,
avAlignVert,clrBackground,clrBorder,
clrBorderLight, clrBorderDark);
{ And display the field }
if Fields[i] is TMemoField then
{ Add all the lines }
begin
slMemo.Assign(TMemoField(Fields[i]));
TableCellStartParams(0,0,0,
ahAlignHoriz, avAlignVert,
clrBackground,clrBorder,
clrBorderLight, clrBorderDark);
for iCount := 0 to slMemo.Count-1 do
EscapeText(slMemo[iCount] + ' ');
TableCellEnd;
end
sCell := Fields[i].DisplayName^;
{$ENDIF}
if UseFieldAlign then
TableHeadingParams(FormatEscapeText(sCell),
0,0,ahAlignments[Fields[i].Alignment],
avDefault,clDefault,clDefault,clDefault,
clDefault)
else
TableHeading(FormatEscapeText(sCell));
end;
TableRowEnd;
end;
10
:= clDefault;
:= clDefault;
:= clDefault;
Informant Spotlight
Delphi 2 / Object Pascal
By Robert Vivrette
Fingerpainting
Building a Custom Color Property Editor
This type declaration defines a range of colors. The beginning (negative) values are used
to represent the System colors (such as
clBtnFace and clWindow). The positive values
denote literal colors that the Windows GDI
can represent.
If you continue reading online Helps
description of the TColor type, youll see that
11
Informant Spotlight
unit Coloradj;
interface
uses
WinProcs,Graphics;
function AdjustColor(A: TColor; Factor: Real): TColor;
implementation
High Word
$0
$0
Low Word
$FF
$0
Low Byte
High Byte
Low Byte
Figure 4:
Clicking on Color
Demos top panel
displays the Color
dialog box.
If solid green was represented as 0 for red, 255 for green, and
0 for blue, then it should be easy to reduce the green value of
255 by 20 percent to get our 80 percent green. So, thats
exactly what well do!
Figure 3 is the AdjustColor function. It accepts a color and
a percentage, and returns a new color. The new color is
formed by breaking the passed-in color into its component
RGB values, which are then multiplied by the percentage
supplied, and then recombined as a TColor for the function result.
The ColorToRGB function takes the passed-in TColor value
and converts it to an RGB value. In most cases, the TColor
is exactly equal to the RGB value. Yet when the TColor is
holding a System color (such as clBtnFace or clWindow), its
first converted into the appropriate RGB value.
After obtaining an RGB value with ColorToRGB, the
GetRValue, GetGValue, and GetBValue functions are used
to break out the individual amounts of red, green, and
blue. The results are then multiplied by the percentage
passed in and assigned to the R, G, and B variables. Lastly,
Informant Spotlight
these three variables are passed in to the Windows API
function, RGB, that combines them back into a composite
RGB (or TColor) value.
Into Action
Lets put this function into action with two examples.
AdjustColor has been saved as a unit named COLORADJ.PAS, and this unit has been placed into the uses
clause for each of the sample programs well discuss.
Figure 4 shows the simple Color
Demo program (DEMO1.DPR)
which contains a number of panels. Clicking on the top panel
(Click Here To Set Color) displays
the common Color dialog box.
After selecting a color, you can
click the OK button to close the
Color dialog box. The Color
Demo application then dyes each
of the 10 panels on the form with
varying shades of the chosen color
(see Figure 5).
unit Demo1u;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes,
Graphics, Controls, Forms, Dialogs, ColorAdj, ExtCtrls;
type
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
Panel3: TPanel;
Panel4: TPanel;
Panel5: TPanel;
Panel6: TPanel;
Panel7: TPanel;
Panel8: TPanel;
Panel9: TPanel;
Panel10: TPanel;
MainPanel: TPanel;
ColorDialog1: TColorDialog;
procedure MainPanelClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.MainPanelClick(Sender: TObject);
begin
if ColorDialog1.Execute then
with ColorDialog1 do begin
MainPanel.Color := Color;
Panel1.Color
:= AdjustColor(Color,1.0);
Panel2.Color
:= AdjustColor(Color,0.9);
Panel3.Color
:= AdjustColor(Color,0.8);
Panel4.Color
:= AdjustColor(Color,0.7);
Panel5.Color
:= AdjustColor(Color,0.6);
Panel6.Color
:= AdjustColor(Color,0.5);
Panel7.Color
:= AdjustColor(Color,0.4);
Panel8.Color
:= AdjustColor(Color,0.3);
Panel9.Color
:= AdjustColor(Color,0.2);
Panel10.Color
:= AdjustColor(Color,0.1);
end;
end;
end.
Informant Spotlight
unit Demo2u;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes,
Graphics, Controls, Forms, Dialogs, StdCtrls,
Buttons, ExtCtrls, ColorAdj;
type
TForm1 = class(TForm)
Label1: TLabel;
BitBtn1: TBitBtn;
BitBtn2: TBitBtn;
procedure FormPaint(Sender: TObject);
procedure BitBtn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormPaint(Sender: TObject);
var
a : Integer;
begin
with Inherited Canvas do
begin
Pen.Style := psClear;
for a := 0 to ClientHeight div 2 do
begin
Brush.Color :=
AdjustColor(clLime,a*2/ClientHeight);
Rectangle(0,ClientHeight-a*2,
ClientWidth+1,ClientHeight-a*2+3);
end;
end;
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
Close;
end;
end.
draw variety, allowing us to include small swatches of the colors. This will help a user visually associate the custom colors
with their constants. Clicking on the color panel activates a
TColorDialog (provided in Delphi) that allows the user to
select and mix custom colors. After selecting a color in this
way, a user can type in a color constant and click on the Save
button to add the new color to the list of those currently
defined.
Although the property editor completely integrates custom
colors into the Delphi IDE, it must perform one additional task to be a complete solution. To allow a user to specify any of the constants within a piece of Delphi source
code (e.g. a unit or project file) the compiler must know
how these constants are defined. As a result, the property
editor includes a button (named Save Source) that writes
out the currently-defined custom color constants as a
source code unit. Then, by adding this unit to any project,
a user can continue to use the custom colors even outside
the IDE.
The interface to the property editor is straightforward. Two
principal ways of activating it are to enter a question mark
( ? ) instead of a color name. The second is to type any
color constant into the Object Inspector that has not
already been defined. For the value of a forms Color property, for example, you could type in clPeriwinkle. This
color is not one of the standard color constants, and it
wasnt already specified as a custom color. Therefore, the
property editor presents our dialog box to allow the user to
pick the color. To enforce the naming scheme that Borland
has established, a color constant is only recognized as such
if the first two characters of the name are cl.
When the custom property editor is displayed, the user can
simply click on the panel under the constant name. This displays the common Color dialog box provided by Windows.
The user can then select one of the basic colors shown, or create
a new color by clicking the Define Custom Colors button. After
closing this dialog box, the panel that had been clicked will be
the selected color. By clicking the Save button, the property
editor adds that color constant definition into its internal array
(which is then displayed by means of the list box). Clicking the
Informant Spotlight
OK button closes the property
editor and populates the original
Color property with the selected
color constant.
Within the ColorEdt property editor, a string list is configured to hold the custom color constants that have been
15
Informant Spotlight
procedure TRVColorProperty.SetValue(const Value: string);
var
NewValue
: Longint;
CurrentValue : string;
RVColorDialog : TRVColorDialog;
begin
if IdentToColor(Value, NewValue) then
SetOrdValue(NewValue)
else
if Value <> '' then
begin
CurrentValue := RVColorsList.Values[Value];
if CurrentValue <> '' then
SetOrdValue(StrToInt(CurrentValue))
else
if (UpperCase(Copy(Value,1,2)) = 'CL') or
(Value[1]='?') then
begin
RVColorDialog := TRVColorDialog.Create(Application);
try
with RVColorDialog do begin
if Value[1] <> '?' then
edtColorConstant.Text := Value
else
edtColorConstant.Text := '';
pnlColor.Color := clWhite;
if ShowModal = mrOK then
SetOrdValue(pnlColor.Color);
end;
finally
RVColorDialog.Free;
end;
end
else
inherited SetValue(Value);
end
else
inherited SetValue(Value);
end;
Now well discuss the last of the three modifications: the procedure SetValue, the exact opposite of GetValue. It takes a
color label (such as clOrange) and converts it into the color
constant that is defined in Figure 11.
SetValue is just a little more complicated than GetValue.
First, we see if the color label is one that is already defined
by Delphi. The IdentToColor function is used to make this
determination. If so, SetValue ends and uses SetOrdValue
to pass the numeric value of the color back to the Object
Inspector.
If its not a standard color, then we again need to look
into the RVColorsList string list to see if the color label is
defined as one of our custom colors. Again, if its found,
the procedure exits and returns the selected value back to
the Object Inspector by means of the SetOrdValue procedure. However, if it isnt, we need to check whether the
user is typing in a new custom color, or wants to see the
ColorEdt dialog box.
As mentioned before, we had established that if a string
value was entered and wasnt recognized, but began with
the letters cl, then the property editor dialog box is displayed. In addition, if the string value entered was a question mark, the dialog box is also displayed. If the user
16
procedure LoadCustomColorsFromIni;
begin
RVColorsList.Clear;
ColorINI := TIniFile.Create('RVCOLORS.INI');
ColorINI.ReadSectionValues('Custom Colors',RVColorsList);
ColorINI.Free;
end;
procedure SaveCustomColorsToIni;
var
I : Integer;
begin
ColorINI := TIniFile.Create('RVCOLORS.INI');
ColorINI.EraseSection('Custom Colors');
for I := 0 to RVColorsList.Count-1 do
ColorINI.WriteString('Custom Colors',
FirstHalf(RVColorsList[I]),
SecondHalf(RVColorsList[I]));
ColorINI.Free;
end;
initialization
RVColorsList := TStringList.Create;
LoadCustomColorsFromIni;
finalization
SaveCustomColorsToIni;
RVColorsList.Free;
Finishing Touches
Finally, we had to instruct the property editor to save these
custom colors somewhere, and to restore them each time
Delphi was restarted. The simplest way was to use an .INI
file. At the bottom of the COLOREDT.PAS file we see the
code shown in Figure 12.
The initialization and finalization sections of the code may
be new to some of you. The code in the initialization section
of a unit is executed once when the application starts. The
code in the finalization section of a unit is likewise executed
once, when the application closes.
These two sections are used to load and save the custom colors
to the .INI file. When the program starts, the string list
RVColorsList is created. Then the LoadCustomColorsFromIni procedure is called to populate this string list. When the application
shuts down, the SaveCustomColorsToIni procedure writes out the
custom colors and disposes of the RVColorsList string list.
Conclusion
Because of space limitations, a discussion of how the
ColorEdt property editor dialog box functions is beyond the
scope of this article. However, all of its features are fairly
Informant Spotlight
straightforward and are covered in the accompanying source
code. You may want to examine some of the more interesting aspects of this dialog box, e.g. how the owner-draw list
box is built.
The simple function AdjustColor shows an easy way of scaling color brightness, but the principles involved can easily
be extended into additional types of color manipulation.
For example, you could provide a function that decreases
the blue component, while increasing the green component.
These techniques of manipulating Delphis color constants
demonstrate more of Delphis easy-to-implement graphics
capabilities. Just like fingerpainting, the possibilities are limited only by your artistic flair.
The demonstration files referenced in this article are available on
the Delphi Informant Works CD located in
INFORM\96\NOV\DI9611RV.
Robert Vivrette is a Senior Programmer/Analyst for Pacific Gas & Electric and
Technical Editor for Delphi Informant. He is also author of a free, online journal,
The Unofficial Newsletter of Delphi Users, that can be found at
http://www.informant.com/undu. He can be reached on CompuServe at
76416,1373 (on the Internet, use 76416.1373@compuserve.com).
17
OP Tech
Delphi 2 / Object Pascal
By Ray Lischner
Subproperty Editors
Undocumented Tricks for Creating Surrogate
Components
oure finishing a nifty new calendar component. All you need to complete
your finest work is a property editor that lets the user choose the date the
calendar displays. You consult the Component Writers Guide and find the
paSubProperties flag. Great! Its just what you want. The user can double-click
the Date property to expose the Day, Month, and Year properties.
But, how do you create the subproperty editors? The short answer is ... you cant. The
only property editors that can have subproperties are TClassProperty and TSetProperty
(and their subclasses). If you want to define
subproperties for any other type of property,
such as TDateTime, youre out of luck.
Or are you? Through a little clever programming, you can create fake subproperties. This
article instructs you how to create subproperty editors for any type of property.
Subproperty Review
Lets quickly review properties and subproperties. The Object Inspector displays the
published properties of a component. When
you write a custom component, you can also
supply property editors to make it easier to
use your component at design time. You can
also write property editors for any of the
standard components, and you can replace
standard property editors with your own.
Every property has a type specifically, an
integer, floating point (except Real ), enumerated, set, character, class, method, or string.
In Delphi 2, you can also have Variant or
WideChar properties. Each property has a
property editor, which is an instance of the
18
OP Tech
TLabel component
Properties of TLabel
Caption
property
Font
property
Height
property
FontStyles
property
Height
property
fsItalic
property
fsStrikethru
property
Subproperties of Font
Color
property
Subproperties of FontStyles
fsBold
property
fsUnderline
property
19
Now you know what you cant do. So what can you do? You
can derive a class from TSetProperty or TClassProperty, and let
the parent class handle the subproperties for you. If you want
to define subproperties for a type other than a set or class
type, then you need to fake out Delphi. You can create a
property editor that looks like it has subproperties to the user.
The way to accomplish this trick is to define a hidden component whose published properties are the desired subproperties.
OP Tech
procedure GetComponentProperties(
Components: TComponentList; Filter: TTypeKinds;
Designer: TFormDesigner; Proc: TGetPropEditProc);
type
{ Define a unique type identifier for each constituent
part. The default property editors limit the input to
the specified range. You can also supply custom
property editors, like TS_PropMonthProperty. }
TS_PropDateTimeDay
= 0..31;
TS_PropDateTimeMonth
= 0..12;
TS_PropDateTimeYear
= Integer;
TS_PropDateTimeHour
= 0..23;
TS_PropDateTimeMinute
= 0..59;
TS_PropDateTimeSecond
= 0..59;
TS_PropDateTimeMilliSec = 0..999;
{ Pseudocomponent that is used to create subproperty
editors for the constituent parts of a TDateTime. }
TS_PropDateTime = class(TComponent)
private
{ Cache the Date/Time property value. }
fDateTime: TDateTime;
{ Pointer back to the property editor. }
fEditor: TS_DateTimeProperty;
function GetDay: TS_PropDateTimeDay;
function GetMonth: TS_PropDateTimeMonth;
function GetYear: TS_PropDateTimeYear;
function GetHour: TS_PropDateTimeHour;
function GetMinute: TS_PropDateTimeMinute;
function GetSecond: TS_PropDateTimeSecond;
function GetMilliSec: TS_PropDateTimeMilliSec;
procedure SetDay(Value: TS_PropDateTimeDay);
procedure SetMonth(Value: TS_PropDateTimeMonth);
procedure SetYear(Value: TS_PropDateTimeYear);
procedure SetHour(Value: TS_PropDateTimeHour);
procedure SetMinute(Value: TS_PropDateTimeMinute);
procedure SetSecond(Value: TS_PropDateTimeSecond);
procedure SetMilliSec(Value: TS_PropDateTimeMilliSec);
procedure SetDateTime(Value: TDateTime);
public
property DateTime: TDateTime read fDateTime
write SetDateTime;
property Editor: TS_DateTimeProperty read fEditor
write fEditor;
published
{ Declare the properties in the order they should be
shown to the user. TS_DateTimeProperty preserves the
declaration order. }
property Year: TS_PropDateTimeYear read GetYear
write SetYear;
property Month: TS_PropDateTimeMonth read GetMonth
write SetMonth;
property Day: TS_PropDateTimeDay read GetDay
write SetDay;
property Hour: TS_PropDateTimeHour read GetHour
write SetHour;
property Minute: TS_PropDateTimeMinute read GetMinute
write SetMinute;
property Second: TS_PropDateTimeSecond read GetSecond
write SetSecond;
property MilliSec: TS_PropDateTimeMilliSec
read GetMilliSec write SetMilliSec;
end;
When the date and time are set, the property editor must be
informed of the new value. This happens in the SetDateTime
20
OP Tech
{ When the user changes a constituent part of the DateTime,
notify the master property editor, so it can update the
property value string. As per standard property editor
usage, changing a property value sets the value for all
selected property editors. }
procedure TS_PropDateTime.SetDateTime(Value: TDateTime);
begin
if fDateTime <> Value then
begin
fDateTime := Value;
if Editor <> nil then
Editor.SetDateTime(Value);
end;
end;
Figure 6: Setting the date and time and updating the property
editor.
type
{ The property editor for TDateTime. It creates a hidden
object and property editor just to access the subproperty
editors of the hidden property editors, pretending that
they are subproperties of TS_DateTimeProperty. }
TS_DateTimeProperty = class(TFloatProperty)
private
SubProps: TList;
{ Sorted list of subproperties. }
ChildList: TComponentList; { List of hidden objects. }
procedure GetSubProps(PropEdit: TPropertyEditor);
procedure SetChildValues;
public
destructor Destroy; override;
function GetAttributes: TPropertyAttributes; override;
function GetValue: string; override;
procedure SetValue(const Value: string); override;
procedure GetProperties(
Proc: TGetPropEditProc); override;
procedure SetDateTime(Value: TDateTime);
end;
Get Set
Implementing the new GetValue and SetValue functions is
easy, as shown in Figure 8. A zero date time is displayed as
an empty string, because its not a valid TDateTime value.
Similarly, an empty string is stored as a zero TDateTime
value. You must also override GetAttributes to set the
paSubProperties attribute.
When the TDateTime value changes in the property editor, the SetValue method calls SetChildValues. This notifies
the surrogate components of the new value. When the
property editor is destroyed, the list of child components
must also be freed. You have already seen that when any
child components value changes, the component notifies
21
OP Tech
{ When the Object Inspector requests the subproperties, it
is time for TS_DateTimeProperty to do its thing. Create a
hidden TS_PropDateTime object to parallel each component
that is currently selected. Then request the property
editors for the hidden TS_PropDateTime objects. Unfortunately, GetComponentProperties sorts the properties
alphabetically, which makes the subproperties more difficult to use. Instead, use the property's run-time type
info (TPropInfo) to get its NameIndex. The NameIndex
gives the order in which properties are declared, which
is the order in which these particular properties should
be shown to the user. }
procedure TS_DateTimeProperty.GetProperties(Proc:
TGetPropEditProc);
var
PropDateTime: TS_PropDateTime;
I: Integer;
begin
if ChildList <> nil then
SetChildValues
else
begin
ChildList := TComponentList.Create;
for I := 0 to PropCount-1 do begin
PropDateTime := TS_PropDateTime.Create;
PropDateTime.DateTime := GetFloatValueAt(I);
PropDateTime.Editor := Self;
ChildList.Add(PropDateTime);
end;
end;
SubProps := TList.Create;
try
{ Build a list of subproperty editors,
in declaration order. }
GetComponentProperties(ChildList, [tkInteger],
Designer, GetSubProps);
for I := 0 to SubProps.Count-1 do
{ The subproperty editor list has holes for
Name and Tag. Skip them. }
if SubProps[I] <> nil then
Proc(TPropertyEditor(SubProps[I]));
finally
SubProps.Free;
SubProps := nil;
end;
end;
{ Discard property editor for Tag, so only the ones that
are specific to date and time are shown to the user. }
procedure TS_DateTimeProperty.GetSubProps(
PropEdit: TPropertyEditor);
var
Index: Integer;
begin
if CompareText(PropEdit.GetName, 'Tag') = 0 then
PropEdit.Free
else
begin
{ Keep the sub property editors in
declaration order. }
Index := TExposePropertyEditor(PropEdit).NameIndex;
if Index >= SubProps.Count then
SubProps.Count := Index+1;
SubProps[Index] := PropEdit;
end;
end;
value changes. The ChildList field holds the list of surrogate components.
The GetProperties method must issue a callback procedure
for each subproperty editor. Start by calling
GetComponentProperties, which creates and initializes the
property editors for the subproperties. Set the filter to
tkInteger, so GetComponentProperties returns property editors only for the integer-type properties. All the constituent date and time properties are integers, but so is the
Tag property. Remove the Tag property by looking at its
name and freeing the property editor if it is Tag.
All in Order
By default, these property editors are sorted in alphabetical
order (Day, Hour, MilliSec, Minute, Month, Second, Year). They
are most useful, however, when they are ordered by magnitude
(i.e. Year, Month, Day, Hour, Minute, Second, MilliSec), so you
should rearrange them. Thus, GetSubProps stores the property
editors in a list, SubProps. The index of each property editor in
the list is given by the editors NameIndex, which is its index in
declaration order. Remember that in TS_PropDateTime, the
order of the published properties is the same order the Object
Inspector should use. This leaves holes in the list (e.g. for Tag),
so skip over any nil items in the list.
After the list is built, issue the callback procedure Proc for
each subproperty. The Object Inspector can then display the
property values for the subproperties. Figure 10 shows the
GetProperties and GetSubProps methods.
22
OP Tech
{ A special property editor for the month. }
type
TS_MonthProperty = class(TIntegerProperty)
public
function GetAttributes: TPropertyAttributes; override;
function GetValue: string; override;
procedure SetValue(const Value: string); override;
procedure GetValues(Proc: TGetStrProc); override;
end;
{ Add a value list, with all the month names. }
function TS_MonthProperty.GetAttributes:
TPropertyAttributes;
begin
Result := inherited GetAttributes + [paValueList]
end;
{ Get the value string by looking up the month number. }
function TS_MonthProperty.GetValue: string;
begin
Result := LongMonthNames[GetOrdValue]
end;
{ Set a new value by looking up the month name. If its not
found, call the inherited SetValue to convert the integer
value. Check the long and short month names. }
procedure TS_MonthProperty.SetValue(const Value: string);
var
Month: Integer;
begin
for Month := Low(LongMonthNames)
to High(LongMonthNames) do
if CompareText(Value, LongMonthNames[Month]) = 0 then
begin
SetOrdValue(Month);
Exit;
end;
for Month := Low(ShortMonthNames)
to High(ShortMonthNames) do
if CompareText(Value, ShortMonthNames[Month]) = 0 then
begin
SetOrdValue(Month);
Exit;
end;
Putting It to Use
Remember that the purpose of
adding subproperty editors to the
date/time property editor is to
Figure 13: An example of
make it easier for the user to set a the TS_DateTimeProperty
editor in use.
date and time. It is simplest to
choose a month by name, not by
number. Thus, you can define the TS_MonthProperty editor,
as shown in Figure 12.
Now that the TS_DateTimeProperty editor is complete,
how is it used? Again, quite simply! Remember that this
property editor will apply to any property of type
TDateTime. Therefore, all you need to do is add a new
property for a component that is of type TDateTime, and
the TS_DateTimeProperty will automatically provide the
property editor for it.
Figure 13 shows the new TS_DateTimeProperty editor in
use. Notice how easy it is to enter a specific date or time
by entering the constituent parts. Figure 14 shows the unit
S_Test.pas used to test the TS_DateTimeProperty editor.
unit S_Test;
{ Test components for demonstrating property editors. }
interface
inherited SetValue(Value);
end;
uses
SysUtils, Classes, DsgnIntf;
type
TS_DateTimeTester = class(TComponent)
private
fDateTime: TDateTime;
public
constructor Create(Owner: TComponent); override;
published
property FancyDateTime: TDateTime read fDateTime
write fDateTime;
property DateOnly: TDateTime read fDateTime
write fDateTime;
end;
implementation
{ An object is initialized to zero, but that is not a valid
TDateTime value, so initialize the TDateTime to something
that makes sense, such as the current date and time. }
constructor TS_DateTimeTester.Create(Owner: TComponent);
begin
inherited Create(Owner);
SimpleDateTime := Now;
end;
end.
Figure 14: The unit S_Test.pas used to test the TDateTime property editor.
OP Tech
Conclusion
Delphi makes it difficult to create subproperties for arbitrary
property types, but it is possible. You can create a surrogate
component that defines the desired subproperties. The parent
property editor creates an instance of the surrogate component, and pretends that the components properties are its
own subproperties. With a little extra work to ensure the parent property editor and the subproperty editors communicate
their changes, you can complete the illusion that your property has subproperties. And users never know about the
magic that takes place behind the scenes.
This article is based on material for Ray Lischners Secrets
of Delphi 2 [Waite Group Press, 1996]. The book is available for US$49.99 from your local bookstore, or by calling
(800) 428-5331.
The demonstration source referenced in this article is available
on the Delphi Informant Works CD located in
INFORM\96\NOV\DI9611RL.
24
DBNavigator
Delphi 1 / Delphi 2
Cross-Platform Delphi
or, IFDEFing for Fun and Profit
t seems we are forever writing applications for two platforms: the one
that dominates the marketplace (currently Windows 3.1), and the one
that will (Windows 95). Specifically, Delphi programmers must be able to
move easily between the 16-bit, segmented-memory environment of
Delphi 1, and the 32-bit, flat-memory world of Delphi 2 often with the
same application.
However, compiling 16- and 32-bit executables from one set of source requires planning,
and sometimes, compromise. This months
column discusses why this is increasingly so.
It also reviews some of the issues and techniques you need to keep in mind when
building cross-platform applications.
Background
Delphi 2 has been available for almost nine
months. Unfortunately for Borland, the
acceptance of Microsoft Windows 95 is less
widespread than expected, especially among
large corporations. Likewise, the number of
end-users running Windows NT is relatively
small. Consequently, not many Delphi developers have been developing applications
exclusively for the 32-bit platform, which is
the only platform that can run Delphi 2. In
fact, based on my interactions with other
Delphi developers, most continue to program exclusively in Delphi 1.
This situation wont last forever; recent developments pave the way for an increase in 32bit applications. The first is the cost of RAM
and hard disk space. Because Windows 95,
and particularly Windows NT, require a lot
of both, the cost of these resources was one
25
obstacle preventing companies from upgrading their operating systems. Now, however,
price is not nearly as big a factor.
The second development is the release of
Windows NT 4.0. Many companies have
been waiting to make their operating system
decision based on this new release of NT.
Now that its available, some corporations
will upgrade to NT 4.0, while others will
decide to migrate to Windows 95.
As a result of these developments, its likely
most independent consultants, as well as
many corporate developers, will find themselves building Delphi applications that must
run on both 16- and 32-bit operating systems. Unfortunately, unlike Borlands C/C++
product line, Delphi 2 doesnt permit you to
compile both. Instead, a developer must
compile an application using Delphi 1 to
create a 16-bit .EXE, and again in Delphi 2
to create the 32-bit version.
Using two versions of Delphi to compile an
application in 16 and 32 bits is often more
than inconvenient. It can involve major compromises and time-consuming coordination.
The reason is that Delphi 2s feature set is not
DBNavigator
only more extensive than Delphi 1s, but there are also incompatibilities between the two products. While the original Delphi
1 VCL (Visual Component Library), and RTL (run-time
library) were designed to be compilable on 16- or 32-bit operating systems, the same is not entirely true of Delphi 2s VCL.
What to Do?
The following is a list of considerations, potential problems,
and possible solutions that you can use when you need to
build a cross-platform application.
Use Delphi 1 to design and maintain the application. Start
by using only those components shared by Delphi 1 and 2.
One way to ensure this is to do all your design work in
Delphi 1; every component available in Delphi 1 is available
in Delphi 2. While this may result in a 32-bit application
that looks more like a Windows 3.x application, it prevents
you from maintaining two sets of source files.
Another reason to do all your design work in Delphi 1 is that
it prevents Delphi 2 from storing properties in the DFM file
that arent in the Delphi 1 version. If you accidentally modify
a project under Delphi 2 and it adds a property to the DFM
file, youll see an error message when you attempt to load
that application in Delphi 1. (You can usually get around this
by instructing Delphi 1 to ignore the error. As long as you do
not need that property in which case the application cannot be compiled with Delphi 1 the next time you compile
the application in Delphi 1, it will remove the unnecessary
property from the DFM file.)
Avoid non-Delphi objects. Avoid using objects that arent
native Delphi components. Specifically, do not use VBXes,
OCXes, or ActiveX controls. VBXes are only supported
under Windows 3.x, while OCXes and ActiveX controls are
supported only under Windows 95 and Windows NT.
Be aware of data type ambiguities. Avoid code that is sensitive to the size and range of platform-dependent data types.
For example, dont write code that assumes an integer will be
either 16- or 32-bit. (This includes strings, which well discuss shortly.)
Use conditional compiles. Use compiler directives to conditionally compile code that can only be compiled under one
platform or the other. For example, the following statement
checks the pre-defined conditional symbol, WIN32, and then
declares the variable InitStorage as a TIniFile type in Delphi
1, or a TRegIniFile type in Delphi 2, accordingly:
{$IFDEF WIN32}
var
InitStorage: TRegIniFile;
{$ELSE}
var
InitStorage: TIniFile;
{$ENDIF}
uses
SysUtils, WinTypes, WinProcs, Messages,
{$IFDEF WIN32}
Registry,
{$ELSE}
IniFiles,
{$ENDIF}
Classes, Graphics, Controls, Forms, Dialogs;
Watch those DLLs. 16- and 32-bit DLLs are not compatible, so youll need to create and compile them separately.
DLLs created for Windows 95 should use the stdcall directive for compatibility with other 32-bit DLLs. This directive takes the place of the export directive used in Delphi 1.
[For an in-depth discussion of this topic (and many others),
see Ray Lischners article Classy DLLs in the October
1996 Delphi Informant.]
Watch unit size. Remember that Delphi 1 cant use units
over 64KB. Delphi 2 has no such limit. This is why Delphi 1
uses two units to provide the Windows 3.x interface:
DBNavigator
Figure 1:
The Library
page of the
Environment
Options
dialog box.
27
Conclusion
While creating a single set of source files that can be compiled using Delphi 1 and Delphi 2 requires some coordination, it is not particularly difficult. Furthermore, the benefit
of maintaining a single source base will usually far outweigh
the burden of making the source compatible with both
Delphi 1 and 2.
Interestingly, the two most common requests from Delphi
developers at the recent Borland Developers Conference were
for a single version of Delphi that can compile 16- or 32-bit
applications, and an updated version of Delphi for Windows
3.x. If either request is granted, building cross-platform applications with Delphi would be even easier.
Cary Jensen is President of Jensen Data Systems, Inc., a Houston-based database
development company. He is author of more than a dozen books, including Delphi
In Depth [Osborne/McGraw-Hill, 1996]. He is also Contributing Editor to Delphi
Informant. You can reach Jensen Data Systems at (713) 359-3311, or via
CompuServe at 76307,1533.
By James Callan
This article explains how you can use inheritance, polymorphism, and specialization in
your client/server applications. Well begin with
concrete examples of these techniques in action,
and provide a firm conceptual foundation.
Moving to database modeling, well introduce
subtypes. Well end by creating an Extra
Sensory Perception (ESP) game that builds on
all the ideas introduced in this article, and
shows you how to add Mighty Morphing
Power Grids to your next application.
Polly Who?
What, you may ask, are Mighty Morphing
Power Grids? Theyre those super-snazzy
dual-row grids in Quicken and other killer
applications that visibly change, depending
on what kind of data you add. These applications use one simple form for dozens of dif-
Some of these sets are nested within other sets. These nested
sets are called subsets. The set of all books is a superset to the
set of all books youve read. Similarly, the books youve read
are a subset of all books. Both My Books and Your
Books share a common ancestry. We could say that both
subsets inherit characteristics from their superset. Lets examine this inheritance more closely.
Books can be read. I read with a pen in hand and tend to
make notes in the margins. When you read, however, you
may leave your books in pristine condition. Both sets of books
can respond to being read, yet the fact that their state is different after having been read can be an indication of polymorphism. Thus, the mere act of reading could specialize a book,
and be used to distinguish one book from another.
... to Databases
A relational database is a collection of tables that interact
based on the principles of relational algebra (which is itself
used to manipulate sets). A well-designed database provides
rich information about the real world entities represented by
the data stored in the tables. A database is a set of tables. A
table is a collection of interesting characteristics about similar
entities such as people, places, or things. Tables are represented by columns and rows. Each row represents one entity
(or object). A table is thus a set of characteristics of entities in
the world we are modeling.
Understanding sets is imperative to developing useful databases.
For example, you create an intersection between two sets when
you execute a SQL SELECT statement that joins two tables. As
well see, sets are also critical to building powerful data grids.
30
Figure 4: A
more complex
ERD.
Hover Editing
Data Model
Well use the ubiquitous Panel component as an editing surface. Because you can place any control on it, using a Panel
brings control choice freedom. If we need to morph our panel
31
Next, add a MainMenu component to the form, then double-click on it to create the forms main menu. Using the
Menu Designer, add &File, &View, and &Help as top-level
menu options. Under File add &New and &Delete, followed
by a separator (insert a dash character for its Caption), and
E&xit. Add &Accuracy under View and set its shortcut key to
1 via the drop-down list of keys. Lastly, add an &About
option under Help, and close the Menu Designer.
On Panel2, place a third Panel and a Button. Set Panel3s
Alignment property to taLeftJustify, its BevelOuter property to
bvLowered, and its Font.Color property to clBlue. Name the
Button OkBtn and set its Caption to &OK. Next, make sure the
Caption properties for Panel2 and Panel3 are blank. The lower
panel should now match the one in Figure 8.
On the main form, just above Panel2, drop an additional
Panel and name it EditPanel. On EditPanel, place six
BitBtn components and space them equally. Set their
Height and Width properties to 38 and make their Caption
properties blank; then set their Glyph properties to the
bitmap files that you created earlier in the Image Editor.
Set the Tag properties for the BitBtn components to 1
through 6 (well use the Tags later) and name them
33
The MorphPanel method changes the EditPanel into a decorated modal dialog box. This is a nifty technique that is helpful for eliminating additional forms from projects. The Close
SpeedButtons OnClick event is dynamically set to the
CloseGraph method. It restores the EditPanel and returns it
to its proper position. The CalcAccuracy method uses some
embedded dynamic SQL to determine the percentage of correct answers as the test proceeds.
Conclusion
Weve discussed how and why subtypes arise in data models.
We have also demonstrated that using subtypes in our data
models gives us new ways of adding inheritance and specialization to our database applications.
By using polymorphism in data-aware grids, we can alter
the appearance of data, and change the way the data is
entered. Through polymorphism, we can shroud diversity
and divine differences. Perhaps the subtle power of Delphi
is now more apparent?
All source code, bitmaps, and database files for the sample
Psychic Diviner program are available on the Delphi Informant
Works CD located in INFORM\96\NOV\DI9611JC.
James Callan, an 18-year computing veteran and former consulting director for
Oracle Corporation, is currently president of Gordian Solutions, Inc., an information technology consulting provider in Cary, NC. A frequent writer and speaker on
information technology and client/server computing, Jim specializes in product
design. He can be reached at (919) 460-0555, or by e-mail at
102533.2247@compuserve.com.
35
W = Waves
S = Star
R = Square (Rectangle) }
36
end;
end;
TrialsDS.Enabled := True;
// Position on description field
TrialsTB.First;
TestDescriptionFld.SetFocus;
// Resynch test name field
TestsLKUTB.GotoCurrent(TestsTB);
// Make sure lookup refreshes
TestNameLKUFld.DataSource := nil;
TestNameLKUFld.DataSource := TestsDS;
end
else
messageDlg('New Test Generation Cancelled',
mtInformation, [mbOK], 0)
end;
procedure TForm1.Delete1Click(Sender: TObject);
begin
if messageDlg(
'Are you sure you want to delete this test?',
mtConfirmation, [mbYes, mbNo], 0) = mrYes then
begin
if EditPanel.Visible then
EditPanel.Visible := False;
DeleteTrialsSQL.ParamByName('TESTID').AsInteger :=
TestsTB['TestID'];
DeleteTrialsSQL.ExecSQL;
TestsTB.Delete
end
37
:= 'N';
38
At Your Fingertips
Delphi / Object Pascal
By David Rippy
Henry Ford
Debugging forms that contain Query components can be tricky at times, particularly if several SQL statements can potentially be assigned
At Your Fingertips
procedure TForm1.Button1Click(Sender: TObject);
var
TblTemp: TTable; { Declare TblTemp }
begin
{ Create instance of TblTemp }
TblTemp := TTable.Create(Self);
TblTemp.DatabaseName := '';
with TblTemp do begin
try
TableName := 'FRUIT.DB';
Open;
First;
while not EOF do begin
ListBox1.Items.Add(FieldByName('Name').AsString);
Next;
end;
finally
Close;
Free;
end;
end;
end;
Figure 3:
SaveToFile
allows you
to save
the SQL
property
value to a
text file.
Figure 6:
OnTimer
and
OnClick
events for
the Timer
and
Button
components.
You can adjust the blink rate by setting the Interval property
of Timer1. The lower the value, the faster the Label will
blink. As you might have guessed, this technique will work
on any object with a Visible property not just Label components. D.R.
Figure 4: You can use any text editor to view the SQL statement
once it is saved.
40
David Rippy is a Senior Consultant with Ensemble Corporation, specializing in the design and deployment of client/server database applications. He has contributed to several books published by QUE. David
can be reached on CompuServe at 74444,415.
Case Study
By David Rippy
Inquire Within
Engaging Kiosk System Reels in Apartment Shoppers
Implementation
Case Study
Results
A critical factor to the success of the system was
Ensembles ability to bring to life the vision of LPCs
Marketing Director. Using Ensembles iterative prototyp42
Case Study
walkouts, increased apartment rentals, and saved thousands of dollars annually in
printing and administrative
costs. With the business concept now proven, the system
will be rolled out to other
LPC properties, each with its
own set of custom floor
plans, amenities, and area
attractions.
The kiosk system is truly a testament to Delphis ideal balance of compiler technology
and rock-solid database features a perfect match for
interactive applications such as
this. More importantly to the
client, Delphis Rapid
Application Design approach
kept development time (and
cost) to a minimum.
APPLICATION PROFILE
Ensemble Corporation of Dallas, TX recently
implemented a state-of-the-art, multimedia
kiosk system for the Knoxbridge apartment
complex, managed by Lincoln Property
Company. The interactive application was
designed to entertain and inform potential
apartment renters when a leasing agent is not
available. Users can review and print out
apartment floor plans, examine amenities, and
learn about local merchants and institutions.
Target Audience: Property management
companies and real-estate agents.
Users: Potential apartment renters and
apartment leasing agents.
Third-Party Tools: Autodesk 3D Studio.
Ensemble Corporation
12655 N. Central Expressway, Suite 700
Dallas, TX 75243
Phone: (214) 960-2700
Fax: (214) 960-2704
Autodesk
642 Harrison St., San Francisco, CA 94107
Phone: (415) 547-2000
Fax: (415) 547-2222
Web Site: http://www.autodesk.com
E-mail: Internet: ktxwebmaster@ktx.com
43
Delphi at Work
Delphi 1/ Delphi 2 / Object Pascal
By Shamiq Cader
A Matter of Time
Exploring the TDateTime Object
ave you ever tried to add five minutes and 30 seconds to the current
time? At first this may seem like a trivial computation. If you look closer, however, the complexity of this logical operation becomes clear. For
example, what do you do if the time is 11:55 PM on the 31st of
December? Suddenly, this doesnt look so trivial.
Wouldnt it be nice if a programming language exists that allows you to easily perform
date and time manipulation? Actually there
is. The language, of course, is Object Pascal,
and its the TDateTime type that makes date
and time manipulation a breeze.
TTimeDates Advantages
Delphi at Work
cannot be understood as a valid date and time? Fortunately,
Delphi provides several functions to convert this cryptic
number.
:= StrToDate(Edit2.Text);
:= StrToTime(Edit1.Text);
:= MyDate + MyTime;
{ MyDate
= X.0 }
{ MyTime
= 0.Y }
{ DateTime = X.Y }
Delphi at Work
and 96 indicate
very different
things, i.e. 96
equates to 96 AD,
not 1996 AD.
The FormatDateTime function takes two parameters: the format string indicating the desired format and TDateTime.
Several formats can be obtained by using FormatDateTime.
Delphis online Help features a complete list of format specifiers; just search using FormatDateTime.
Lets look at an example of FormatDateTime. Assuming
MyDate has 01/08/96, using:
FormatDateTime('yyyy mmmm dd',MyDate)
yields 1996
January 08
in the display.
If yy was used:
FormatDateTime('yy mmmm dd',MyDate)
January 08.
The sidebar Date and Time Combos on page 47 lists all the
possible combinations for the date January 8, 1996 and the
time 2:05:08 (assigned to the MyDate variable).
Delphi at Work
functions to achieve this. These are
FileDateToDateTime:
Specifier
Example
d
dd
ddd
dddd
ddddd
8
08
Mon
Monday
1/8/96
dddddd
m
Monday January
8, 1996
1
mm
01
mmm
mmmm
yy
yyyy
Jan
January
96
1996
Description
Displays
Displays
Displays
Displays
Displays
format
Displays
format
Displays
zero
Displays
zero
Displays
Displays
Displays
Displays
FormatDateTime('dddd/dd/mm/yy',MyDate)
The separator used ( / ) could be changed to ( - ) by changing the format string to:
'dddd-dd-mm-yy'
Specifier
Example
h
hh
n
2
02
5
'dddd/dd/mm/yy'
nn
s
05
8
ss
t
08
2:05 AM
tt
02:05:08 AM
am/pm
02:05:08am
a/p
02:05:08a
instead of:
Description
Displays hour without leading zero
Displays hour with leading zero
Displays minutes without leading
zero
Displays minutes with leading zero
Displays seconds without leading
zero
Displays seconds with leading zero
Displays time using short time
format
Displays time using long time
format
Uses 12-hour clock and inserts
am/pm immediately after time
string
Same as above but inserts a or p
Notice that the separator can be changed by inserting any value between the format
codes.
Delphis online Help has a list of the available format strings. To reference them,
search on formatting data.
Shamiq Cader
47
FileDateToDateTime(DosVar : Longint) :
TDateTime
and DateTimeToFileDate:
DateTimeToFileDate(DT : TDateTime) :
Longint
Delphi at Work
Function(s)
Now, Date, Time
DateToStr, TimeToStr
StrToDate, StrToTime
DecodeDate,
DecodeTime
EncodeDate,
EncodeTime
DayOfWeek
FormatDateTime
FileDateToDateTime,
DateTimeToFileDate
GetTime, GetDate,
SetTime, SetDate
Description
Retrieves system date/time or both
Converts TDateTime to string
Converts string to TDateTime
Separates date/time into individual
components
Assembles the TDateTime from the
individual components
Returns day of week for given date
Formats date/time for output
Converts to and from DOS date/time
format
Object Pascal routines
Conclusion
Confronting date and time issues in your Windows applications is not easy. (And this programmers task will be monstrous as the year 2000 approaches. For more information on
managing end-of-the-century software issues, see the sidebar
On December 31, 1999, at 11:59:59 PM ... on page 48).
Delphi at Work
Begin Listing Three System Date and Time
{ Demo program to extract system date and time }
unit Fig1;
var
Form1: TForm1;
implementation
interface
{$R *.DFM}
uses
SysUtils, WinTypes, WinProcs, Messages, Classes,
Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
DateTime : TDateTime;
begin
{ Extracts the date and time }
DateTime := Now;
{ Converts the date to a string }
Edit1.Text := DateToStr(DateTime);
{ Converts the time to a string }
Edit2.Text := TimeToStr(DateTime);
end;
end.
49
Delphi at Work
{ e.g. 1996-January-08 }
Edit2.Text := FormatDateTime('yyyy-mmmm-dd',MyDate);
{ e.g. Jan,8,96,Mon }
specific formats }
Edit3.Text := FormatDateTime('mmm,d,yy,ddd',MyDate);
unit Fig3;
{ e.g. Monday/08/01/96 }
Edit4.Text := FormatDateTime('dddd/dd/mm/yy',MyDate);
interface
{ e.g. 10:50 PM }
Edit6.Text := FormatDateTime('t',MyTime);
uses
{ e.g. 10:50:00 PM }
Edit7.Text := FormatDateTime('tt',MyTime);
{ e.g. 10-50-00-p }
Edit8.Text := FormatDateTime('hh-nn-ss-a/p',MyTime);
type
end;
TForm1 = class(TForm)
Label1: TLabel;
end.
Label2: TLabel;
Label3: TLabel;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Edit4: TEdit;
Label4: TLabel;
Edit5: TEdit;
unit Fig4;
Edit6: TEdit;
Edit7: TEdit;
interface
Edit8: TEdit;
Label5: TLabel;
uses
Label6: TLabel;
Button1: TButton;
Button2: TButton;
procedure Button2Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
type
TForm1 = class(TForm)
private
Label1: TLabel;
{ Private declarations }
Label2: TLabel;
public
Label3: TLabel;
Label4: TLabel;
{ Public declarations }
Edit1: TEdit;
end;
Edit2: TEdit;
Edit3: TEdit;
var
Button1: TButton;
Form1: TForm1;
Button2: TButton;
Label5: TLabel;
implementation
Edit4: TEdit;
Label6: TLabel;
{$R *.DFM}
Edit5: TEdit;
procedure Button2Click(Sender: TObject);
{ Private declarations }
Edit1.Text := '';
public
Edit2.Text := '';
Edit3.Text := '';
{ Public declarations }
Edit4.Text := '';
end;
Edit5.Text := '';
Edit6.Text := '';
var
Edit7.Text := '';
Form1: TForm1;
Edit8.Text := '';
end;
implementation
{$R *.DFM}
var
MyDate, MyTime : TDateTime;
begin
50
begin
MyDate := StrToDate(Edit1.Text);
{ e.g. 01/08/96 }
MyTime := StrToTime(Edit5.Text);
{ e.g. 22:50 }
Edit1.Text := '';
Delphi at Work
Edit2.Text := '';
Edit3.Text := '';
Edit4.Text := '';
end;
procedure TForm1.Button1Click(Sender: TObject);
var
DT
: TDateTime;
ErrorFlag
: Boolean;
DOW
: Integer;
begin
ErrorFlag := FALSE;
{ Try to encode the year, month and day given by
the user }
try
DT := EncodeDate(StrToInt(Edit3.Text),
StrToInt(Edit1.Text),
StrToInt(Edit2.Text));
{ Exception clause }
except
{ If an error occurs set the flag }
on EConvertError do ErrorFlag := True;
end;
if ErrorFlag then
{ Error flag was needed to catch the else part }
ShowMessage('Invalid Date')
else
{ If valid display date }
Edit4.Text := DateToStr(DT);
{ Get DayOfWeek integer and pass it into
the case statement }
DOW := DayOfWeek(DT);
{ Match the string }
case DOW of
1 : Edit5.Text := 'Sunday';
2 : Edit5.Text := 'Monday';
3 : Edit5.Text := 'Tuesday';
4 : Edit5.Text := 'Wednesday';
5 : Edit5.Text := 'Thursday';
6 : Edit5.Text := 'Friday';
7 : Edit5.Text := 'Saturday';
end;
end;
end.
51
TextFile
File | New
Directions / Commentary
eveloping a successful software application is not unlike engineering a quality automobile. The most respected autoD
mobile makers in the world such as Lexus, BMW, and Volvo are successful because of their ability to combine performance, reliability, and ergodynamics into a marketable package. So too, the most popular software applications combine these three qualities.
Unfortunately, this is far from typical of most software applications
built today. While most developers
focus on speed and stability, there is
a tendency to overlook the final element of this triad: usability. This
propensity to dismiss user interface
(UI) issues as trivial is prevalent
among developers and IT managers
alike. And even if Lexus had a superior engine and unrivaled reliability,
not many people would buy one if
it had wooden seats and an AM
radio. The same principle holds true
for your application: no matter
what is under the hood, the application will only be accepted if its UI
is successful. This month well look
at the Windows interface, focusing
on emerging trends in interface
design to keep in mind when creating Delphi applications.
Similarity. UIs are becoming increasingly similar. Windows applications
look far more alike today than they did
five years ago. At one time, it seemed
that every major Windows software
vendor had their own distinct look and
feel, as did Borland with BWCC.DLL
custom controls. Windows 95s new
interface along with Microsofts
rigid Win95 logo requirements
helped curb this trend. While different
vendors still put their own twists on an
applications UI, Windows 95 applications are remarkably similar.
The result is consistency across
applications. As Charles Petzold
once said, Even a bad interface is
good so long as its consistent
53