Delphi 7 - Developers Guide
Delphi 7 - Developers Guide
Borland®
™
Delphi 7
for Windows™
Borland Software Corporation
100 Enterprise Way, Scotts Valley, CA 95066-3249
www.borland.com
Refer to the DEPLOY document located in the root directory of your Delphi 7 product for a complete list of files that
you can distribute in accordance with the Delphi 7 License Statement and Limited Warranty.
Borland Software Corporation may have patents and/or pending patent applications covering subject matter in this
document. Please refer to the product CD or the About dialog box for the list of applicable patents. The furnishing of
this document does not give you any license to these patents.
COPYRIGHT © 1983–2002 Borland Software Corporation. All rights reserved. All Borland brand and product names
are trademarks or registered trademarks of Borland Software Corporation in the United States and other countries.
All other marks are the property of their respective owners.
Printed in the U.S.A.
HDE1370WW21001 7E5R0802
0203040506-9 8 7 6 5 4 3 2 1
D3
Contents
Chapter 1 Using object variables . . . . . . . . . . . . . . 4-7
Creating, instantiating, and destroying
Introduction 1-1 objects . . . . . . . . . . . . . . . . . . . . . . 4-8
What’s in this manual? . . . . . . . . . . . . . . 1-1
Components and ownership . . . . . . . . . 4-9
Manual conventions . . . . . . . . . . . . . . . . 1-2
Defining new classes . . . . . . . . . . . . . . . 4-9
Developer support services . . . . . . . . . . . . 1-3
Using interfaces . . . . . . . . . . . . . . . . . . 4-12
Part I Using interfaces across the hierarchy . . . . 4-13
Using interfaces with procedures . . . . . . 4-14
Programming with Delphi Implementing IInterface . . . . . . . . . . . 4-14
TInterfacedObject . . . . . . . . . . . . . . . 4-15
Chapter 2 Using the as operator with interfaces . . . . 4-16
Developing applications with Delphi 2-1 Reusing code and delegation. . . . . . . . . 4-16
Integrated development environment . . . . . . 2-1 Using implements for delegation . . . . 4-17
Designing applications . . . . . . . . . . . . . . 2-2 Aggregation . . . . . . . . . . . . . . . . 4-18
Creating projects . . . . . . . . . . . . . . . . . . 2-3 Memory management of interface
Editing code . . . . . . . . . . . . . . . . . . . . 2-4 objects. . . . . . . . . . . . . . . . . . . . . 4-18
Compiling applications . . . . . . . . . . . . . . 2-4 Using reference counting . . . . . . . . 4-19
Debugging applications . . . . . . . . . . . . . . 2-5 Not using reference counting . . . . . . 4-20
Deploying applications . . . . . . . . . . . . . . 2-5 Using interfaces in distributed
applications . . . . . . . . . . . . . . . . . 4-21
Chapter 3
Using the component library 3-1 Chapter 5
Understanding the component library . . . . . 3-1 Using BaseCLX 5-1
Properties, methods, and events . . . . . . . 3-3 Using streams . . . . . . . . . . . . . . . . . . . 5-2
Properties . . . . . . . . . . . . . . . . . . 3-3 Using streams to read or write data . . . . . 5-2
Methods . . . . . . . . . . . . . . . . . . 3-4 Stream methods for reading
Events . . . . . . . . . . . . . . . . . . . . 3-4 and writing . . . . . . . . . . . . . . . 5-2
User events . . . . . . . . . . . . . . . . . 3-4 Reading and writing components . . . 5-3
System events . . . . . . . . . . . . . . . 3-4 Reading and writing strings . . . . . . . 5-3
Internal events . . . . . . . . . . . . . . . 3-4 Copying data from one stream
Objects, components, and controls . . . . . . . . 3-5 to another. . . . . . . . . . . . . . . . . . . 5-4
TObject branch . . . . . . . . . . . . . . . . . 3-6 Specifying the stream position and size. . . 5-4
TPersistent branch . . . . . . . . . . . . . . . 3-7 Seeking to a specific position . . . . . . 5-4
TComponent branch . . . . . . . . . . . . . . 3-7 Using Position and Size properties . . . 5-5
TControl branch . . . . . . . . . . . . . . . . 3-9 Working with files . . . . . . . . . . . . . . . . 5-5
TWinControl/TWidgetControl branch . . . 3-10 Approaches to file I/O . . . . . . . . . . . . 5-6
Using file streams . . . . . . . . . . . . . . . 5-6
Chapter 4 Creating and opening files using
Using the object model 4-1 file streams . . . . . . . . . . . . . . . 5-7
What is an object? . . . . . . . . . . . . . . . . . 4-1 Using the file handle . . . . . . . . . . . 5-8
Examining a Delphi object . . . . . . . . . . 4-2 Manipulating files . . . . . . . . . . . . . . . 5-8
Changing the name of a component . . . . . 4-4 Deleting a file . . . . . . . . . . . . . . . 5-8
Inheriting data and code from an object. . . . . 4-5 Finding a file . . . . . . . . . . . . . . . 5-8
Scope and qualifiers . . . . . . . . . . . . . . . . 4-5 Renaming a file . . . . . . . . . . . . . . 5-10
Private, protected, public, and published File date-time routines . . . . . . . . . . 5-10
declarations . . . . . . . . . . . . . . . . . . 4-6 Copying a file . . . . . . . . . . . . . . . 5-11
iii
Working with ini files and the system Creating a simple conversion family
Registry . . . . . . . . . . . . . . . . . . . . . 5-11 and adding units. . . . . . . . . . . . . . . 5-34
Using TIniFile and TMemIniFile . . . . 5-12 Declare variables . . . . . . . . . . . . . 5-35
Using TRegistryIniFile . . . . . . . . . 5-13 Register the conversion family . . . . . 5-35
Using TRegistry . . . . . . . . . . . . . 5-13 Register measurement units . . . . . . . 5-35
Working with lists . . . . . . . . . . . . . . . . 5-14 Use the new units . . . . . . . . . . . . . 5-35
Common list operations . . . . . . . . . . . 5-15 Using a conversion function . . . . . . . . . 5-36
Adding list items . . . . . . . . . . . . 5-15 Declare variables . . . . . . . . . . . . . 5-36
Deleting list items . . . . . . . . . . . . 5-15 Register the conversion family . . . . . 5-36
Accessing list items . . . . . . . . . . . 5-16 Register the base unit . . . . . . . . . . 5-36
Rearranging list items . . . . . . . . . . 5-16 Write methods to convert to and
Persistent lists. . . . . . . . . . . . . . . . . 5-16 from the base unit . . . . . . . . . . . . 5-36
Working with string lists . . . . . . . . . . . . 5-17 Register the other units . . . . . . . . . 5-37
Loading and saving string lists . . . . . . . 5-17 Use the new units . . . . . . . . . . . . . 5-37
Creating a new string list . . . . . . . . . . 5-18 Using a class to manage conversions . . . . 5-37
Short-term string lists . . . . . . . . . . 5-18 Creating the conversion class . . . . . . 5-38
Long-term string lists . . . . . . . . . . 5-18 Declare variables . . . . . . . . . . . . . 5-39
Manipulating strings in a list . . . . . . . . 5-20 Register the conversion family and
Counting the strings in a list . . . . . . 5-20 the other units . . . . . . . . . . . . . . 5-39
Accessing a particular string . . . . . . 5-20 Use the new units . . . . . . . . . . . . . 5-40
Locating items in a string list . . . . . . 5-20 Defining custom variants . . . . . . . . . . . . 5-40
Iterating through strings in a list . . . . 5-20 Storing a custom variant type’s data . . . . 5-41
Adding a string to a list . . . . . . . . . 5-21 Creating a class to enable the
Moving a string within a list . . . . . . 5-21 custom variant type . . . . . . . . . . . . . 5-42
Deleting a string from a list . . . . . . . 5-21 Enabling casting . . . . . . . . . . . . . 5-42
Associating objects with a Implementing binary operations . . . . 5-44
string list . . . . . . . . . . . . . . . . 5-22 Implementing comparison
Working with strings . . . . . . . . . . . . . . 5-22 operations . . . . . . . . . . . . . . . . 5-46
Wide character routines . . . . . . . . . . . 5-22 Implementing unary operations . . . . 5-47
Commonly used long string routines . . . 5-23 Copying and clearing custom variants . . . 5-48
Commonly used routines for Loading and saving custom
null-terminated strings. . . . . . . . . . . 5-26 variant values . . . . . . . . . . . . . . 5-49
Declaring and initializing strings . . . . . . 5-27 Using the TCustomVariantType
Mixing and converting string types . . . . 5-28 descendant . . . . . . . . . . . . . . . . 5-50
String to PChar conversions. . . . . . . . . 5-28 Writing utilities to work with a
String dependencies . . . . . . . . . . . 5-29 custom variant type . . . . . . . . . . . . . 5-50
Returning a PChar local variable . . . 5-29 Supporting properties and methods
Passing a local variable as in custom variants . . . . . . . . . . . . . . 5-51
a PChar . . . . . . . . . . . . . . . . . 5-29 Using TInvokeableVariantType . . . . . 5-51
Compiler directives for strings . . . . . . . 5-30 Using TPublishableVariantType . . . . 5-53
Creating drawing spaces . . . . . . . . . . . . 5-31
Printing . . . . . . . . . . . . . . . . . . . . . . 5-32 Chapter 6
Converting measurements . . . . . . . . . . . 5-33 Working with components 6-1
Performing conversions . . . . . . . . . . . 5-33 Setting component properties . . . . . . . . . . 6-2
Performing simple conversions . . . . 5-33 Setting properties at design time . . . . . . 6-2
Performing complex conversions . . . 5-33 Using property editors . . . . . . . . . . 6-3
Adding new measurement types . . . . . . 5-34 Setting properties at runtime. . . . . . . . . 6-3
Calling methods. . . . . . . . . . . . . . . . . . 6-3
iv
Working with events and event handlers . . . . 6-3 Adding graphics to controls . . . . . . . . . . . 7-13
Generating a new event handler . . . . . . . 6-4 Indicating that a control is
Generating a handler for a owner-drawn. . . . . . . . . . . . . . . . . 7-13
component’s default event . . . . . . . . . 6-4 Adding graphical objects to
Locating event handlers . . . . . . . . . . . . 6-4 a string list . . . . . . . . . . . . . . . . . . 7-14
Associating an event with an existing Adding images to an application . . . . 7-14
event handler . . . . . . . . . . . . . . . . . 6-5 Adding images to a string list . . . . . . 7-14
Using the Sender parameter . . . . . . . 6-5 Drawing owner-drawn items . . . . . . 7-15
Displaying and coding shared Sizing owner-draw items . . . . . . . . . . . 7-16
events . . . . . . . . . . . . . . . . . . . 6-5 Drawing owner-draw items . . . . . . . . . 7-17
Associating menu events with
event handlers . . . . . . . . . . . . . . . . 6-6 Chapter 8
Deleting event handlers . . . . . . . . . . . . 6-6 Building applications, components,
Cross-platform and non-cross-platform
components . . . . . . . . . . . . . . . . . . . . 6-7
and libraries 8-1
Creating applications . . . . . . . . . . . . . . . 8-1
Adding custom components to the
GUI applications. . . . . . . . . . . . . . . . 8-2
Component palette . . . . . . . . . . . . . . 6-9
User interface models . . . . . . . . . . 8-2
SDI applications . . . . . . . . . . . . . 8-2
Chapter 7 MDI applications . . . . . . . . . . . . . 8-2
Working with controls 7-1 Setting IDE, project, and compiler
Implementing drag and drop in controls . . . . 7-1 options . . . . . . . . . . . . . . . . . . 8-3
Starting a drag operation . . . . . . . . . . . 7-1 Programming templates . . . . . . . . . . . 8-3
Accepting dragged items . . . . . . . . . . . 7-2 Console applications . . . . . . . . . . . . . 8-4
Dropping items . . . . . . . . . . . . . . . . . 7-3 Service applications . . . . . . . . . . . . . . 8-5
Ending a drag operation. . . . . . . . . . . . 7-3 Service threads . . . . . . . . . . . . . . 8-8
Customizing drag and drop with Service name properties . . . . . . . . . 8-9
a drag object. . . . . . . . . . . . . . . . . . 7-3 Debugging service applications . . . . . 8-10
Changing the drag mouse pointer . . . . . . 7-4 Creating packages and DLLs . . . . . . . . . . 8-11
Implementing drag and dock in controls . . . . 7-4 When to use packages and DLLs . . . . . . 8-11
Making a windowed control a Writing database applications . . . . . . . . . . 8-12
docking site . . . . . . . . . . . . . . . . . . 7-4 Distributing database applications . . . . . 8-13
Making a control a dockable child . . . . . . 7-5 Creating Web server applications . . . . . . . . 8-13
Controlling how child controls Creating Web Broker applications . . . . . . 8-14
are docked . . . . . . . . . . . . . . . . . . . 7-5 Creating WebSnap applications . . . . . . . 8-15
Controlling how child controls Creating Web Services applications . . . . . 8-15
are undocked . . . . . . . . . . . . . . . . . 7-6 Writing applications using COM . . . . . . . . 8-16
Controlling how child controls respond Using COM and DCOM . . . . . . . . . . . 8-16
to drag-and-dock operations . . . . . . . . 7-6 Using MTS and COM+ . . . . . . . . . . . . 8-16
Working with text in controls. . . . . . . . . . . 7-6 Using data modules . . . . . . . . . . . . . . . 8-17
Setting text alignment . . . . . . . . . . . . . 7-7 Creating and editing standard data
Adding scroll bars at runtime . . . . . . . . . 7-7 modules. . . . . . . . . . . . . . . . . . . . 8-17
Adding the clipboard object. . . . . . . . . . 7-8 Naming a data module and
Selecting text . . . . . . . . . . . . . . . . . . 7-9 its unit file . . . . . . . . . . . . . . . . 8-18
Selecting all text . . . . . . . . . . . . . . . . 7-9 Placing and naming components . . . . 8-19
Cutting, copying, and pasting text . . . . . 7-10 Using component properties and
Deleting selected text . . . . . . . . . . . . 7-10 events in a data module . . . . . . . . 8-19
Disabling menu items . . . . . . . . . . . . 7-11 Creating business rules in a
Providing a pop-up menu . . . . . . . . . . 7-11 data module . . . . . . . . . . . . . . . 8-20
Handling the OnPopup event. . . . . . . . 7-12
v
Accessing a data module from a form . . . 8-20 Chapter 9
Adding a remote data module to an
application server project . . . . . . . . . 8-21
Developing the application
Using the Object Repository . . . . . . . . . . 8-21 user interface 9-1
Sharing items within a project . . . . . . . 8-21 Controlling application behavior . . . . . . . . 9-1
Adding items to the Object Working at the application level . . . . . . . 9-2
Repository . . . . . . . . . . . . . . . . . . 8-22 Handling the screen . . . . . . . . . . . . . . 9-2
Sharing objects in a team Setting up forms. . . . . . . . . . . . . . . . . . 9-3
environment. . . . . . . . . . . . . . . . . 8-22 Using the main form . . . . . . . . . . . . . 9-3
Using an Object Repository item in Hiding the main form. . . . . . . . . . . . . 9-3
a project . . . . . . . . . . . . . . . . . . . 8-22 Adding forms . . . . . . . . . . . . . . . . . 9-4
Copying an item . . . . . . . . . . . . . 8-22 Linking forms . . . . . . . . . . . . . . . 9-4
Inheriting an item . . . . . . . . . . . . 8-23 Avoiding circular unit references . . . . 9-4
Using an item . . . . . . . . . . . . . . 8-23 Managing layout . . . . . . . . . . . . . . . 9-5
Using project templates . . . . . . . . . . . 8-23 Using forms . . . . . . . . . . . . . . . . . . . . 9-6
Modifying shared items . . . . . . . . . . . 8-23 Controlling when forms reside
Specifying a default project, new form, in memory . . . . . . . . . . . . . . . . . . 9-6
and main form . . . . . . . . . . . . . . . 8-24 Displaying an auto-created form . . . . 9-6
Enabling Help in applications . . . . . . . . . 8-24 Creating forms dynamically . . . . . . . 9-7
Help system interfaces . . . . . . . . . . . . 8-25 Creating modeless forms such
Implementing ICustomHelpViewer . . . . 8-25 as windows . . . . . . . . . . . . . . . 9-8
Communicating with the Help Creating a form instance using
Manager . . . . . . . . . . . . . . . . . . . 8-26 a local variable . . . . . . . . . . . . . 9-8
Asking the Help Manager for Passing additional arguments to forms . . . 9-8
information . . . . . . . . . . . . . . . . . 8-26 Retrieving data from forms. . . . . . . . . . 9-9
Displaying keyword-based Help . . . . . . 8-27 Retrieving data from modeless
Displaying tables of contents . . . . . . . . 8-28 forms . . . . . . . . . . . . . . . . . . . 9-9
Implementing IExtendedHelpViewer . . . 8-28 Retrieving data from modal forms . . . 9-11
Implementing IHelpSelector . . . . . . . . 8-29 Reusing components and groups of
Registering Help system objects . . . . . . 8-30 components . . . . . . . . . . . . . . . . . . . 9-13
Registering Help viewers . . . . . . . . 8-30 Creating and using component
Registering Help selectors . . . . . . . 8-30 templates . . . . . . . . . . . . . . . . . . . . . 9-13
Using Help in a VCL application. . . . . . . . 8-31 Working with frames . . . . . . . . . . . . . . . 9-14
How TApplication processes Creating frames . . . . . . . . . . . . . . . . 9-14
VCL Help . . . . . . . . . . . . . . . . . . 8-31 Adding frames to the Component
How VCL controls process Help . . . . . . 8-31 palette . . . . . . . . . . . . . . . . . . . . . 9-15
Using Help in a CLX application. . . . . . . . 8-32 Using and modifying frames. . . . . . . . . 9-15
How TApplication processes Sharing frames. . . . . . . . . . . . . . . . . 9-16
CLX Help . . . . . . . . . . . . . . . . . . 8-32 Developing dialog boxes . . . . . . . . . . . . . 9-17
How CLX controls process Help . . . . . . 8-32 Using open dialog boxes . . . . . . . . . . . 9-17
Calling a Help system directly . . . . . . . . . 8-33 Organizing actions for toolbars
Using IHelpSystem . . . . . . . . . . . . . . . 8-33 and menus . . . . . . . . . . . . . . . . . . . . 9-18
Customizing the IDE Help system . . . . . . . 8-34 What is an action? . . . . . . . . . . . . . . . 9-19
Setting up action bands . . . . . . . . . . . . 9-20
vi
Creating toolbars and menus . . . . . . . . 9-20 Manipulating menu items at runtime . . . . 9-44
Adding color, patterns, or pictures Merging menus . . . . . . . . . . . . . . . . 9-44
to menus, buttons, and toolbars . . . 9-22 Specifying the active menu: Menu
Adding icons to menus and property . . . . . . . . . . . . . . . . . 9-45
toolbars . . . . . . . . . . . . . . . . . 9-22 Determining the order of merged menu
Selecting menu and toolbar styles . . . 9-23 items: GroupIndex property . . . . . . 9-45
Creating dynamic menus . . . . . . . . 9-24 Importing resource files . . . . . . . . . . . 9-45
Creating toolbars and menus that Designing toolbars and cool bars . . . . . . . . 9-46
users can customize . . . . . . . . . . 9-24 Adding a toolbar using a panel
Hiding unused items and categories component . . . . . . . . . . . . . . . . . . 9-47
in action bands . . . . . . . . . . . . . 9-24 Adding a speed button to a panel . . . 9-47
Creating most recently used Assigning a speed button’s glyph . . . 9-48
(MRU) lists . . . . . . . . . . . . . . . 9-25 Setting the initial condition of a
Using action lists . . . . . . . . . . . . . . . . . 9-26 speed button . . . . . . . . . . . . . . . 9-48
Setting up action lists . . . . . . . . . . . . 9-26 Creating a group of speed buttons . . . 9-48
What happens when an action fires . . . . 9-27 Allowing toggle buttons . . . . . . . . . 9-49
Responding with events . . . . . . . . 9-27 Adding a toolbar using the toolbar
How actions find their targets . . . . . 9-29 component . . . . . . . . . . . . . . . . . . 9-49
Updating actions . . . . . . . . . . . . . . . 9-29 Adding a tool button . . . . . . . . . . . 9-49
Predefined action classes . . . . . . . . . . 9-30 Assigning images to tool buttons . . . . 9-50
Writing action components . . . . . . . . . 9-31 Setting tool button appearance and
Registering actions . . . . . . . . . . . . . . 9-31 initial conditions . . . . . . . . . . . . 9-50
Creating and managing menus. . . . . . . . . 9-32 Creating groups of tool buttons . . . . . 9-51
Opening the Menu Designer . . . . . . . . 9-33 Allowing toggled tool buttons . . . . . 9-51
Building menus . . . . . . . . . . . . . . . . 9-34 Adding a cool bar component . . . . . . . . 9-51
Naming menus . . . . . . . . . . . . . 9-34 Setting the appearance of the
Naming the menu items . . . . . . . . 9-34 cool bar . . . . . . . . . . . . . . . . . . 9-52
Adding, inserting, and deleting Responding to clicks . . . . . . . . . . . . . 9-52
menu items . . . . . . . . . . . . . . . 9-35 Assigning a menu to a tool button . . . 9-52
Adding separator bars . . . . . . . . . 9-36 Adding hidden toolbars . . . . . . . . . . . 9-53
Specifying accelerator keys and Hiding and showing toolbars . . . . . . . . 9-53
keyboard shortcuts . . . . . . . . . . 9-36 Demo programs . . . . . . . . . . . . . . . . 9-53
Creating submenus. . . . . . . . . . . . . . 9-37 Common controls and XP themes. . . . . . . . 9-54
Creating submenus by demoting
existing menus . . . . . . . . . . . . . 9-37 Chapter 10
Moving menu items . . . . . . . . . . . 9-38 Types of controls 10-1
Adding images to menu items . . . . . 9-38 Text controls . . . . . . . . . . . . . . . . . . . . 10-1
Viewing the menu . . . . . . . . . . . . 9-39 Edit controls . . . . . . . . . . . . . . . . . . 10-1
Editing menu items in the Object Memo and rich edit controls . . . . . . 10-2
Inspector . . . . . . . . . . . . . . . . . . . 9-39 Text viewing controls . . . . . . . . . . . . . 10-3
Using the Menu Designer context Labels . . . . . . . . . . . . . . . . . . . . . . 10-3
menu . . . . . . . . . . . . . . . . . . . . . 9-40 Specialized input controls . . . . . . . . . . . . 10-4
Commands on the context menu . . . 9-40 Scroll bars . . . . . . . . . . . . . . . . . . . 10-4
Switching between menus at Track bars. . . . . . . . . . . . . . . . . . . . 10-5
design time . . . . . . . . . . . . . . . 9-41 Up-down controls . . . . . . . . . . . . . . . 10-5
Using menu templates . . . . . . . . . . . . 9-41 Spin edit controls (CLX only) . . . . . . . . 10-5
Saving a menu as a template . . . . . . . . 9-43 Hot key controls (VCL only) . . . . . . . . . 10-6
Naming conventions for template Splitter controls . . . . . . . . . . . . . . . . 10-6
menu items and event handlers . . . 9-44
vii
Buttons and similar controls . . . . . . . . . . 10-6 Using ModelMaker views . . . . . . . . . . . . 11-4
Button controls . . . . . . . . . . . . . . . . 10-7 Collections pane . . . . . . . . . . . . . . . . 11-5
Bitmap buttons . . . . . . . . . . . . . . . . 10-7 Classes view . . . . . . . . . . . . . . . . 11-5
Speed buttons. . . . . . . . . . . . . . . . . 10-8 Units view . . . . . . . . . . . . . . . . . 11-5
Check boxes . . . . . . . . . . . . . . . . . . 10-8 Diagrams view . . . . . . . . . . . . . . 11-6
Radio buttons . . . . . . . . . . . . . . . . . 10-8 Members pane . . . . . . . . . . . . . . . . . 11-7
Toolbars . . . . . . . . . . . . . . . . . . . . 10-9 Editors pane . . . . . . . . . . . . . . . . . . 11-7
Cool bars (VCL only). . . . . . . . . . . . . 10-9 Implementation Editor . . . . . . . . . . 11-7
List controls. . . . . . . . . . . . . . . . . . . . 10-9 Unit Code Editor . . . . . . . . . . . . . 11-8
List boxes and check-list boxes . . . . . . . 10-10 Diagram Editor . . . . . . . . . . . . . . 11-9
Combo boxes . . . . . . . . . . . . . . . . . 10-11 Other Editors . . . . . . . . . . . . . . . 11-9
Tree views . . . . . . . . . . . . . . . . . . . 10-11 For more information. . . . . . . . . . . . . . 11-10
List views . . . . . . . . . . . . . . . . . . . 10-12
Icon views (CLX only) . . . . . . . . . . . . 10-12 Chapter 12
Date-time pickers and month Working with graphics and
calendars. . . . . . . . . . . . . . . . . . . 10-12
Grouping controls . . . . . . . . . . . . . . . . 10-12
multimedia 12-1
Overview of graphics programming . . . . . . 12-1
Group boxes and radio groups . . . . . . . 10-13
Refreshing the screen . . . . . . . . . . . . . 12-2
Panels . . . . . . . . . . . . . . . . . . . . . 10-13
Types of graphic objects . . . . . . . . . . . 12-3
Scroll boxes . . . . . . . . . . . . . . . . . . 10-13
Common properties and methods
Tab controls . . . . . . . . . . . . . . . . . . 10-14
of Canvas . . . . . . . . . . . . . . . . . . . 12-4
Page controls . . . . . . . . . . . . . . . . . 10-14
Using the properties of the Canvas
Header controls. . . . . . . . . . . . . . . . 10-14
object . . . . . . . . . . . . . . . . . . . . . 12-5
Display controls . . . . . . . . . . . . . . . . . 10-15
Using pens . . . . . . . . . . . . . . . . 12-5
Status bars. . . . . . . . . . . . . . . . . . . 10-15
Using brushes . . . . . . . . . . . . . . . 12-8
Progress bars . . . . . . . . . . . . . . . . . 10-15
Reading and setting pixels . . . . . . . 12-9
Help and hint properties . . . . . . . . . . 10-16
Using Canvas methods to draw
Grids. . . . . . . . . . . . . . . . . . . . . . . . 10-16
graphic objects . . . . . . . . . . . . . . . 12-10
Draw grids . . . . . . . . . . . . . . . . . . 10-16
Drawing lines and polylines . . . . . 12-10
String grids . . . . . . . . . . . . . . . . . . 10-16
Drawing shapes . . . . . . . . . . . . . 12-11
Value list editors (VCL only) . . . . . . . . . . 10-17
Handling multiple drawing objects
Graphic controls . . . . . . . . . . . . . . . . . 10-18
in your application . . . . . . . . . . . . 12-12
Images . . . . . . . . . . . . . . . . . . . . . 10-18
Keeping track of which drawing
Shapes . . . . . . . . . . . . . . . . . . . . . 10-18
tool to use . . . . . . . . . . . . . . . 12-12
Bevels . . . . . . . . . . . . . . . . . . . . . 10-18
Changing the tool with speed
Paint boxes . . . . . . . . . . . . . . . . . . 10-19
buttons . . . . . . . . . . . . . . . . . 12-13
Animation control . . . . . . . . . . . . . . 10-19
Using drawing tools . . . . . . . . . . 12-14
Drawing on a graphic . . . . . . . . . . . . 12-16
Chapter 11 Making scrollable graphics . . . . . . 12-17
Designing classes and Adding an image control . . . . . . . 12-17
components with ModelMaker 11-1 Loading and saving graphics files . . . . . 12-19
ModelMaker fundamentals . . . . . . . . . . . 11-2 Loading a picture from a file . . . . . 12-19
ModelMaker models . . . . . . . . . . . . . 11-2 Saving a picture to a file . . . . . . . . 12-20
Using ModelMaker with the IDE . . . . . . 11-2 Replacing the picture . . . . . . . . . . 12-20
Creating models . . . . . . . . . . . . . . . 11-3
viii
Using the clipboard with graphics . . . . . 12-21 Executing thread objects . . . . . . . . . . . . 13-12
Copying graphics to the Overriding the default priority . . . . . . 13-12
clipboard . . . . . . . . . . . . . . . . 12-22 Starting and stopping threads . . . . . . . 13-12
Cutting graphics to the clipboard . . . 12-22 Debugging multi-threaded applications . . . 13-13
Pasting graphics from the Naming a thread. . . . . . . . . . . . . . . 13-13
clipboard . . . . . . . . . . . . . . . . 12-23 Converting an unnamed thread
Rubber banding example . . . . . . . . . . 12-24 to a named thread . . . . . . . . . . 13-13
Responding to the mouse . . . . . . . . 12-24 Assigning separate names to
Responding to a mouse-down similar threads . . . . . . . . . . . . 13-15
action . . . . . . . . . . . . . . . . . . 12-25
Adding a field to a form object to Chapter 14
track mouse actions . . . . . . . . . . 12-27 Exception handling 14-1
Refining line drawing . . . . . . . . . . 12-28 Defining protected blocks . . . . . . . . . . . . 14-2
Working with multimedia . . . . . . . . . . . 12-30 Writing the try block . . . . . . . . . . . . . 14-2
Adding silent video clips to an Raising an exception . . . . . . . . . . . 14-3
application. . . . . . . . . . . . . . . . . . 12-30 Writing exception handlers. . . . . . . . . . 14-4
Example of adding silent Exception-handling statements . . . . . 14-4
video clips . . . . . . . . . . . . . . . 12-31 Handling classes of exceptions . . . . . 14-6
Adding audio and/or video clips to Scope of exception handlers . . . . . . . 14-6
an application . . . . . . . . . . . . . . . . 12-32 Reraising exceptions . . . . . . . . . . . 14-7
Example of adding audio and/or Writing finally blocks . . . . . . . . . . . . . 14-8
video clips (VCL only) . . . . . . . . 12-33 Writing a finally block . . . . . . . . . . 14-9
Handling exceptions in VCL
Chapter 13 applications . . . . . . . . . . . . . . . . . . . 14-9
Writing multi-threaded applications 13-1 VCL exception classes . . . . . . . . . . . 14-10
Defining thread objects . . . . . . . . . . . . . 13-2 Default exception handling in VCL . . . . 14-11
Initializing the thread . . . . . . . . . . . . 13-3 Silent exceptions. . . . . . . . . . . . . . . 14-12
Assigning a default priority . . . . . . 13-3 Defining your own VCL exceptions. . . . 14-13
Indicating when threads are freed . . . 13-4
Writing the thread function . . . . . . . . . 13-4 Chapter 15
Using the main VCL/CLX thread . . . 13-4 Developing cross-platform
Using thread-local variables . . . . . . 13-6
Checking for termination by other
applications 15-1
Creating CLX applications . . . . . . . . . . . . 15-2
threads . . . . . . . . . . . . . . . . . 13-6
Porting VCL applications . . . . . . . . . . . . 15-2
Handling exceptions in the thread
Porting techniques . . . . . . . . . . . . . . 15-2
function . . . . . . . . . . . . . . . . . 13-6
Platform-specific ports . . . . . . . . . . 15-3
Writing clean-up code . . . . . . . . . . . . 13-7
Cross-platform ports . . . . . . . . . . . 15-3
Coordinating threads . . . . . . . . . . . . . . 13-7
Windows emulation ports . . . . . . . . 15-3
Avoiding simultaneous access . . . . . . . 13-7
Modifying VCL applications . . . . . . . . . 15-4
Locking objects . . . . . . . . . . . . . 13-8
WinCLX versus VisualCLX. . . . . . . . . . 15-5
Using critical sections . . . . . . . . . . 13-8
What VisualCLX does differently . . . . 15-6
Using the multi-read exclusive-write
Features that do not port directly
synchronizer . . . . . . . . . . . . . . 13-8
or are missing . . . . . . . . . . . . . . . . 15-7
Other techniques for sharing
Comparing WinCLX and
memory . . . . . . . . . . . . . . . . . 13-9
VisualCLX units . . . . . . . . . . . . . . . 15-8
Waiting for other threads . . . . . . . . . . 13-9
Differences in CLX object constructors . . 15-11
Waiting for a thread to finish
Handling system and widget events . . . 15-12
executing . . . . . . . . . . . . . . . . 13-10
Waiting for a task to be
completed . . . . . . . . . . . . . . . 13-10
ix
Writing portable code . . . . . . . . . . . . 15-12 Editing package source files
Using conditional directives . . . . . . 15-13 manually . . . . . . . . . . . . . . . . . . 16-10
Terminating conditional Compiling packages . . . . . . . . . . . . 16-10
directives . . . . . . . . . . . . . . . . 15-14 Package-specific compiler
Including inline assembler code . . . . 15-15 directives . . . . . . . . . . . . . . . . 16-11
Programming differences on Linux . . . . 15-16 Compiling and linking from the
Transferring applications between command line . . . . . . . . . . . . . 16-13
Windows and Linux . . . . . . . . . . . . . . 15-17 Package files created when
Sharing source files between compiling . . . . . . . . . . . . . . . 16-13
Windows and Linux . . . . . . . . . . . . 15-17 Deploying packages . . . . . . . . . . . . . . 16-14
Environmental differences between Deploying applications that use
Windows and Linux . . . . . . . . . . . . 15-18 packages . . . . . . . . . . . . . . . . . . 16-14
Registry . . . . . . . . . . . . . . . . . . 15-20 Distributing packages to other
Look and feel . . . . . . . . . . . . . . . 15-20 developers . . . . . . . . . . . . . . . . . 16-14
Directory structure on Linux . . . . . . . . 15-20 Package collection files . . . . . . . . . . . 16-14
Cross-platform database applications . . . . . 15-21
dbExpress differences . . . . . . . . . . . . 15-22 Chapter 17
Component-level differences . . . . . . . . 15-22 Creating international applications 17-1
User interface-level differences . . . . . . . 15-23 Internationalization and localization . . . . . . 17-1
Porting database applications Internationalization . . . . . . . . . . . . . . 17-1
to Linux . . . . . . . . . . . . . . . . . . . 15-24 Localization . . . . . . . . . . . . . . . . . . 17-2
Updating data in dbExpress Internationalizing applications . . . . . . . . . 17-2
applications . . . . . . . . . . . . . . . . . 15-26 Enabling application code . . . . . . . . . . 17-2
Cross-platform Internet applications . . . . . 15-28 ~Character sets . . . . . . . . . . . . . . 17-2
Porting Internet applications OEM and ANSI character sets . . . . . 17-3
to Linux . . . . . . . . . . . . . . . . . . . 15-28 Multibyte character sets . . . . . . . . . 17-3
Wide characters . . . . . . . . . . . . . . 17-4
Chapter 16 Including bi-directional functionality
Working with packages and in applications . . . . . . . . . . . . . . 17-4
components 16-1 BiDiMode property . . . . . . . . . . . . 17-4
Why use packages? . . . . . . . . . . . . . . . 16-2 Locale-specific features . . . . . . . . . 17-7
Packages and standard DLLs . . . . . . . . 16-2 Designing the user interface . . . . . . . . . 17-7
Runtime packages . . . . . . . . . . . . . . . . 16-3 Text . . . . . . . . . . . . . . . . . . . . . 17-7
Loading packages in an application . . . . 16-3 Graphic images . . . . . . . . . . . . . . 17-8
Loading packages with the Formats and sort order . . . . . . . . . . 17-8
LoadPackage function . . . . . . . . . . 16-4 Keyboard mappings . . . . . . . . . . . 17-8
Deciding which runtime packages Isolating resources. . . . . . . . . . . . . . . 17-8
to use . . . . . . . . . . . . . . . . . . . . . 16-4 Creating resource DLLs. . . . . . . . . . . . 17-9
Custom packages . . . . . . . . . . . . . . . 16-5 Using resource DLLs . . . . . . . . . . . . 17-10
Design-time packages . . . . . . . . . . . . . . 16-5 Dynamic switching of resource DLLs . . . 17-11
Installing component packages . . . . . . . 16-6 Localizing applications . . . . . . . . . . . . . 17-12
Creating and editing packages . . . . . . . . . 16-7 Localizing resources. . . . . . . . . . . . . 17-12
Creating a package . . . . . . . . . . . . . . 16-7
Editing an existing package . . . . . . . . . 16-8
Understanding the structure of
a package . . . . . . . . . . . . . . . . . . 16-8
Naming packages . . . . . . . . . . . . 16-8
Requires clause . . . . . . . . . . . . . 16-9
Contains clause . . . . . . . . . . . . . 16-9
x
Chapter 18 Transactions . . . . . . . . . . . . . . . . . . 19-4
Referential integrity, stored procedures,
Deploying applications 18-1 and triggers. . . . . . . . . . . . . . . . . . 19-5
Deploying general applications . . . . . . . . 18-1
Database architecture. . . . . . . . . . . . . . . 19-6
Using installation programs. . . . . . . . . 18-2
General structure . . . . . . . . . . . . . . . 19-6
Identifying application files . . . . . . 18-2
The user interface form . . . . . . . . . 19-6
Application files . . . . . . . . . . . . . 18-3
The data module . . . . . . . . . . . . . 19-6
Package files . . . . . . . . . . . . . . . 18-3
Connecting directly to a database
Merge modules . . . . . . . . . . . . . 18-3
server . . . . . . . . . . . . . . . . . . . . . 19-8
ActiveX controls . . . . . . . . . . . . . 18-5
Using a dedicated file on disk . . . . . . . . 19-9
Helper applications . . . . . . . . . . . 18-5
Connecting to another dataset . . . . . . . 19-10
DLL locations . . . . . . . . . . . . . . 18-6
Connecting a client dataset to another
Deploying CLX applications . . . . . . . . . . 18-6
dataset in the same application . . . 19-12
Deploying database applications. . . . . . . . 18-6
Using a multi-tiered architecture . . . 19-13
Deploying dbExpress database
Combining approaches . . . . . . . . . . . 19-14
applications . . . . . . . . . . . . . . . . . 18-7
Designing the user interface . . . . . . . . . . 19-15
Deploying BDE applications . . . . . . . . 18-8
Analyzing data . . . . . . . . . . . . . . . 19-15
Borland Database Engine . . . . . . . . 18-8
Writing reports. . . . . . . . . . . . . . . . 19-16
Deploying multi-tiered database
applications (DataSnap) . . . . . . . . . . 18-9
Deploying Web applications . . . . . . . . . . 18-9
Chapter 20
Deploying on Apache servers . . . . . . . 18-10 Using data controls 20-1
Enabling modules . . . . . . . . . . . . 18-10 Using common data control features . . . . . . 20-2
CGI applications . . . . . . . . . . . . . 18-11 Associating a data control with
Programming for varying host a dataset . . . . . . . . . . . . . . . . . . . 20-3
environments . . . . . . . . . . . . . . . . . . 18-12 Changing the associated dataset
Screen resolutions and color depths . . . . 18-12 at runtime . . . . . . . . . . . . . . . . 20-4
Considerations when not Enabling and disabling the data
dynamically resizing . . . . . . . . . 18-12 source . . . . . . . . . . . . . . . . . . 20-4
Considerations when dynamically Responding to changes mediated
resizing forms and controls . . . . . . 18-13 by the data source . . . . . . . . . . . 20-4
Accommodating varying Editing and updating data . . . . . . . . . . 20-5
color depths . . . . . . . . . . . . . . 18-14 Enabling editing in controls on
Fonts . . . . . . . . . . . . . . . . . . . . . . 18-14 user entry . . . . . . . . . . . . . . . . 20-5
Operating systems versions . . . . . . . . . 18-15 Editing data in a control . . . . . . . . . 20-5
Software license requirements . . . . . . . . . 18-15 Disabling and enabling data display . . . . 20-6
DEPLOY . . . . . . . . . . . . . . . . . . . . 18-15 Refreshing data display. . . . . . . . . . . . 20-7
README . . . . . . . . . . . . . . . . . . . 18-16 Enabling mouse, keyboard, and
No-nonsense license agreement . . . . . . 18-16 timer events . . . . . . . . . . . . . . . . . 20-7
Third-party product documentation . . . . 18-16 Choosing how to organize the data . . . . . . . 20-7
Displaying a single record . . . . . . . . . . 20-7
Part II Displaying data as labels . . . . . . . . 20-8
Displaying and editing fields in
Developing database applications an edit box . . . . . . . . . . . . . . . . 20-8
Displaying and editing text in a
Chapter 19 memo control . . . . . . . . . . . . . . 20-9
Designing database applications 19-1 Displaying and editing text in a rich
Using databases . . . . . . . . . . . . . . . . . 19-1 edit memo control . . . . . . . . . . . 20-9
Types of databases . . . . . . . . . . . . . . 19-2 Displaying and editing graphics
Database security. . . . . . . . . . . . . . . 19-4 fields in an image control . . . . . . 20-10
xi
Displaying and editing data in list Component overview . . . . . . . . . . . . . . 21-4
and combo boxes . . . . . . . . . . . 20-10 VCL/CLX components . . . . . . . . . . . . 21-4
Handling Boolean field values Engine components . . . . . . . . . . . 21-4
with check boxes . . . . . . . . . . . . 20-13 Render components . . . . . . . . . . . 21-4
Restricting field values with Data connection components . . . . . . 21-4
radio controls . . . . . . . . . . . . . . 20-14 Rave project component . . . . . . . . . 21-5
Displaying multiple records. . . . . . . . . 20-14 Reporting components . . . . . . . . . . . . 21-5
Viewing and editing data with TDBGrid . . . 20-15 Project components . . . . . . . . . . . . 21-5
Using a grid control in its default Data objects . . . . . . . . . . . . . . . . 21-5
state . . . . . . . . . . . . . . . . . . . . . 20-16 Standard components . . . . . . . . . . 21-5
Creating a customized grid . . . . . . . . . 20-17 Drawing components . . . . . . . . . . 21-5
Understanding persistent Report components . . . . . . . . . . . . 21-6
columns . . . . . . . . . . . . . . . . . 20-17 Bar code components . . . . . . . . . . 21-6
Creating persistent columns . . . . . . 20-18 Getting more information . . . . . . . . . . . . 21-6
Deleting persistent columns . . . . . . 20-19
Arranging the order of persistent Chapter 22
columns . . . . . . . . . . . . . . . . . 20-19 Using decision support
Setting column properties at
design time . . . . . . . . . . . . . . . 20-20
components 22-1
Overview . . . . . . . . . . . . . . . . . . . . . 22-1
Defining a lookup list column . . . . . 20-21
About crosstabs . . . . . . . . . . . . . . . . . . 22-2
Putting a button in a column . . . . . . 20-22
One-dimensional crosstabs. . . . . . . . . . 22-3
Restoring default values to
Multidimensional crosstabs . . . . . . . . . 22-3
a column . . . . . . . . . . . . . . . . 20-22
Guidelines for using decision support
Displaying ADT and array fields . . . . . . 20-22
components . . . . . . . . . . . . . . . . . . . 22-4
Setting grid options . . . . . . . . . . . . . 20-24
Using datasets with decision support
Editing in the grid . . . . . . . . . . . . . . 20-26
components . . . . . . . . . . . . . . . . . . . 22-5
Controlling grid drawing . . . . . . . . . . 20-26
Creating decision datasets with
Responding to user actions
TQuery or TTable . . . . . . . . . . . . . . 22-6
at runtime . . . . . . . . . . . . . . . . . . 20-27
Creating decision datasets with the
Creating a grid that contains other
Decision Query editor. . . . . . . . . . . . 22-6
data-aware controls . . . . . . . . . . . . . . 20-28
Using decision cubes . . . . . . . . . . . . . . . 22-7
Navigating and manipulating records. . . . . 20-29
Decision cube properties and events . . . . 22-7
Choosing navigator buttons to
Using the Decision Cube editor . . . . . . . 22-8
display . . . . . . . . . . . . . . . . . . . . 20-30
Viewing and changing dimension
Hiding and showing navigator
settings . . . . . . . . . . . . . . . . . . 22-8
buttons at design time . . . . . . . . . 20-30
Setting the maximum available
Hiding and showing navigator
dimensions and summaries . . . . . . 22-9
buttons at runtime . . . . . . . . . . . 20-31
Viewing and changing design
Displaying fly-over help. . . . . . . . . . . 20-31
options . . . . . . . . . . . . . . . . . . 22-9
Using a single navigator for multiple
Using decision sources . . . . . . . . . . . . . . 22-9
datasets . . . . . . . . . . . . . . . . . . . 20-32
Properties and events . . . . . . . . . . . . . 22-9
Using decision pivots. . . . . . . . . . . . . . 22-10
Chapter 21 Decision pivot properties. . . . . . . . . . 22-10
Creating reports with Rave Reports 21-1 Creating and using decision grids . . . . . . 22-11
Overview . . . . . . . . . . . . . . . . . . . . . 21-1 Creating decision grids . . . . . . . . . . . 22-11
Getting started . . . . . . . . . . . . . . . . . . 21-2 Using decision grids . . . . . . . . . . . . 22-11
The Rave Visual Designer. . . . . . . . . . . . 21-3 Opening and closing decision
grid fields . . . . . . . . . . . . . . . 22-11
xii
Reorganizing rows and columns in Obtaining metadata. . . . . . . . . . . . . . . 23-13
decision grids . . . . . . . . . . . . . 22-12 Listing available tables . . . . . . . . . . . 23-14
Drilling down for detail in Listing the fields in a table . . . . . . . . . 23-14
decision grids . . . . . . . . . . . . . 22-12 Listing available stored procedures . . . . 23-14
Limiting dimension selection in Listing available indexes . . . . . . . . . . 23-14
decision grids . . . . . . . . . . . . . 22-12 Listing stored procedure parameters . . . 23-15
Decision grid properties . . . . . . . . . . . 22-12
Creating and using decision graphs . . . . . . 22-13 Chapter 24
Creating decision graphs . . . . . . . . . . 22-13 Understanding datasets 24-1
Using decision graphs . . . . . . . . . . . . 22-14 Using TDataSet descendants . . . . . . . . . . 24-2
The decision graph display . . . . . . . . . 22-15 Determining dataset states. . . . . . . . . . . . 24-3
Customizing decision graphs . . . . . . . . 22-16 Opening and closing datasets . . . . . . . . . . 24-4
Setting decision graph template Navigating datasets. . . . . . . . . . . . . . . . 24-5
defaults . . . . . . . . . . . . . . . . . 22-17 Using the First and Last methods . . . . . . 24-6
Customizing decision graph Using the Next and Prior methods . . . . . 24-7
series . . . . . . . . . . . . . . . . . . 22-18 Using the MoveBy method . . . . . . . . . . 24-7
Decision support components at Using the Eof and Bof properties . . . . . . 24-8
runtime . . . . . . . . . . . . . . . . . . . . . 22-19 Eof . . . . . . . . . . . . . . . . . . . . . 24-8
Decision pivots at runtime . . . . . . . . . 22-19 Bof . . . . . . . . . . . . . . . . . . . . . 24-9
Decision grids at runtime . . . . . . . . . . 22-19 Marking and returning to records . . . . . . 24-9
Decision graphs at runtime . . . . . . . . . 22-20 The Bookmark property . . . . . . . . . 24-9
Decision support components and The GetBookmark method . . . . . . 24-10
memory control . . . . . . . . . . . . . . . . 22-20 The GotoBookmark and
Setting maximum dimensions, BookmarkValid methods . . . . . . . 24-10
summaries, and cells . . . . . . . . . . . . 22-20 The CompareBookmarks method . . 24-10
Setting dimension state . . . . . . . . . . . 22-21 The FreeBookmark method . . . . . . 24-10
Using paged dimensions . . . . . . . . . . 22-21 A bookmarking example . . . . . . . 24-10
Searching datasets . . . . . . . . . . . . . . . 24-11
Chapter 23 Using Locate . . . . . . . . . . . . . . . . . 24-11
Connecting to databases 23-1 Using Lookup . . . . . . . . . . . . . . . . 24-12
Using implicit connections . . . . . . . . . . . 23-2 Displaying and editing a subset of data
Controlling connections . . . . . . . . . . . . . 23-3 using filters . . . . . . . . . . . . . . . . . . 24-13
Connecting to a database server . . . . . . 23-3 Enabling and disabling filtering . . . . . . 24-13
Disconnecting from a database server . . . 23-4 Creating filters . . . . . . . . . . . . . . . . 24-13
Controlling server login . . . . . . . . . . . . . 23-4 Setting the Filter property . . . . . . . 24-14
Managing transactions . . . . . . . . . . . . . 23-6 Writing an OnFilterRecord
Starting a transaction . . . . . . . . . . . . 23-7 event handler . . . . . . . . . . . . . 24-15
Ending a transaction . . . . . . . . . . . . . 23-8 Switching filter event handlers
Ending a successful transaction . . . . 23-8 at runtime . . . . . . . . . . . . . . . 24-16
Ending an unsuccessful Setting filter options. . . . . . . . . . . . . 24-16
transaction . . . . . . . . . . . . . . . 23-9 Navigating records in a filtered
Specifying the transaction dataset . . . . . . . . . . . . . . . . . . . 24-16
isolation level . . . . . . . . . . . . . . . . 23-9 Modifying data . . . . . . . . . . . . . . . . . 24-17
Sending commands to the server . . . . . . . 23-10 Editing records. . . . . . . . . . . . . . . . 24-18
Working with associated datasets . . . . . . . 23-12 Adding new records . . . . . . . . . . . . 24-19
Closing all datasets without Inserting records . . . . . . . . . . . . 24-19
disconnecting from the server. . . . . . . 23-12 Appending records . . . . . . . . . . . 24-20
Iterating through the associated Deleting records . . . . . . . . . . . . . . . 24-20
datasets . . . . . . . . . . . . . . . . . . . 23-13 Posting data . . . . . . . . . . . . . . . . . 24-21
xiii
Canceling changes . . . . . . . . . . . . . . 24-21 Using parameters in queries . . . . . . . . 24-45
Modifying entire records . . . . . . . . . . 24-22 Supplying parameters at design
Calculating fields . . . . . . . . . . . . . . . . 24-23 time . . . . . . . . . . . . . . . . . . . 24-45
Types of datasets . . . . . . . . . . . . . . . . . 24-24 Supplying parameters at runtime . . 24-47
Using table type datasets . . . . . . . . . . . . 24-25 Establishing master/detail relationships
Advantages of using table type using parameters . . . . . . . . . . . . . 24-47
datasets . . . . . . . . . . . . . . . . . . . 24-26 Preparing queries . . . . . . . . . . . . . . 24-48
Sorting records with indexes . . . . . . . . 24-26 Executing queries that don’t return
Obtaining information about a result set . . . . . . . . . . . . . . . . . 24-49
indexes . . . . . . . . . . . . . . . . . 24-27 Using unidirectional result sets . . . . . . 24-49
Specifying an index with Using stored procedure-type datasets . . . . 24-50
IndexName . . . . . . . . . . . . . . . 24-27 Working with stored procedure
Creating an index with parameters . . . . . . . . . . . . . . . . . 24-51
IndexFieldNames . . . . . . . . . . . 24-28 Setting up parameters at
Using Indexes to search for records . . . . 24-28 design time . . . . . . . . . . . . . . 24-52
Executing a search with Goto Using parameters at runtime . . . . . 24-54
methods . . . . . . . . . . . . . . . . . 24-29 Preparing stored procedures . . . . . . . . 24-55
Executing a search with Find Executing stored procedures that don’t
methods . . . . . . . . . . . . . . . . . 24-30 return a result set . . . . . . . . . . . . . 24-55
Specifying the current record after Fetching multiple result sets . . . . . . . . 24-56
a successful search . . . . . . . . . . . 24-30
Searching on partial keys . . . . . . . . 24-30 Chapter 25
Repeating or extending a search . . . . 24-30 Working with field components 25-1
Limiting records with ranges . . . . . . . . 24-31 Dynamic field components . . . . . . . . . . . 25-2
Understanding the differences Persistent field components . . . . . . . . . . . 25-3
between ranges and filters . . . . . . 24-31 Creating persistent fields . . . . . . . . . . . 25-4
Specifying ranges . . . . . . . . . . . . 24-31 Arranging persistent fields . . . . . . . . . . 25-5
Modifying a range . . . . . . . . . . . . 24-34 Defining new persistent fields . . . . . . . . 25-5
Applying or canceling a range . . . . . 24-34 Defining a data field . . . . . . . . . . . 25-6
Creating master/detail relationships. . . . 24-35 Defining a calculated field . . . . . . . . 25-7
Making the table a detail of Programming a calculated field . . . . . 25-8
another dataset . . . . . . . . . . . . . 24-35 Defining a lookup field . . . . . . . . . 25-9
Using nested detail tables . . . . . . . 24-37 Defining an aggregate field . . . . . . 25-10
Controlling Read/write access Deleting persistent field components . . . 25-11
to tables . . . . . . . . . . . . . . . . . . . 24-38 Setting persistent field properties
Creating and deleting tables . . . . . . . . 24-38 and events . . . . . . . . . . . . . . . . . 25-11
Creating tables . . . . . . . . . . . . . . 24-38 Setting display and edit properties
Deleting tables . . . . . . . . . . . . . . 24-41 at design time . . . . . . . . . . . . . 25-11
Emptying tables . . . . . . . . . . . . . . . 24-41 Setting field component properties
Synchronizing tables . . . . . . . . . . . . . 24-42 at runtime . . . . . . . . . . . . . . . 25-13
Using query-type datasets . . . . . . . . . . . 24-42 Creating attribute sets for field
Specifying the query . . . . . . . . . . . . . 24-43 components . . . . . . . . . . . . . . 25-13
Specifying a query using the Associating attribute sets with field
SQL property . . . . . . . . . . . . . . 24-44 components . . . . . . . . . . . . . . 25-14
Specifying a query using the Removing attribute associations . . . 25-14
CommandText property . . . . . . . 24-44 Controlling and masking
user input . . . . . . . . . . . . . . . 25-15
xiv
Using default formatting for numeric, Chapter 26
date, and time fields . . . . . . . . . . 25-15
Handling events . . . . . . . . . . . . . 25-16
Using the Borland Database Engine 26-1
BDE-based architecture. . . . . . . . . . . . . . 26-1
Working with field component methods
Using BDE-enabled datasets . . . . . . . . . 26-2
at runtime . . . . . . . . . . . . . . . . . . . . 25-17
Associating a dataset with database
Displaying, converting, and accessing
and session connections . . . . . . . . 26-3
field values . . . . . . . . . . . . . . . . . . . 25-18
Caching BLOBs . . . . . . . . . . . . . . 26-4
Displaying field component values in
Obtaining a BDE handle . . . . . . . . . 26-4
standard controls . . . . . . . . . . . . . . 25-18
Using TTable . . . . . . . . . . . . . . . . . . 26-5
Converting field values . . . . . . . . . . . 25-19
Specifying the table type for local
Accessing field values with the default
tables . . . . . . . . . . . . . . . . . . . 26-5
dataset property . . . . . . . . . . . . . . 25-20
Controlling read/write access to
Accessing field values with a dataset’s
local tables . . . . . . . . . . . . . . . . 26-6
Fields property . . . . . . . . . . . . . . . 25-21
Specifying a dBASE index file . . . . . . 26-6
Accessing field values with a dataset’s
Renaming local tables . . . . . . . . . . 26-8
FieldByName method . . . . . . . . . . . 25-21
Importing data from another table . . . 26-8
Setting a default value for a field. . . . . . . . 25-22
Using TQuery . . . . . . . . . . . . . . . . . 26-9
Working with constraints . . . . . . . . . . . . 25-22
Creating heterogeneous queries . . . . 26-9
Creating a custom constraint . . . . . . . . 25-22
Obtaining an editable result set . . . . 26-10
Using server constraints . . . . . . . . . . . 25-23
Updating read-only result sets . . . . 26-11
Using object fields . . . . . . . . . . . . . . . . 25-23
Using TStoredProc . . . . . . . . . . . . . 26-11
Displaying ADT and array fields . . . . . . 25-24
Binding parameters . . . . . . . . . . 26-12
Working with ADT fields . . . . . . . . . . 25-25
Working with Oracle overloaded
Using persistent field
stored procedures . . . . . . . . . . . 26-12
components . . . . . . . . . . . . . . 25-25
Connecting to databases with
Using the dataset’s FieldByName
TDatabase . . . . . . . . . . . . . . . . . 26-12
method . . . . . . . . . . . . . . . . . 25-25
Associating a database component
Using the dateset’s FieldValues
with a session . . . . . . . . . . . . . 26-13
property . . . . . . . . . . . . . . . . . 25-25
Understanding database and session
Using the ADT field’s FieldValues
component interactions . . . . . . . 26-13
property . . . . . . . . . . . . . . . . . 25-26
Identifying the database . . . . . . . . 26-14
Using the ADT field’s Fields
Opening a connection using
property . . . . . . . . . . . . . . . . . 25-26
TDatabase . . . . . . . . . . . . . . . 26-15
Working with array fields . . . . . . . . . . 25-26
Using database components in data
Using persistent fields . . . . . . . . . 25-26
modules . . . . . . . . . . . . . . . . 26-16
Using the array field’s FieldValues
Managing database sessions . . . . . . . . 26-16
property . . . . . . . . . . . . . . . . . 25-27
Activating a session . . . . . . . . . . 26-18
Using the array field’s Fields
Specifying default database
property . . . . . . . . . . . . . . . . . 25-27
connection behavior . . . . . . . . . 26-18
Working with dataset fields . . . . . . . . . 25-27
Managing database connections . . . 26-19
Displaying dataset fields . . . . . . . . 25-27
Working with password-protected
Accessing data in a nested dataset . . . 25-28
Paradox and dBASE tables . . . . . 26-21
Working with reference fields. . . . . . . . 25-28
Specifying Paradox directory
Displaying reference fields . . . . . . . 25-28
locations . . . . . . . . . . . . . . . . 26-24
Accessing data in a reference field . . . 25-29
Working with BDE aliases . . . . . . . 26-25
Retrieving information about
a session . . . . . . . . . . . . . . . . 26-27
xv
Creating additional sessions . . . . . . 26-28 Indicating the types of operations
Naming a session . . . . . . . . . . . . 26-29 the connection supports . . . . . . . . 27-6
Managing multiple sessions . . . . . . 26-29 Specifying whether the connection
Using transactions with the BDE . . . . . . . . 26-31 automatically initiates
Using passthrough SQL . . . . . . . . . . . 26-32 transactions . . . . . . . . . . . . . . . 27-7
Using local transactions . . . . . . . . . . . 26-32 Accessing the connection’s
Using the BDE to cache updates . . . . . . . . 26-33 commands . . . . . . . . . . . . . . . . . . 27-7
Enabling BDE-based cached updates . . . 26-34 ADO connection events. . . . . . . . . . . . 27-8
Applying BDE-based cached updates . . . 26-35 Events when establishing a
Applying cached updates using connection . . . . . . . . . . . . . . . . 27-8
a database . . . . . . . . . . . . . . . . 26-36 Events when disconnecting . . . . . . . 27-8
Applying cached updates with dataset Events when managing
component methods . . . . . . . . . . 26-36 transactions . . . . . . . . . . . . . . . 27-9
Creating an OnUpdateRecord Other events . . . . . . . . . . . . . . . . 27-9
event handler . . . . . . . . . . . . . . 26-37 Using ADO datasets . . . . . . . . . . . . . . . 27-9
Handling cached update errors . . . . 26-38 Connecting an ADO dataset to
Using update objects to update a data store . . . . . . . . . . . . . . 27-10
a dataset . . . . . . . . . . . . . . . . . . . 26-40 Working with record sets . . . . . . . 27-11
Creating SQL statements for update Filtering records based on
components . . . . . . . . . . . . . . 26-41 bookmarks . . . . . . . . . . . . . . . 27-11
Using multiple update objects . . . . . 26-45 Fetching records asynchronously . . . 27-12
Executing the SQL statements . . . . . 26-46 Using batch updates . . . . . . . . . . 27-13
Using TBatchMove. . . . . . . . . . . . . . . . 26-49 Loading data from and saving
Creating a batch move component . . . . . 26-49 data to files . . . . . . . . . . . . . . 27-15
Specifying a batch move mode . . . . . . . 26-50 Using TADODataSet . . . . . . . . . . . . 27-16
Appending records . . . . . . . . . . . 26-50 Using Command objects . . . . . . . . . . . . 27-18
Updating records . . . . . . . . . . . . 26-50 Specifying the command . . . . . . . . . . 27-18
Appending and updating Using the Execute method . . . . . . . . . 27-19
records . . . . . . . . . . . . . . . . . 26-51 Canceling commands . . . . . . . . . . . . 27-19
Copying datasets . . . . . . . . . . . . 26-51 Retrieving result sets with commands . . 27-20
Deleting records . . . . . . . . . . . . . 26-51 Handling command parameters . . . . . . 27-20
Mapping data types . . . . . . . . . . . . . 26-51
Executing a batch move . . . . . . . . . . . 26-52 Chapter 28
Handling batch move errors . . . . . . . . 26-52 Using unidirectional datasets 28-1
The Data Dictionary . . . . . . . . . . . . . . . 26-53 Types of unidirectional datasets . . . . . . . . . 28-2
Tools for working with the BDE . . . . . . . . 26-55 Connecting to the database server . . . . . . . 28-2
Setting up TSQLConnection . . . . . . . . . 28-3
Chapter 27 Identifying the driver . . . . . . . . . . 28-3
Working with ADO components 27-1 Specifying connection parameters . . . 28-4
Overview of ADO components . . . . . . . . 27-2 Naming a connection description . . . . 28-4
Connecting to ADO data stores . . . . . . . . 27-3 Using the Connection Editor . . . . . . 28-5
Connecting to a data store using Specifying what data to display . . . . . . . . . 28-6
TADOConnection. . . . . . . . . . . . . . 27-3 Representing the results of a query . . . . . 28-6
Accessing the connection object . . . . 27-5 Representing the records in a table . . . . . 28-7
Fine-tuning a connection . . . . . . . . . . 27-5 Representing a table using
Forcing asynchronous TSQLDataSet . . . . . . . . . . . . . . 28-7
connections . . . . . . . . . . . . . . . 27-5 Representing a table using
Controlling time-outs . . . . . . . . . . 27-6 TSQLTable . . . . . . . . . . . . . . . . 28-7
xvi
Representing the results of a Copying data from another dataset . . . . 29-14
stored procedure . . . . . . . . . . . . . . 28-8 Assigning data directly . . . . . . . . 29-14
Fetching the data. . . . . . . . . . . . . . . . . 28-8 Cloning a client dataset cursor . . . . 29-15
Preparing the dataset . . . . . . . . . . . . 28-9 Adding application-specific information
Fetching multiple datasets . . . . . . . . . 28-9 to the data . . . . . . . . . . . . . . . . . 29-15
Executing commands that do not Using a client dataset to cache updates. . . . 29-16
return records. . . . . . . . . . . . . . . . . . 28-10 Overview of using cached updates . . . . 29-17
Specifying the command to execute . . . . 28-10 Choosing the type of dataset for caching
Executing the command . . . . . . . . . . . 28-11 updates . . . . . . . . . . . . . . . . . . . 29-18
Creating and modifying server Indicating what records are modified . . . 29-19
metadata . . . . . . . . . . . . . . . . . . . 28-11 Updating records . . . . . . . . . . . . . . 29-20
Setting up master/detail linked cursors. . . . 28-12 Applying updates . . . . . . . . . . . 29-20
Accessing schema information . . . . . . . . . 28-13 Intervening as updates are
Fetching metadata into a unidirectional applied . . . . . . . . . . . . . . . . . 29-21
dataset . . . . . . . . . . . . . . . . . . . . 28-13 Reconciling update errors . . . . . . . 29-23
Fetching data after using the dataset Using a client dataset with a provider . . . . 29-24
for metadata . . . . . . . . . . . . . . 28-14 Specifying a provider . . . . . . . . . . . . 29-25
The structure of metadata Requesting data from the source dataset
datasets . . . . . . . . . . . . . . . . . 28-14 or document . . . . . . . . . . . . . . . . 29-26
Debugging dbExpress applications . . . . . . 28-19 Incremental fetching . . . . . . . . . . 29-26
Using TSQLMonitor to monitor Fetch-on-demand . . . . . . . . . . . . 29-27
SQL commands . . . . . . . . . . . . . . . 28-19 Getting parameters from the source
Using a callback to monitor dataset . . . . . . . . . . . . . . . . . . . 29-27
SQL commands . . . . . . . . . . . . . . . 28-20 Passing parameters to the source
dataset . . . . . . . . . . . . . . . . . . . 29-28
Chapter 29 Sending query or stored procedure
Using client datasets 29-1 parameters . . . . . . . . . . . . . . . 29-29
Working with data using a client dataset . . . 29-2 Limiting records with parameters . . 29-29
Navigating data in client datasets . . . . . 29-2 Handling constraints from the server . . . 29-30
Limiting what records appear. . . . . . . . 29-2 Refreshing records. . . . . . . . . . . . . . 29-31
Editing data . . . . . . . . . . . . . . . . . . 29-5 Communicating with providers using
Undoing changes . . . . . . . . . . . . 29-5 custom events . . . . . . . . . . . . . . . 29-31
Saving changes . . . . . . . . . . . . . 29-6 Overriding the source dataset . . . . . . . 29-32
Constraining data values . . . . . . . . . . 29-7 Using a client dataset with file-based
Specifying custom constraints . . . . . 29-7 data . . . . . . . . . . . . . . . . . . . . . . . 29-33
Sorting and indexing. . . . . . . . . . . . . 29-8 Creating a new dataset . . . . . . . . . . . 29-33
Adding a new index . . . . . . . . . . . 29-8 Loading data from a file or stream . . . . 29-34
Deleting and switching indexes . . . . 29-9 Merging changes into data . . . . . . . . . 29-34
Using indexes to group data . . . . . . 29-9 Saving data to a file or stream . . . . . . . 29-35
Representing calculated values . . . . . . . 29-10 Using a simple dataset . . . . . . . . . . . . . 29-35
Using internally calculated fields When to use TSimpleDataSet . . . . . . . 29-36
in client datasets . . . . . . . . . . . . 29-11 Setting up a simple dataset . . . . . . . . . 29-36
Using maintained aggregates . . . . . . . . 29-11
Specifying aggregates . . . . . . . . . 29-12
Aggregating over groups of
records . . . . . . . . . . . . . . . . . 29-13
Obtaining aggregate values . . . . . . 29-14
xvii
Chapter 30 Building a multi-tiered application . . . . . . 31-11
Creating the application server . . . . . . . . 31-12
Using provider components 30-1 Setting up the remote data module . . . . 31-13
Determining the source of data. . . . . . . . . 30-2
Configuring
Using a dataset as the source
TRemoteDataModule . . . . . . . . 31-13
of the data . . . . . . . . . . . . . . . . . . 30-2
Configuring TMTSDataModule . . . 31-15
Using an XML document as the source
Configuring TSoapDataModule . . . 31-16
of the data . . . . . . . . . . . . . . . . . . 30-2
Extending the application server’s
Communicating with the client dataset . . . . 30-3
interface. . . . . . . . . . . . . . . . . . . 31-16
Choosing how to apply updates using
Adding callbacks to the application
a dataset provider . . . . . . . . . . . . . . . 30-4
server’s interface . . . . . . . . . . . 31-17
Controlling what information is included
Extending a transactional application
in data packets . . . . . . . . . . . . . . . . . 30-4
server’s interface . . . . . . . . . . . 31-17
Specifying what fields appear in
Managing transactions in multi-tiered
data packets . . . . . . . . . . . . . . . . . 30-4
applications . . . . . . . . . . . . . . . . 31-17
Setting options that influence the
Supporting master/detail
data packets . . . . . . . . . . . . . . . . . 30-5
relationships . . . . . . . . . . . . . . . . 31-18
Adding custom information to
Supporting state information in remote
data packets . . . . . . . . . . . . . . . . . 30-6
data modules. . . . . . . . . . . . . . . . 31-19
Responding to client data requests. . . . . . . 30-7
Using multiple remote data modules . . . 31-21
Responding to client update requests . . . . . 30-8
Registering the application server . . . . . . 31-22
Editing delta packets before updating
Creating the client application. . . . . . . . . 31-22
the database . . . . . . . . . . . . . . . . . 30-9
Connecting to the application server . . . 31-23
Influencing how updates are applied . . . 30-10
Specifying a connection
Screening individual updates . . . . . . . . 30-11
using DCOM . . . . . . . . . . . . . 31-24
Resolving update errors on
Specifying a connection
the provider . . . . . . . . . . . . . . . . . 30-11
using sockets . . . . . . . . . . . . . 31-24
Applying updates to datasets that do
Specifying a connection
not represent a single table . . . . . . . . 30-12
using HTTP . . . . . . . . . . . . . . 31-25
Responding to client-generated events . . . . 30-12
Specifying a connection
Handling server constraints . . . . . . . . . . 30-13
using SOAP . . . . . . . . . . . . . . 31-26
Chapter 31 Brokering connections . . . . . . . . . 31-27
Managing server connections . . . . . . . 31-27
Creating multi-tiered applications 31-1 Connecting to the server . . . . . . . . 31-27
Advantages of the multi-tiered Dropping or changing a server
database model . . . . . . . . . . . . . . . . . 31-2 connection . . . . . . . . . . . . . . . 31-28
Understanding multi-tiered database Calling server interfaces . . . . . . . . . . 31-28
applications . . . . . . . . . . . . . . . . . . . 31-2 Using early binding with DCOM . . . 31-29
Overview of a three-tiered application. . . 31-3 Using dispatch interfaces with
The structure of the client application . . . 31-4 TCP/IP or HTTP . . . . . . . . . . . 31-29
The structure of the application server. . . 31-5 Calling the interface of a SOAP-based
The contents of the remote server . . . . . . . . . . . . . . . . . . 31-30
data module . . . . . . . . . . . . . . 31-6 Connecting to an application server that
Using transactional data modules . . . 31-7 uses multiple data modules . . . . . . . 31-30
Pooling remote data modules . . . . . 31-8 Writing Web-based client applications . . . . 31-31
Choosing a connection protocol . . . . . . 31-9 Distributing a client application as an
Using DCOM connections . . . . . . . 31-9 ActiveX control . . . . . . . . . . . . . . 31-32
Using Socket connections . . . . . . . . 31-9 Creating an Active Form for the client
Using Web connections . . . . . . . . . 31-10 application . . . . . . . . . . . . . . . 31-33
Using SOAP connections . . . . . . . . 31-11
xviii
Building Web applications using Part III
InternetExpress . . . . . . . . . . . . . . . 31-33
Building an InternetExpress
Writing Internet applications
application. . . . . . . . . . . . . . . . . . 31-34
Using the javascript libraries . . . . . . 31-35 Chapter 33
Granting permission to access and Creating Internet server
launch the application server . . . . . 31-36 applications 33-1
Using an XML broker . . . . . . . . . . . . 31-36 About Web Broker and WebSnap . . . . . . . . 33-1
Fetching XML data packets . . . . . . . 31-36 Terminology and standards . . . . . . . . . . . 33-3
Applying updates from XML Parts of a Uniform Resource Locator . . . . 33-3
delta packets . . . . . . . . . . . . . . 31-37 URI vs. URL . . . . . . . . . . . . . . . . 33-4
Creating Web pages with an HTTP request header information. . . . . . 33-4
InternetExpress page producer . . . . . . 31-39 HTTP server activity . . . . . . . . . . . . . . . 33-5
Using the Web page editor . . . . . . . 31-39 Composing client requests . . . . . . . . . . 33-5
Setting Web item properties . . . . . . 31-40 Serving client requests . . . . . . . . . . . . 33-5
Customizing the InternetExpress Responding to client requests . . . . . . . . 33-6
page producer template . . . . . . . . 31-41 Types of Web server applications . . . . . . . . 33-6
ISAPI and NSAPI . . . . . . . . . . . . . 33-6
Chapter 32 CGI stand-alone . . . . . . . . . . . . . 33-6
Using XML in database Apache . . . . . . . . . . . . . . . . . . . 33-7
applications 32-1 Web App Debugger . . . . . . . . . . . 33-7
Defining transformations . . . . . . . . . . . . 32-1 Converting Web server application
Mapping between XML nodes and target types . . . . . . . . . . . . . . . . . . 33-8
data packet fields . . . . . . . . . . . . . . 32-2 Debugging server applications . . . . . . . . . 33-9
Using XMLMapper. . . . . . . . . . . . . . 32-4 Using the Web Application Debugger . . . 33-9
Loading an XML schema or Launching your application with
data packet . . . . . . . . . . . . . . . 32-4 the Web Application Debugger . . . . 33-9
Defining mappings . . . . . . . . . . . 32-5 Converting your application to
Generating transformation files . . . . 32-6 another type of Web server
Converting XML documents into application . . . . . . . . . . . . . . . 33-10
data packets. . . . . . . . . . . . . . . . . . . 32-6 Debugging Web applications
Specifying the source XML document . . . 32-6 that are DLLs. . . . . . . . . . . . . . . . 33-10
Specifying the transformation . . . . . . . 32-7 User rights necessary for
Obtaining the resulting data packet . . . . 32-7 DLL debugging . . . . . . . . . . . . 33-10
Converting user-defined nodes . . . . . . . 32-7
Using an XML document as the source Chapter 34
for a provider . . . . . . . . . . . . . . . . . . 32-8 Using Web Broker 34-1
Using an XML document as the client Creating Web server applications with
of a provider . . . . . . . . . . . . . . . . . . 32-9 Web Broker. . . . . . . . . . . . . . . . . . . . 34-1
Fetching an XML document from The Web module. . . . . . . . . . . . . . . . 34-2
a provider . . . . . . . . . . . . . . . . . . 32-9 The Web Application object . . . . . . . . . 34-3
Applying updates from an XML The structure of a Web Broker
document to a provider . . . . . . . . . . 32-11 application . . . . . . . . . . . . . . . . . . . . 34-3
The Web dispatcher. . . . . . . . . . . . . . . . 34-5
Adding actions to the dispatcher . . . . . . 34-5
Dispatching request messages . . . . . . . . 34-5
xix
Action items . . . . . . . . . . . . . . . . . . . 34-6 Representing database information
Determining when action items fire . . . . 34-6 in HTML . . . . . . . . . . . . . . . . . . 34-19
The target URL . . . . . . . . . . . . . . 34-6 Using dataset page producers . . . . . 34-19
The request method type . . . . . . . . 34-7 Using table producers . . . . . . . . . 34-20
Enabling and disabling action Specifying the table attributes . . . . . 34-20
items . . . . . . . . . . . . . . . . . . . 34-7 Specifying the row attributes . . . . . 34-20
Choosing a default action item . . . . . 34-7 Specifying the columns . . . . . . . . 34-20
Responding to request messages with Embedding tables in HTML
action items . . . . . . . . . . . . . . . . . 34-8 documents . . . . . . . . . . . . . . . 34-21
Sending the response . . . . . . . . . . 34-8 Setting up a dataset table
Using multiple action items . . . . . . 34-9 producer . . . . . . . . . . . . . . . . 34-21
Accessing client request information . . . . . 34-9 Setting up a query table
Properties that contain request header producer . . . . . . . . . . . . . . . . 34-21
information . . . . . . . . . . . . . . . . . 34-9
Properties that identify the target . . . 34-9 Chapter 35
Properties that describe the Creating Web Server applications
Web client . . . . . . . . . . . . . . . . 34-10
Properties that identify the purpose
using WebSnap 35-1
Fundamental WebSnap components . . . . . . 35-2
of the request . . . . . . . . . . . . . . 34-10
Web modules. . . . . . . . . . . . . . . . . . 35-2
Properties that describe the expected
Web application module types . . . . . 35-3
response . . . . . . . . . . . . . . . . 34-10
Web page modules . . . . . . . . . . . . 35-4
Properties that describe the
Web data modules . . . . . . . . . . . . 35-5
content . . . . . . . . . . . . . . . . . 34-11
Adapters . . . . . . . . . . . . . . . . . . . . 35-5
The content of HTTP request
Fields . . . . . . . . . . . . . . . . . . . . 35-6
messages. . . . . . . . . . . . . . . . . . . 34-11
Actions . . . . . . . . . . . . . . . . . . . 35-6
Creating HTTP response messages . . . . . . 34-11
Errors . . . . . . . . . . . . . . . . . . . 35-6
Filling in the response header. . . . . . . . 34-11
Records . . . . . . . . . . . . . . . . . . 35-6
Indicating the response status . . . . . 34-12
Page producers . . . . . . . . . . . . . . . . 35-6
Indicating the need for client
Creating Web server applications
action . . . . . . . . . . . . . . . . . . 34-12
with WebSnap . . . . . . . . . . . . . . . . . . 35-7
Describing the server application . . . 34-12
Selecting a server type . . . . . . . . . . . . 35-8
Describing the content . . . . . . . . . 34-12
Specifying application module
Setting the response content . . . . . . . . 34-13
components . . . . . . . . . . . . . . . . . 35-9
Sending the response . . . . . . . . . . . . 34-13
Selecting Web application module
Generating the content of response
options . . . . . . . . . . . . . . . . . . . 35-10
messages . . . . . . . . . . . . . . . . . . . . 34-13
Advanced HTML design. . . . . . . . . . . . 35-11
Using page producer components . . . . . 34-14
Manipulating server-side script in
HTML templates . . . . . . . . . . . . . 34-14
HTML files . . . . . . . . . . . . . . . . . 35-12
Specifying the HTML template . . . . 34-15
Login support . . . . . . . . . . . . . . . . . . 35-13
Converting HTML-transparent
Adding login support. . . . . . . . . . . . 35-13
tags . . . . . . . . . . . . . . . . . . . 34-16
Using the sessions service . . . . . . . . . 35-14
Using page producers from an
Login pages . . . . . . . . . . . . . . . . . 35-15
action item . . . . . . . . . . . . . . . 34-16
Setting pages to require logins . . . . . . . 35-17
Chaining page producers
User access rights . . . . . . . . . . . . . . 35-17
together . . . . . . . . . . . . . . . . . 34-17
Dynamically displaying fields as
Using database information in
edit or text boxes . . . . . . . . . . . 35-18
responses . . . . . . . . . . . . . . . . . . . . 34-18
Hiding fields and their contents . . . 35-18
Adding a session to the Web module . . . 34-18
Preventing page access . . . . . . . . . 35-19
xx
Server-side scripting in WebSnap . . . . . . . 35-19 Abstracting XML documents with the
Active scripting . . . . . . . . . . . . . . . . 35-20 Data Binding wizard . . . . . . . . . . . . . . 37-6
Script engine . . . . . . . . . . . . . . . . . 35-20 Using the XML Data Binding wizard . . . . 37-8
Script blocks. . . . . . . . . . . . . . . . . . 35-20 Using code that the XML Data
Creating script . . . . . . . . . . . . . . . . 35-21 Binding wizard generates. . . . . . . . . . 37-9
Wizard templates . . . . . . . . . . . . 35-21
TAdapterPageProducer . . . . . . . . . 35-21 Chapter 38
Editing and viewing script . . . . . . . . . 35-21 Using Web Services 38-1
Including script in a page . . . . . . . . . . 35-21 Understanding invokable interfaces . . . . . . 38-2
Script objects . . . . . . . . . . . . . . . . . 35-22 Using nonscalar types in invokable
Dispatching requests and responses . . . . . . 35-22 interfaces . . . . . . . . . . . . . . . . . . . 38-4
Dispatcher components . . . . . . . . . . . 35-23 Registering nonscalar types . . . . . . . 38-5
Adapter dispatcher operation. . . . . . . . 35-23 Using remotable objects . . . . . . . . . 38-6
Using adapter components to Representing attachments . . . . . . . . 38-7
generate content . . . . . . . . . . . . 35-23 Managing the lifetime of remotable
Receiving adapter requests and objects . . . . . . . . . . . . . . . . . . 38-7
generating responses . . . . . . . . . 35-25 Remotable object example . . . . . . . . 38-7
Image request . . . . . . . . . . . . . . 35-26 Writing servers that support Web Services. . . 38-9
Image response . . . . . . . . . . . . . 35-27 Building a Web Service server . . . . . . . . 38-9
Dispatching action items . . . . . . . . . . 35-27 Using the SOAP application wizard . . . 38-10
Page dispatcher operation . . . . . . . . . . 35-28 Adding new Web Services . . . . . . . . . 38-11
Editing the generated code . . . . . . 38-12
Chapter 36 Using a different base class . . . . . . 38-12
Creating Web server applications Using the WSDL importer . . . . . . . . . 38-13
using IntraWeb 36-1 Browsing for Business services . . . . . . 38-14
Using IntraWeb components . . . . . . . . . . 36-2 Understanding UDDI . . . . . . . . . 38-15
Getting started with IntraWeb . . . . . . . . . 36-3 Using the UDDI browser . . . . . . . 38-15
Creating a new IntraWeb application . . . 36-4 Defining and using SOAP headers . . . . 38-16
Editing the main form . . . . . . . . . . . . 36-4 Defining header classes . . . . . . . . 38-16
Writing an event handler for Sending and receiving headers . . . . 38-16
the button . . . . . . . . . . . . . . . . . . 36-5 Handling scalar-type headers . . . . . 38-17
Running the completed application . . . . 36-6 Communicating the structure of your
Using IntraWeb with Web Broker headers to other applications . . . . 38-18
and WebSnap . . . . . . . . . . . . . . . . . . 36-7 Creating custom exception classes for
For more information . . . . . . . . . . . . . . 36-8 Web Services . . . . . . . . . . . . . . . . 38-18
Generating WSDL documents for
Chapter 37 a Web Service application. . . . . . . . . 38-19
Writing clients for Web Services. . . . . . . . 38-20
Working with XML documents 37-1 Importing WSDL documents . . . . . . . 38-20
Using the Document Object Model . . . . . . 37-2
Calling invokable interfaces . . . . . . . . 38-20
Working with XML components . . . . . . . . 37-4
Obtaining an invokable interface
Using TXMLDocument . . . . . . . . . . . 37-4
from the generated function . . . . . 38-21
Working with XML nodes . . . . . . . . . . 37-4
Using a remote interfaced object . . . 38-21
Working with a node’s value . . . . . . 37-5
Processing headers in client
Working with a node’s attributes . . . 37-5
applications . . . . . . . . . . . . . . . . 38-23
Adding and deleting child nodes . . . 37-6
xxi
Chapter 39 Part IV
Working with sockets 39-1 Developing COM-based applications
Implementing services . . . . . . . . . . . . . 39-1
Understanding service protocols . . . . . . 39-2 Chapter 40
Communicating with
applications . . . . . . . . . . . . . . . 39-2
Overview of COM technologies 40-1
COM as a specification and
Services and ports . . . . . . . . . . . . . . 39-2
implementation . . . . . . . . . . . . . 40-2
Types of socket connections. . . . . . . . . . . 39-3
COM extensions . . . . . . . . . . . . . 40-2
Client connections . . . . . . . . . . . . . . 39-3
Parts of a COM application . . . . . . . . . . . 40-3
Listening connections . . . . . . . . . . . . 39-3
COM interfaces . . . . . . . . . . . . . . . . 40-3
Server connections . . . . . . . . . . . . . . 39-3
The fundamental COM interface,
Describing sockets . . . . . . . . . . . . . . . . 39-4
IUnknown . . . . . . . . . . . . . . . . 40-4
Describing the host . . . . . . . . . . . . . . 39-4
COM interface pointers . . . . . . . . . 40-5
Choosing between a host name
COM servers . . . . . . . . . . . . . . . . . . 40-5
and an IP address . . . . . . . . . . . 39-5
CoClasses and class factories . . . . . . 40-6
Using ports . . . . . . . . . . . . . . . . . . 39-5
In-process, out-of-process, and
Using socket components . . . . . . . . . . . . 39-6
remote servers . . . . . . . . . . . . . . 40-7
Getting information about the
The marshaling mechanism . . . . . . . 40-8
connection . . . . . . . . . . . . . . . . . . 39-6
Aggregation . . . . . . . . . . . . . . . . 40-9
Using client sockets . . . . . . . . . . . . . 39-6
COM clients . . . . . . . . . . . . . . . . . 40-10
Specifying the desired server . . . . . . 39-7
COM extensions. . . . . . . . . . . . . . . . . 40-10
Forming the connection . . . . . . . . . 39-7
Automation servers . . . . . . . . . . . . . 40-12
Getting information about the
Active Server Pages . . . . . . . . . . . . . 40-13
connection . . . . . . . . . . . . . . . 39-7
ActiveX controls . . . . . . . . . . . . . . . 40-13
Closing the connection . . . . . . . . . 39-7
Active Documents. . . . . . . . . . . . . . 40-14
Using server sockets . . . . . . . . . . . . . 39-7
Transactional objects . . . . . . . . . . . . 40-15
Specifying the port . . . . . . . . . . . 39-8
Type libraries. . . . . . . . . . . . . . . . . 40-16
Listening for client requests . . . . . . 39-8
The content of type libraries . . . . . . 40-16
Connecting to clients . . . . . . . . . . 39-8
Creating type libraries . . . . . . . . . 40-17
Closing server connections . . . . . . . 39-8
When to use type libraries . . . . . . . 40-17
Responding to socket events . . . . . . . . . . 39-8
Accessing type libraries . . . . . . . . 40-18
Error events . . . . . . . . . . . . . . . . . . 39-9
Benefits of using type libraries . . . . 40-18
Client events . . . . . . . . . . . . . . . . . 39-9
Using type library tools . . . . . . . . 40-19
Server events . . . . . . . . . . . . . . . . . 39-9
Implementing COM objects
Events when listening . . . . . . . . . . 39-9
with wizards . . . . . . . . . . . . . . . . . . 40-19
Events with client connections . . . . . 39-10
Code generated by wizards . . . . . . . . 40-22
Reading and writing over socket
connections . . . . . . . . . . . . . . . . . . . 39-10
Non-blocking connections. . . . . . . . . . 39-10
Reading and writing events . . . . . . 39-11
Blocking connections. . . . . . . . . . . . . 39-11
xxii
Chapter 41 Chapter 42
Working with type libraries 41-1 Creating COM clients 42-1
Type Library editor . . . . . . . . . . . . . . . 41-2 Importing type library information . . . . . . . 42-2
Parts of the Type Library editor. . . . . . . 41-3 Using the Import Type Library dialog . . . 42-3
Toolbar . . . . . . . . . . . . . . . . . . 41-3 Using the Import ActiveX dialog . . . . . . 42-4
Object list pane . . . . . . . . . . . . . . 41-5 Code generated when you import
Status bar . . . . . . . . . . . . . . . . . 41-5 type library information . . . . . . . . . . 42-5
Pages of type information . . . . . . . 41-6 Controlling an imported object . . . . . . . . . 42-6
Type library elements . . . . . . . . . . . . 41-8 Using component wrappers . . . . . . . . . 42-6
Interfaces . . . . . . . . . . . . . . . . . 41-9 ActiveX wrappers . . . . . . . . . . . . 42-6
Dispinterfaces . . . . . . . . . . . . . . 41-9 Automation object wrappers . . . . . . 42-7
CoClasses . . . . . . . . . . . . . . . . . 41-10 Using data-aware ActiveX controls . . . . . 42-8
Type definitions . . . . . . . . . . . . . 41-10 Example: Printing a document with
Modules . . . . . . . . . . . . . . . . . 41-11 Microsoft Word . . . . . . . . . . . . . . . 42-9
Using the Type Library editor. . . . . . . . 41-11 Preparing Delphi for this example . . 42-10
Valid types . . . . . . . . . . . . . . . . 41-12 Importing the Word type library . . . 42-10
Using Delphi or IDL syntax . . . . . . 41-13 Using a VTable or dispatch
Creating a new type library . . . . . . 41-19 interface object to control
Opening an existing type library . . . 41-20 Microsoft Word . . . . . . . . . . . . 42-11
Adding an interface to the type Cleaning up the example . . . . . . . 42-12
library . . . . . . . . . . . . . . . . . . 41-21 Writing client code based on type
Modifying an interface using the library definitions . . . . . . . . . . . . . 42-13
type library . . . . . . . . . . . . . . . 41-21 Connecting to a server . . . . . . . . . 42-13
Adding properties and methods to Controlling an Automation server
an interface or dispinterface . . . . . 41-22 using a dual interface . . . . . . . . 42-13
Adding a CoClass to the type Controlling an Automation server
library . . . . . . . . . . . . . . . . . . 41-23 using a dispatch interface . . . . . . 42-14
Adding an interface to a CoClass . . . 41-23 Handling events in an automation
Adding an enumeration to the controller . . . . . . . . . . . . . . . . 42-14
type library . . . . . . . . . . . . . . . 41-24 Creating clients for servers that do not
Adding an alias to the type have a type library . . . . . . . . . . . . . . 42-16
library . . . . . . . . . . . . . . . . . . 41-24 Using .NET assemblies with Delphi . . . . . 42-17
Adding a record or union to the Requirements for COM
type library . . . . . . . . . . . . . . . 41-24 interoperability . . . . . . . . . . . . . . 42-17
Adding a module to the type .NET components and type libraries . . . 42-18
library . . . . . . . . . . . . . . . . . . 41-25 Accessing user-defined .NET
Saving and registering type library components . . . . . . . . . . . . . . . . 42-20
information . . . . . . . . . . . . . . . 41-25
Apply Updates dialog . . . . . . . . . 41-26
Saving a type library . . . . . . . . . . 41-26
Refreshing the type library . . . . . . . 41-26
Registering the type library . . . . . . 41-27
Exporting an IDL file . . . . . . . . . . 41-27
Deploying type libraries . . . . . . . . . . . . 41-27
xxiii
Chapter 43 Registering an Active Server Object . . . . . . 44-8
Registering an in-process server . . . . . . . 44-8
Creating simple COM servers 43-1 Registering an out-of-process server . . . . 44-8
Overview of creating a COM object . . . . . . 43-2
Testing and debugging the Active Server
Designing a COM object . . . . . . . . . . . . 43-2
Page application. . . . . . . . . . . . . . . . . 44-8
Using the COM object wizard . . . . . . . . . 43-3
Using the Automation object wizard . . . . . 43-5
COM object instancing types . . . . . . . . 43-6
Chapter 45
Choosing a threading model . . . . . . . . 43-6 Creating an ActiveX control 45-1
Writing an object that supports the Overview of ActiveX control creation . . . . . 45-2
free threading model . . . . . . . . . 43-8 Elements of an ActiveX control . . . . . . . 45-2
Writing an object that supports the VCL control . . . . . . . . . . . . . . . . 45-3
apartment threading model . . . . . 43-9 ActiveX wrapper . . . . . . . . . . . . . 45-3
Writing an object that supports the Type library . . . . . . . . . . . . . . . . 45-3
neutral threading model . . . . . . . 43-9 Property page . . . . . . . . . . . . . . . 45-3
Defining a COM object’s interface . . . . . . . 43-9 Designing an ActiveX control . . . . . . . . . . 45-4
Adding a property to the object’s Generating an ActiveX control from a
interface . . . . . . . . . . . . . . . . . . . 43-10 VCL control . . . . . . . . . . . . . . . . . . . 45-4
Adding a method to the object’s Generating an ActiveX control based on
interface . . . . . . . . . . . . . . . . . . . 43-10 a VCL form. . . . . . . . . . . . . . . . . . . . 45-6
Exposing events to clients . . . . . . . . . . 43-11 Licensing ActiveX controls. . . . . . . . . . . . 45-7
Managing events in your Customizing the ActiveX control’s
Automation object . . . . . . . . . . . 43-12 interface . . . . . . . . . . . . . . . . . . . . . 45-8
Automation interfaces . . . . . . . . . . . . . . 43-13 Adding additional properties,
Dual interfaces . . . . . . . . . . . . . . . . 43-13 methods, and events . . . . . . . . . . . . 45-9
Dispatch interfaces . . . . . . . . . . . . . . 43-14 Adding properties and methods . . . . 45-9
Custom interfaces . . . . . . . . . . . . . . 43-15 Adding events . . . . . . . . . . . . . 45-10
Marshaling data . . . . . . . . . . . . . . . . . 43-15 Enabling simple data binding with
Automation compatible types . . . . . . . 43-16 the type library. . . . . . . . . . . . . . . 45-11
Type restrictions for automatic Creating a property page for an
marshaling . . . . . . . . . . . . . . . . . 43-16 ActiveX control . . . . . . . . . . . . . . . . 45-12
Custom marshaling . . . . . . . . . . . . . 43-17 Creating a new property page . . . . . . . 45-13
Registering a COM object . . . . . . . . . . . . 43-17 Adding controls to a property page . . . . 45-13
Registering an in-process server . . . . . . 43-17 Associating property page controls
Registering an out-of-process server . . . . 43-17 with ActiveX control properties . . . . . 45-13
Testing and debugging the application . . . . 43-18 Updating the property page . . . . . . 45-13
Updating the object . . . . . . . . . . 45-14
Chapter 44 Connecting a property page to an
ActiveX control . . . . . . . . . . . . . . 45-14
Creating an Active Server Page 44-1 Registering an ActiveX control . . . . . . . . 45-15
Creating an Active Server Object. . . . . . . . 44-2
Testing an ActiveX control . . . . . . . . . . . 45-15
Using the ASP intrinsics . . . . . . . . . . . 44-3
Deploying an ActiveX control
Application . . . . . . . . . . . . . . . . 44-4
on the Web . . . . . . . . . . . . . . . . . . . 45-15
Request . . . . . . . . . . . . . . . . . . 44-4
Setting options . . . . . . . . . . . . . . . . 45-16
Response . . . . . . . . . . . . . . . . . 44-5
Session . . . . . . . . . . . . . . . . . . 44-6
Server . . . . . . . . . . . . . . . . . . . 44-6
Creating ASPs for in-process or
out-of-process servers . . . . . . . . . . . 44-7
xxiv
Chapter 46 Role-based security . . . . . . . . . . . . . . . 46-15
Overview of creating transactional
Creating MTS or COM+ objects 46-1 objects . . . . . . . . . . . . . . . . . . . . . 46-15
Understanding transactional objects. . . . . . 46-2
Using the Transactional Object wizard . . . . 46-16
Requirements for a transactional
Choosing a threading model for a
object . . . . . . . . . . . . . . . . . . . . . 46-3
transactional object . . . . . . . . . . . . 46-17
Managing resources . . . . . . . . . . . . . . . 46-3
Activities . . . . . . . . . . . . . . . . 46-18
Accessing the object context. . . . . . . . . 46-4
Generating events under COM+ . . . . . . . 46-19
Just-in-time activation . . . . . . . . . . . . 46-4
Using the Event Object wizard. . . . . . . 46-21
Resource pooling . . . . . . . . . . . . . . . 46-5
Using the COM+ Event Subscription
Database resource dispensers . . . . . 46-6
object wizard . . . . . . . . . . . . . . . . 46-22
Shared property manager . . . . . . . 46-6
Firing events using a COM+ event
Releasing resources . . . . . . . . . . . 46-8
object . . . . . . . . . . . . . . . . . . . . 46-23
Object pooling . . . . . . . . . . . . . . . . 46-8
Passing object references . . . . . . . . . . . . 46-23
MTS and COM+ transaction support . . . . . 46-9
Using the SafeRef method . . . . . . . 46-24
Transaction attributes . . . . . . . . . . . . 46-10
Callbacks . . . . . . . . . . . . . . . . 46-25
Setting the transaction attribute . . . . 46-11
Debugging and testing transactional
Stateful and stateless objects . . . . . . . . 46-11
objects . . . . . . . . . . . . . . . . . . . . . 46-25
Influencing how transactions end . . . . . 46-12
Installing transactional objects . . . . . . . . 46-26
Initiating transactions . . . . . . . . . . . . 46-12
Administering transactional objects . . . . . 46-27
Setting up a transaction object
on the client side . . . . . . . . . . . . 46-13
Setting up a transaction object on
Index I-1
the server side . . . . . . . . . . . . . 46-14
Transaction time-out . . . . . . . . . . . . . 46-14
xxv
Tables
1.1 Typefaces and symbols . . . . . . . . . . . 1-2 10.1 Edit control properties . . . . . . . . . . . 10-2
3.1 Component sublibraries . . . . . . . . . . 3-1 12.1 Graphic object types . . . . . . . . . . . . 12-3
3.2 Important base classes . . . . . . . . . . . 3-5 12.2 Common properties of the Canvas
5.1 Values for the Origin parameter . . . . . . 5-5 object . . . . . . . . . . . . . . . . . . . . . 12-4
5.2 Open modes . . . . . . . . . . . . . . . . . 5-7 12.3 Common methods of the Canvas
5.3 Share modes . . . . . . . . . . . . . . . . . 5-7 object . . . . . . . . . . . . . . . . . . . . . 12-4
5.4 Shared modes available for each 12.4 CLX MIME types and constants . . . . 12-22
open mode . . . . . . . . . . . . . . . . . . 5-7 12.5 Mouse events . . . . . . . . . . . . . . . 12-24
5.5 Attribute constants and values . . . . . . . 5-9 12.6 Mouse-event parameters. . . . . . . . . 12-25
5.6 Classes for managing lists . . . . . . . . . 5-14 12.7 Multimedia device types and their
5.7 String comparison routines . . . . . . . . . 5-24 functions . . . . . . . . . . . . . . . . . . 12-33
5.8 Case conversion routines . . . . . . . . . . 5-25 13.1 Thread priorities . . . . . . . . . . . . . . 13-3
5.9 String modification routines . . . . . . . . 5-25 13.2 WaitFor return values . . . . . . . . . . .13-11
5.10 Sub-string routines . . . . . . . . . . . . . 5-25 14.1 Selected exception classes . . . . . . . . 14-10
5.11 Null-terminated string comparison 15.1 Porting techniques . . . . . . . . . . . . . 15-2
routines . . . . . . . . . . . . . . . . . . . . 5-26 15.2 Changed or different features . . . . . . . 15-7
5.12 Case conversion routines for 15.3 WinCLX-only and equivalent
null-terminated strings . . . . . . . . . . . 5-26 VisualCLX units. . . . . . . . . . . . . . . 15-8
5.13 String modification routines . . . . . . . . 5-26 15.4 VisualCLX-only units . . . . . . . . . . . 15-9
5.14 Sub-string routines . . . . . . . . . . . . . 5-26 15.5 WinCLX-only units . . . . . . . . . . . . . 15-9
5.15 String copying routines . . . . . . . . . . . 5-27 15.6 Differences in the Linux and Windows
5.16 Compiler directives for strings . . . . . . . 5-30 operating environments . . . . . . . . . 15-18
6.1 Component palette pages . . . . . . . . . 6-7 15.7 Common Linux directories . . . . . . . 15-20
7.1 Properties of selected text. . . . . . . . . . 7-9 15.8 Comparable data-access
7.2 Fixed vs. variable owner-draw styles . . . 7-13 components . . . . . . . . . . . . . . . . 15-23
8.1 Compiler directives for libraries . . . . . . 8-11 15.9 Properties, methods, and events
8.2 Database pages on the Component for cached updates . . . . . . . . . . . . 15-27
palette . . . . . . . . . . . . . . . . . . . . . 8-12 16.1 Package files. . . . . . . . . . . . . . . . . 16-2
8.3 Web server applications. . . . . . . . . . . 8-14 16.2 Package-specific compiler directives . . .16-11
8.4 Context menu options for data 16.3 Package-specific command-line
modules. . . . . . . . . . . . . . . . . . . . 8-18 compiler switches. . . . . . . . . . . . . 16-13
8.5 Help methods in TApplication . . . . . . . 8-31 17.1 Runtime library functions . . . . . . . . . 17-3
9.1 Action setup terminology. . . . . . . . . . 9-18 17.2 VCL methods that support BiDi . . . . . 17-6
9.2 Default values of the action manager’s 17.3 Estimating string lengths . . . . . . . . . 17-7
PrioritySchedule property . . . . . . . . . 9-25 18.1 Application files . . . . . . . . . . . . . . 18-3
9.3 Action classes . . . . . . . . . . . . . . . . 9-30 18.2 Merge modules and their
9.4 Methods overriden by base classes dependencies . . . . . . . . . . . . . . . . 18-4
of specific actions . . . . . . . . . . . . . . 9-31 18.3 dbExpress deployment as stand-alone
9.5 Sample captions and their derived executable . . . . . . . . . . . . . . . . . . 18-7
names . . . . . . . . . . . . . . . . . . . . . 9-34 18.4 dbExpress deployment with
9.6 Menu Designer context menu driver DLLs . . . . . . . . . . . . . . . . . 18-8
commands . . . . . . . . . . . . . . . . . . 9-40 20.1 Data controls . . . . . . . . . . . . . . . . 20-2
9.7 Setting speed buttons’ appearance. . . . . 9-48 20.2 Column properties . . . . . . . . . . . . 20-20
9.8 Setting tool buttons’ appearance . . . . . . 9-50 20.3 Expanded TColumn Title
9.9 Setting a cool button’s appearance. . . . . 9-52 properties . . . . . . . . . . . . . . . . . 20-21
xxvi
20.4 Properties that affect the way 27.1 ADO components. . . . . . . . . . . . . . 27-2
composite fields appear . . . . . . . . . . 20-24 27.2 Connection parameters . . . . . . . . . . 27-4
20.5 Expanded TDBGrid Options 27.3 ADO connection modes . . . . . . . . . . 27-6
properties . . . . . . . . . . . . . . . . . . 20-25 27.4 Execution options for ADO
20.6 Grid control events . . . . . . . . . . . . 20-27 datasets . . . . . . . . . . . . . . . . . . 27-12
20.7 Selected database control grid 27.5 Comparison of ADO and client dataset
properties . . . . . . . . . . . . . . . . . . 20-29 cached updates . . . . . . . . . . . . . . 27-13
20.8 TDBNavigator buttons . . . . . . . . . . 20-30 28.1 Columns in tables of metadata
21.1 Rave Reports documentation. . . . . . . . 21-6 listing tables . . . . . . . . . . . . . . . . 28-15
23.1 Database connection components . . . . . 23-1 28.2 Columns in tables of metadata
24.1 Values for the dataset State property . . . 24-3 listing stored procedures. . . . . . . . . 28-15
24.2 Navigational methods of datasets . . . . . 24-5 28.3 Columns in tables of metadata
24.3 Navigational properties of datasets . . . . 24-6 listing fields . . . . . . . . . . . . . . . . 28-16
24.4 Comparison and logical operators 28.4 Columns in tables of metadata
that can appear in a filter . . . . . . . . . 24-14 listing indexes . . . . . . . . . . . . . . . 28-17
24.5 FilterOptions values . . . . . . . . . . . . 24-16 28.5 Columns in tables of metadata listing
24.6 Filtered dataset navigational parameters. . . . . . . . . . . . . . . . . 28-18
methods. . . . . . . . . . . . . . . . . . . 24-16 29.1 Filter support in client datasets . . . . . . 29-3
24.7 Dataset methods for inserting, 29.2 Summary operators for maintained
updating, and deleting data . . . . . . . 24-17 aggregates . . . . . . . . . . . . . . . . . 29-12
24.8 Methods that work with entire 29.3 Specialized client datasets for
records . . . . . . . . . . . . . . . . . . . 24-22 caching updates. . . . . . . . . . . . . . 29-18
24.9 Index-based search methods . . . . . . . 24-28 30.1 AppServer interface members. . . . . . . 30-3
25.1 TFloatField properties that affect 30.2 Provider options . . . . . . . . . . . . . . 30-5
data display . . . . . . . . . . . . . . . . . 25-1 30.3 UpdateStatus values . . . . . . . . . . . . 30-9
25.2 Special persistent field kinds . . . . . . . . 25-6 30.4 UpdateMode values . . . . . . . . . . . 30-10
25.3 Field component properties . . . . . . . 25-11 30.5 ProviderFlags values . . . . . . . . . . . 30-10
25.4 Field component formatting 31.1 Components used in multi-tiered
routines . . . . . . . . . . . . . . . . . . . 25-15 applications . . . . . . . . . . . . . . . . . 31-3
25.5 Field component events. . . . . . . . . . 25-16 31.2 Connection components . . . . . . . . . . 31-5
25.6 Selected field component methods . . . 25-17 31.3 Javascript libraries . . . . . . . . . . . . 31-35
25.7 Special conversion results . . . . . . . . 25-20 33.1 Web Broker versus WebSnap . . . . . . . 33-2
25.8 Types of object field components . . . . 25-24 34.1 MethodType values. . . . . . . . . . . . . 34-7
25.9 Common object field descendant 34.2 Predefined tag names . . . . . . . . . . 34-10
properties . . . . . . . . . . . . . . . . . . 25-24 35.1 Web application module types . . . . . . 35-3
26.1 Table types recognized by the BDE 35.2 Web server application types . . . . . . . 35-8
based on file extension . . . . . . . . . . . 26-5 35.3 Web application components . . . . . . . 35-9
26.2 TableType values. . . . . . . . . . . . . . . 26-6 35.4 Script objects . . . . . . . . . . . . . . . 35-22
26.3 BatchMove import modes . . . . . . . . . 26-8 35.5 Request information found in
26.4 Database-related informational action requests . . . . . . . . . . . . . . 35-25
methods for session components . . . . 26-27 36.1 VCL/CLX and IntraWeb
26.5 TSessionList properties and components . . . . . . . . . . . . . . . . . 36-2
methods. . . . . . . . . . . . . . . . . . . 26-30 38.1 Remotable classes. . . . . . . . . . . . . . 38-6
26.6 Properties, methods, and events 40.1 COM object requirements . . . . . . . . 40-12
for cached updates. . . . . . . . . . . . . 26-33 40.2 Delphi wizards for implementing COM,
26.7 UpdateKind values . . . . . . . . . . . . 26-39 Automation, and ActiveX objects . . . . 40-21
26.8 Batch move modes. . . . . . . . . . . . . 26-50 40.3 DAX Base classes for generated
26.9 Data Dictionary interface . . . . . . . . . 26-54 implementation classes . . . . . . . . . 40-23
xxvii
41.1 Type Library editor files . . . . . . . . . . 41-2 44.4 ISessionObject interface members . . . . 44-6
41.2 Type Library editor parts . . . . . . . . . . 41-3 44.5 IServer interface members . . . . . . . . . 44-6
41.3 Attribute syntax . . . . . . . . . . . . . . 41-14 46.1 IObjectContext methods for
43.1 Threading models for COM objects . . . . 43-7 transaction support . . . . . . . . . . . . 46-12
44.1 IApplicationObject interface 46.2 Threading models for transactional
members . . . . . . . . . . . . . . . . . . . 44-4 objects . . . . . . . . . . . . . . . . . . . 46-17
44.2 IRequest interface members . . . . . . . . 44-4 46.3 Call synchronization options . . . . . . 46-19
44.3 IResponse interface members . . . . . . . 44-5 46.4 Event publisher return codes . . . . . . 46-23
xxviii
Figures
3.1 A simplified hierarchy diagram . . . . . . 3-5 20.5 TDBCtrlGrid at design time . . . . . . . 20-28
4.1 A simple form . . . . . . . . . . . . . . . . 4-3 20.6 Buttons on the TDBNavigator
9.1 A frame with data-aware controls control . . . . . . . . . . . . . . . . . . . 20-29
and a data source component . . . . . . . 9-16 22.1 Decision support components
9.3 Menu terminology. . . . . . . . . . . . . . 9-32 at design time . . . . . . . . . . . . . . . . 22-2
9.4 MainMenu and PopupMenu 22.2 One-dimensional crosstab . . . . . . . . . 22-3
components . . . . . . . . . . . . . . . . . 9-33 22.3 Three-dimensional crosstab . . . . . . . . 22-3
9.6 Adding menu items to a main menu . . . 9-36 22.4 Decision graphs bound to different
9.7 Nested menu structures. . . . . . . . . . . 9-37 decision sources. . . . . . . . . . . . . . 22-15
10.2 A progress bar . . . . . . . . . . . . . . . 10-15 26.1 Components in a BDE-based
11.1 Part of the ModelMaker toolbar . . . . . . 11-3 application. . . . . . . . . . . . . . . . . . 26-2
11.2 ModelMaker showing a sample 31.1 Web-based multi-tiered database
model . . . . . . . . . . . . . . . . . . . . . 11-4 application. . . . . . . . . . . . . . . . . 31-31
11.3 The Classes view. . . . . . . . . . . . . . . 11-5 33.1 Parts of a Uniform Resource Locator . . . 33-3
11.4 The Units view . . . . . . . . . . . . . . . . 11-5 34.1 Structure of a Server Application . . . . . 34-4
11.5 The Diagrams view . . . . . . . . . . . . . 11-6 35.2 Web App Components dialog . . . . . . . 35-9
11.6 The Members view . . . . . . . . . . . . . 11-7 35.3 Web App Components dialog with
11.7 The Implementation Editor view . . . . . 11-8 options for login support selected . . . 35-14
11.8 The Unit Code Editor . . . . . . . . . . . . 11-8 35.4 An example of a login page as seen
11.9 The Diagram Editor . . . . . . . . . . . . . 11-9 from a Web page editor . . . . . . . . . 35-16
12.1 Bitmap-dimension dialog box from 35.5 Generating content flow . . . . . . . . . 35-24
the BMPDlg unit . . . . . . . . . . . . . . 12-21 35.6 Action request and response . . . . . . 35-26
17.1 TListBox set to bdLeftToRight . . . . . . . 17-5 35.7 Image response to a request . . . . . . . 35-27
17.2 TListBox set to bdRightToLeft . . . . . . . 17-5 35.8 Dispatching a page . . . . . . . . . . . . 35-28
17.3 TListBox set to 36.2 The main form of the IntraWeb
bdRightToLeftNoAlign . . . . . . . . . . . 17-5 application. . . . . . . . . . . . . . . . . . 36-5
17.4 TListBox set to 40.1 A COM interface . . . . . . . . . . . . . . 40-3
bdRightToLeftReadingOnly . . . . . . . . 17-5 40.2 Interface vtable . . . . . . . . . . . . . . . 40-5
19.1 Generic Database Architecture . . . . . . . 19-6 40.3 In-process server . . . . . . . . . . . . . . 40-7
19.2 Connecting directly to the 40.4 Out-of-process and remote servers . . . . 40-8
database server. . . . . . . . . . . . . . . . 19-8 40.5 COM-based technologies . . . . . . . . .40-11
19.3 A file-based database application . . . . . 19-9 40.6 Simple COM object interface . . . . . . 40-20
19.4 Architecture combining a client 40.7 Automation object interface . . . . . . . 40-20
dataset and another dataset . . . . . . . 19-12 40.8 ActiveX object interface . . . . . . . . . 40-20
19.5 Multi-tiered database architecture . . . . 19-13 40.9 Delphi ActiveX framework . . . . . . . 40-23
20.1 TDBGrid control . . . . . . . . . . . . . . 20-15 41.1 Type Library editor . . . . . . . . . . . . . 41-3
20.2 TDBGrid control with ObjectView 41.2 Object list pane . . . . . . . . . . . . . . . 41-5
set to False . . . . . . . . . . . . . . . . . 20-23 43.1 Dual interface VTable . . . . . . . . . . 43-14
20.3 TDBGrid control with Expanded 45.1 Mask Edit property page in
set to False . . . . . . . . . . . . . . . . . 20-23 design mode. . . . . . . . . . . . . . . . 45-13
20.4 TDBGrid control with Expanded 46.1 The COM+ Events system . . . . . . . . 46-21
set to True. . . . . . . . . . . . . . . . . . 20-24
xxix
xxx
Chapter
1
Introduction
Chapter1
The Developer’s Guide describes intermediate and advanced development topics, such
as building client/server database applications, creating Internet Web server
applications, and writing custom components. It allows you to build applications
that meet many industry-standard specifications such as SOAP, TCP/IP, COM+, and
ActiveX. Many of the advanced features that support Web development, advanced
XML technologies, and database development require components or wizards that
are not available in all editions of Delphi.
The Developer’s Guide assumes you are familiar with using Delphi and understand
fundamental Delphi programming techniques. For an introduction to Delphi
programming and the integrated development environment (IDE), see the Quick
Start manual or the online Help.
Introduction 1-1
Manual conventions
to determine which packages, DLLs, and other libraries to use when building the
production-quality version of your application.
• Part II, “Developing database applications,” describes how to build database
applications using database tools and components. You can access several types of
databases, including local databases such as Paradox and dBASE, and network
SQL server databases such as InterBase, Oracle, and Sybase. You can choose from
a variety of data access mechanisms, including dbExpress, InterbaseExpress, and
ADO. To implement the more advanced database applications, you need the
features that are not available in all editions.
• Part III, “Writing Internet applications,” describes how to create applications that
are distributed over the Internet. Delphi includes a wide array of tools for writing
Web server applications, including: the Web Broker architecture, with which you
can create cross-platform server applications; WebSnap, with which you can
design Web pages in a GUI environment; support for working with XML
documents; and BizSnap, an architecture for using SOAP-based Web Services. For
lower-level support that underlies much of the messaging in Internet applications,
this section also describes how to work with socket components. The components
that implement many of these features are not available in all editions.
• Part IV, “Developing COM-based applications,” describes how to build
applications that can interoperate with other COM-based API objects on the
system such as Windows Shell extensions or multimedia applications. Delphi
contains components that support the ActiveX, COM+, and a COM-based library
for COM controls that can be used for general-purpose and Web-based
applications. A Type Library editor simplifies the development of COM servers.
Support for COM controls and ActiveX controls is not available in all editions of
Delphi.
Manual conventions
This manual uses the typefaces and symbols described in Table 1.1 to indicate special
text.
Introduction 1-3
1-4 Developer’s Guide
Part
I
Programming with Delphi
Part I
The chapters in “Programming with Delphi” introduce concepts and skills necessary
for creating applications using any edition of Delphi.
• Object Inspector for examining and changing an object’s properties and events.
• Object TreeView for displaying and changing a components’ logical relationships.
• Code editor for writing and editing the underlying program logic.
• Project Manager for managing the files that make up one or more projects.
• Integrated debugger for finding and fixing errors in your code.
• Many other tools such as property editors to change the values for an object’s
property.
• Command-line tools including compilers, linkers, and other utilities.
• Extensive class libraries with many reusable objects. Many of the objects provided
in the class library are accessible in the IDE from the Component palette. By
convention, the names of objects in the class library begin with a T, such as
TStatusBar. Names of objects that begin with a Q are based on the Qt library and
are used for cross-platform applications.
Some tools may not be included in all editions of the product.
A more complete overview of the development environment is presented in the
Quick Start manual included with the product. In addition, the online Help system
provides help on all menus, dialog boxes, and windows.
Designing applications
You can design any kind of 32-bit application—from general-purpose utilities to
sophisticated data access programs or distributed applications.
As you visually design the user interface for your application, the Form Designer
generates the underlying Delphi code to support the application. As you select and
modify the properties of components and forms, the results of those changes appear
automatically in the source code, and vice versa. You can modify the source files
directly with any text editor, including the built-in Code editor. The changes you
make are immediately reflected in the visual environment.
You can create your own components using the Delphi language. Most of the
components provided are written in Delphi. You can add components that you write
to the Component palette and customize the palette for your use by including new
tabs if needed.
You can also design applications that run on both Linux and Windows by using CLX
components. CLX contains a set of classes that, if used instead of those in the VCL,
allows your program to port between Windows and Linux. Refer to Chapter 15,
“Developing cross-platform applications” for details about cross-platform
programming and the differences between the Windows and Linux environments. If
you are using Kylix while developing cross-platform applications, Kylix also
includes a Developer’s Guide that is tailored for the Linux environment. You can refer
to the manual both in the Kylix online Help or the printed manual provided with the
Kylix product.
Chapter 8, “Building applications, components, and libraries,” introduces support
for different types of applications.
Creating projects
All application development revolves around projects. When you create an
application in Delphi you are creating a project. A project is a collection of files that
make up an application. Some of these files are created at design time. Others are
generated automatically when you compile the project source code.
You can view the contents of a project in a project management tool called the Project
Manager. The Project Manager lists, in a hierarchical view, the unit names, the forms
contained in the unit (if there is one), and shows the paths to the files in the project.
Although you can edit many of these files directly, it is often easier and more reliable
to use the visual tools.
At the top of the project hierarchy is a group file. You can combine multiple projects
into a project group. This allows you to open more than one project at a time in the
Project Manager. Project groups let you organize and work on related projects, such
as applications that function together or parts of a multi-tiered application. If you are
only working on one project, you do not need a project group file to create an
application.
Project files, which describe individual projects, files, and associated options, have a
.dpr extension. Project files contain directions for building an application or shared
object. When you add and remove files using the Project Manager, the project file is
updated. You specify project options using a Project Options dialog which has tabs
for various aspects of your project such as forms, application, and compiler. These
project options are stored in the project file with the project.
Units and forms are the basic building blocks of an application. A project can share
any existing form and unit file including those that reside outside the project
directory tree. This includes custom procedures and functions that have been written
as standalone routines.
If you add a shared file to a project, realize that the file is not copied into the current
project directory; it remains in its current location. Adding the shared file to the
current project registers the file name and path in the uses clause of the project file.
Delphi automatically handles this as you add units to a project.
When you compile a project, it does not matter where the files that make up the
project reside. The compiler treats shared files the same as those created by the
project itself.
Editing code
The Code editor is a full-featured ASCII editor. If using the visual programming
environment, a form is automatically displayed as part of a new project. You can start
designing your application interface by placing objects on the form and modifying
how they work in the Object Inspector. But other programming tasks, such as writing
event handlers for objects, must be done by typing the code.
The contents of the form, all of its properties, its components, and their properties
can be viewed and edited as text in the Code editor. You can adjust the generated
code in the Code editor and add more components within the editor by typing code.
As you type code into the editor, the compiler is constantly scanning for changes and
updating the form with the new layout. You can then go back to the form, view and
test the changes you made in the editor, and continue adjusting the form from there.
The code generation and property streaming systems are completely open to
inspection. The source code for everything that is included in your final executable
file—all of the VCL objects, CLX objects, RTL sources, and project files—can be
viewed and edited in the Code editor.
Compiling applications
When you have finished designing your application interface on the form and
writing additional code so it does what you want, you can compile the project from
the IDE or from the command line.
All projects have as a target a single distributable executable file. You can view or test
your application at various stages of development by compiling, building, or
running it:
• When you compile, only units that have changed since the last compile are
recompiled.
• When you build, all units in the project are compiled, regardless of whether they
have changed since the last compile. This technique is useful when you are unsure
of exactly which files have or have not been changed, or when you simply want to
ensure that all files are current and synchronized. It's also important to build when
you've changed global compiler directives to ensure that all code compiles in the
proper state.You can also test the validity of your source code without attempting
to compile the project.
• When you run, you compile and then execute your application. If you modified
the source code since the last compilation, the compiler recompiles those changed
modules and relinks your application.
If you have grouped several projects together, you can compile or build all projects in
a single project group at once. Choose Project|Compile All Projects or Project|Build
All Projects with the project group selected in the Project Manager.
Note To compile a CLX application on Linux, you need Kylix.
Debugging applications
With the integrated debugger, you can find and fix errors in your applications. The
integrated debugger lets you control program execution, monitor variable values and
items in data structures, and modify data values while debugging.
The integrated debugger can track down both runtime errors and logic errors. By
running to specific program locations and viewing the variable values, the functions
on the call stack, and the program output, you can monitor how your program
behaves and find the areas where it is not behaving as designed. The debugger is
described in online Help.
You can also use exception handling to recognize, locate, and deal with errors.
Exceptions are classes, like other classes in Delphi, except, by convention, they begin
with an initial E rather than a T.
Deploying applications
Delphi includes add-on tools to help with application deployment. For example,
InstallShield Express (not available in all editions) helps you to create an installation
package for your application that includes all of the files needed for running a
distributed application. TeamSource software (not available in all editions) is also
available for tracking application updates.
To deploy a CLX application on Linux, you need Kylix.
Note Not all editions have deployment capabilities.
Refer to Chapter 18, “Deploying applications,” for specific information on
deployment.
The VCL and CLX contain many of the same sublibraries. They both include
BaseCLX, DataCLX, NetCLX. The VCL also includes WinCLX while CLX includes
VisualCLX instead. Use the VCL when you want to use native Windows controls,
Windows-specific features, or extend an existing VCL application. Use CLX when
you want to write a cross-platform application or use controls that are available in
CLX applications, such as TLCDNumber. For more information on writing cross-
platform applications, see Chapter 15, “Developing cross-platform applications.”
All classes descend from TObject. TObject introduces methods that implement
fundamental behavior like construction, destruction, and message handling.
Components are a subset of the component library that descend from the class
TComponent. You can place components on a form or data module and manipulate
them at design time. Using the Object Inspector, you can assign property values
without writing code. Most components are either visual or nonvisual, depending on
whether they are visible at runtime. Some components appear on the Component
palette.
Visual components, such as TForm and TSpeedButton, are called controls and descend
from TControl. Controls are used in GUI applications, and appear to the user at
runtime. TControl provides properties that specify the visual attributes of controls,
such as their height and width.
Nonvisual components are used for a variety of tasks. For example, if you are writing
an application that connects to a database, you can place a TDataSource component
on a form to connect a control and a dataset used by the control. This connection is
not visible to the user, so TDataSource is nonvisual. At design time, nonvisual
components are represented by an icon. This allows you to manipulate their
properties and events just as you would a visual control.
Classes that are not components (that is, classes that descend from TObject but not
TComponent) are also used for a variety of tasks. Typically, these classes are used for
accessing system objects (such as a file or the clipboard) or for transient tasks (such as
storing data in a list). You can’t create instances of these classes at design time,
although they are sometimes created by the components that you add in the Form
Designer.
Detailed reference material on all VCL and CLX objects is accessible through online
Help while you are programming. In the Code editor, place the cursor anywhere on
the object and press F1 to display the Help topic. Objects, properties, methods, and
events that are in the VCL are marked “VCL Reference” and those in CLX are
marked “CLX Reference.”
Properties
Properties are characteristics of an object that influence either the visible behavior or
the operations of the object. For example, the Visible property determines whether an
object can be seen in an application interface. Well-designed properties make your
components easier for others to use and easier for you to maintain.
Here are some of the useful features of properties:
• Unlike methods, which are only available at runtime, you can see and change
some properties at design time and get immediate feedback as the components
change in the IDE.
• You can access some properties in the Object Inspector, where you can modify the
values of your object visually. Setting properties at design time is easier than
writing code and makes your code easier to maintain.
• Because the data is encapsulated, it is protected and private to the actual object.
• The calls to get and set the values of properties can be methods, so special
processing can be done that is invisible to the user of the object. For example, data
could reside in a table, but could appear as a normal data member to the
programmer.
• You can implement logic that triggers events or modifies other data during the
access of a property. For example, changing the value of one property may require
you to modify another. You can change the methods created for the property.
• Properties can be virtual.
• A property is not restricted to a single object. Changing one property on one object
can affect several objects. For example, setting the Checked property on a radio
button affects all of the radio buttons in the group.
Methods
A method is a procedure that is always associated with a class. Methods define the
behavior of an object. Class methods can access all the public, protected, and
privateproperties and fields of the class and are commonly referred to as member
functions. See “Controlling access” on page 2-6 of the Component Writer’s Guide.
Although most methods belong to an instance of a class, some methods belong
instead to the class type. These are called class methods.
Events
An event is an action or occurrence detected by a program. Most modern applications
are said to be event-driven, because they are designed to respond to events. In a
program, the programmer has no way of predicting the exact sequence of actions a
user will perform. For example, the user may choose a menu item, click a button, or
mark some text. You can write code to handle the events in which you are interested,
rather than writing code that always executes in the same restricted order.
Regardless of how an event is triggered, VCL objects look to see if you have written
any code to handle that event. If you have, that code is executed; otherwise, the
default event handling behavior takes place.
The kinds of events that can occur can be divided into two main categories:
• User events
• System events
• Internal events
User events
User events are actions that the user initiates. Examples of user events are OnClick
(the user clicked the mouse), OnKeyPress (the user pressed a key on the keyboard),
and OnDblClick (the user double-clicked a mouse button).
System events
System events are events that the operating system fires for you. For example, the
OnTimer event (which the Timer component issues whenever a predefined interval
has elapsed), the OnPaint event (a component or window needs to be redrawn), and
so on. Usually, system events are not directly initiated by a user action.
Internal events
Internal events are events that are generated by the objects in your application. An
example of an internal event is the OnPost event that a dataset generates when your
application tells it to post the current record.
[Objects]
[Objects] [Objects] TGraphicControl [Objects]
[Objects]
Every object (class) inherits from TObject. Objects that can appear in the Form
Designer inherit from TPersistent or TComponent. Controls, which appear to the user
at runtime, inherit from TControl. There are two types of controls, graphic controls,
which inherit from TGraphicControl, and windowed controls, which inherit from
TWinControl or TWidgetControl. A control like TCheckBox inherits all the functionality
of TObject, TPersistent, TComponent, TControl, and TWinControl or TWidgetControl,
and adds specialized capabilities of its own.
The figure shows several important base classes, which are described in the
following table:
The next few sections present a general description of the types of classes that each
branch contains. For a complete overview of the VCL and CLX object hierarchies,
refer to the VCL Object Hierarchy and CLX Object Hierarchy wall charts included
with this product.
TObject branch
The TObject branch includes all VCL and CLX classes that descend from TObject but
not from TPersistent. Much of the powerful capability of the component library is
established by the methods that TObject introduces. TObject encapsulates the
fundamental behavior common to all classes in the component library by introducing
methods that provide:
• The ability to respond when object instances are created or destroyed.
• Class type and instance information on an object, and runtime type information
(RTTI) about its published properties.
• Support for handling messages (VCL applications) or handling notifications (CLX
applications).
TObject is the immediate ancestor of many simple classes. Classes in the TObject
branch have one common, important characteristic: they are transitory. This means
that these classes do not have a method to save the state that they are in prior to
destruction; they are not persistent.
One of the main groups of classes in this branch is the Exception class. This class
provides a large set of built-in exception classes for automatically handling divide-
by-zero errors, file I/O errors, invalid typecasts, and many other exception
conditions.
Another group in the TObject branch is classes that encapsulate data structures, such
as:
• TBits, a class that stores an “array” of Boolean values.
• TList, a linked list class.
• TStack, a class that maintains a last-in first-out array of pointers.
• TQueue, a class that maintains a first-in first-out array of pointers.
Another group in the TObject branch are wrappers for external objects like TPrinter,
which encapsulates a printer interface, and TIniFile, which lets a program read from
or write to an ini file.
TStream is a good example of another type of class in this branch. TStream is the base
class type for stream objects that can read from or write to various kinds of storage
media, such as disk files, dynamic memory, and so on (see “Using streams” on
page 5-2 for information on streams).
See Chapter 5, “Using BaseCLX,” for information on many of the classes in the
TObject branch (as well as on many global routines in the Delphi Runtime Library).
TPersistent branch
The TPersistent branch includes all VCL and CLX classes that descend from
TPersistent but not from TComponent. Persistence determines what gets saved with a
form file or data module and what gets loaded into the form or data module when it
is retrieved from memory.
Because of their persistence, objects from this branch can appear at design time.
However, they can’t exist independently. Rather, they implement properties for
components. Properties are only loaded and saved with a form if they have an
owner. The owner must be some component. TPersistent introduces the GetOwner
method, which lets the Form Designer determine the owner of the object.
Classes in this branch are also the first to include a published section where
properties can be automatically loaded and saved. A DefineProperties method lets
each class indicate how to load and save properties.
Following are some of the classes in the TPersistent branch of the hierarchy:
• Graphics such as: TBrush, TFont, and TPen.
• Classes such as TBitmap and TIcon, which store and display visual images, and
TClipboard, which contains text or graphics that have been cut or copied from an
application.
• String lists, such as TStringList, which represent text or lists of strings that can be
assigned at design time.
• Collections and collection items, which descend from TCollection or
TCollectionItem. These classes maintain indexed collections of specially defined
items that belong to a component. Examples include THeaderSections and
THeaderSection or TListColumns and TListColumn.
TComponent branch
The TComponent branch contains classes that descend from TComponent but not
TControl. Objects in this branch are components that you can manipulate on forms at
design time but which do not appear to the user at runtime. They are persistent
objects that can do the following:
• Appear on the Component palette and be changed on the form.
• Own and manage other components.
• Load and save themselves.
TControl branch
The TControl branch consists of components that descend from TControl but not
TWinControl (TWidgetControl in CLX applications). Classes in this branch are
controls: visual objects that the user can see and manipulate at runtime. All controls
have properties, methods, and events in common that relate to how the control looks,
such as its position, the cursor associated with the control’s window, methods to
paint or move the control, and events to respond to mouse actions. Controls in this
branch, however, can never receive keyboard input.
Whereas TComponent defines behavior for all components, TControl defines behavior
for all visual controls. This includes drawing routines, standard events, and
containership.
TControl introduces many visual properties that all controls inherit. These include the
Caption, Color, Font, and HelpContext or HelpKeyword. While these properties inherited
from TControl, they are only published—and hence appear in the Object Inspector—
for controls to which they are applicable. For example, TImage does not publish the
Color property, since its color is determined by the graphic it displays. TControl also
introduces the Parent property, which specifies another control that visually contains
the control.
Classes in the TControl branch often called graphic controls, because they all descend
from TGraphicControl, which is an immediate descendant of TControl. Although these
controls appear to the user at runtime, graphic controls do not have their own
underlying window or widget. Instead, they use their parent’s window or widget. It
is because of this limitation that graphic controls cant receive keyboard input or act
as a parent to other controls. However, because they do not have their own window
or widget, graphic controls use fewer system resources. For details on many of the
classes in the TControl branch, see “Graphic controls” on page 10-18.
There are two versions of TControl, one for VCL (Windows-only) applications and
one for CLX (cross-platform) applications. Most controls have two versions as well, a
Windows-only version that descends from the Windows-only version of TControl,
and a cross-platform version that descends from the cross-platform version of
TControl. The Windows-only controls use native Windows APIs in their
implementations, while the cross-platform versions sit on top of the Qt cross-
platform widget library.
See Chapter 7, “Working with controls,” for details on how to interact with controls
at runtime.
TWinControl/TWidgetControl branch
Most controls fall into the TWinControl/ TWidgetControl branch. Unlike graphic
controls, controls in this branch have their own associated window or widget.
Because of this, they are sometimes called windowed controls or widget controls.
Windowed controls all descend from TWinControl, which descends from the
windows-only version of TControl. Widget controls all descend from TWidgetControl,
which descends from the CLX version of TControl.
Controls in the TWinControl/TWidgetControl branch:
• Can receive focus while an application is running, which means they can receive
keyboard input from the application user. In comparison, graphic controls can
only display data and respond to the mouse.
• Can be the parent of one or more child controls.
• Have a handle, or unique identifier, that allows them to access the underlying
window or widget.
The TWinControl/TWidgetControl branch includes both controls that are drawn
automatically (such as TEdit, TListBox, TComboBox, TPageControl, and so on) and
custom controls that do not correspond directly to a single underlying Windows
control or widget. Controls in this latter category, which includes classes like
TStringGrid and TDBNavigator, must handle the details of painting themselves.
Because of this, they descend from TCustomControl, which introduces a Canvas
property on which they can paint themselves.
For details on many of the controls in the TWinControl/TWidgetControl branch, see
Chapter 10, “Types of controls.”
What is an object?
A class is a data type that encapsulates data and operations on data in a single unit.
Before object-oriented programming, data and operations (functions) were treated as
separate elements. An object is an instance of a class. That is, it is a value whose type
is a class. The term object is often used more loosely in this documentation and where
the distinction between a class and an instance of the class is not important, the term
“object” may also refer to a class.
You can begin to understand objects if you understand Pascal records or structures in
C. Records are made of up fields that contain data, where each field has its own type.
Records make it easy to refer to a collection of varied data elements.
Objects are also collections of data elements. But objects—unlike records—contain
procedures and functions that operate on their data. These procedures and functions
are called methods.
An object’s data elements are accessed through properties. The properties of many
Delphi objects have values that you can change at design time without writing code.
If you want a property value to change at runtime, you need to write only a small
amount of code.
The combination of data and functionality in a single unit is called encapsulation. In
addition to encapsulation, object-oriented programming is characterized by
inheritance and polymorphism. Inheritance means that objects derive functionality from
other objects (called ancestors); objects can modify their inherited behavior.
Polymorphism means that different objects derived from the same ancestor support
the same method and property interfaces, which often can be called interchangeably.
Form1 represents an instance, or object, of the class type TForm1. You can declare
more than one instance of a class type; you might want to do this, for example, to
create multiple child windows in a Multiple Document Interface (MDI) application.
Each instance maintains its own data, but all instances use the same code to execute
methods.
Although you haven’t added any components to the form or written any code, you
already have a complete GUI application that you can compile and run. All it does is
display a blank form.
Suppose you add a button component to this form and write an OnClick event
handler that changes the color of the form when the user clicks the button. The result
might look like this:
Figure 4.1 A simple form
When the user clicks the button, the form’s color changes to green. This is the event-
handler code for the button’s OnClick event:
procedure TForm1.Button1Click(Sender: TObject);
begin
Form1.Color := clGreen;
end;
Objects can contain other objects as data fields. Each time you place a component on
a form, a new field appears in the form’s type declaration. If you create the
application described above and look at the code in the Code editor, this is what you
see:
unit Unit1;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
Button1: TButton;{ New data field }
procedure Button1Click(Sender: TObject);{ New method declaration }
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);{ The code of the new method }
begin
Form1.Color := clGreen;
end;
end.
TForm1 has a Button1 field that corresponds to the button you added to the form.
TButton is a class type, so Button1 refers to an object.
All the event handlers you write using the IDE are methods of the form object. Each
time you create an event handler, a method is declared in the form object type. The
TForm1 type now contains a new method, the Button1Click procedure, declared in the
TForm1 type declaration. The code that implements the Button1Click method appears
in the implementation part of the unit.
Note that the code in the OnClick event handler for the button hasn’t changed.
Because you wrote the code, you have to update it yourself and correct any
references to the form:
procedure TColorWindow.Button1Click(Sender: TObject);
begin
ColorWindow.Color := clGreen;
end;
• The protected section includes fields and methods with some access restrictions. A
protected member is accessible within the unit where its class is declared and by
any descendant class, regardless of the descendant class’s unit.
• The private section declares fields and methods that have rigorous access
restrictions. A private member is accessible only within the unit where it is
declared. Private members are often used in a class to implement other (public or
published) methods and properties.
• For classes that descend from TPersistent, a published section declares properties
and events that are available at design time. A published member has the same
visibility as a public member, but the compiler generates runtime type information
for published members. Published properties appear in the Object Inspector at
design time.
When you declare a field, property, or method, the new member is added to one of
these four sections, which gives it its visibility: private, protected, public, or
published.
For more information about visibility, see the Delphi Language Guide.
In addition to the fields, properties, and methods you’ve defined, TEmployee inherits
all the methods of TObject. You can place a type declaration like this one in either the
interface or implementation part of a unit, and then create instances of the new class
by calling the Create method that TEmployee inherits from TObject:
var
Employee: TEmployee;
begin
Employee := TEmployee.Create;
end;
The Create method is called a constructor. It allocates memory for a new instance
object and returns a reference to the object.
Components on a form are created and destroyed automatically. However, if you
write your own code to instantiate objects, you are responsible for disposing of them
as well. Every object inherits a Destroy method (called a destructor) from TObject. To
destroy an object, however, you should call the Free method (also inherited from
TObject), because Free checks for a nil reference before calling Destroy. For example,
Employee.Free;
destroys the Employee object and deallocates its memory.
If you do not have class completion, you need to write the code yourself,
completing property declarations and writing the methods.
Given the example above, if you have class completion, read and write specifiers
are added to your declaration, including any supporting fields or methods:
type TMyButton = class(TButton)
property Size: Integer read FSize write SetSize;
procedure DoSomething;
private
FSize: Integer;
procedure SetSize(const Value: Integer);
The following code is also added to the implementation section of the unit.
{ TMyButton }
procedure TMyButton.DoSomething;
begin
end;
procedure TMyButton.SetSize(const Value: Integer);
begin
FSize := Value;
end;
5 Fill in the methods. For example, to make it so the button beeps when you call the
DoSomething method, add the Beep between begin and end.
{ TMyButton }
procedure TMyButton.DoSomething;
begin
Beep;
end;
procedure TMyButton.SetSize(const Value: Integer);
begin
if fsize < > value then
begin
FSize := Value;
DoSomething;
end;
end;
Note that the button also beeps when you call SetSize to change the size of the
button.
For more information about the syntax, language definitions, and rules for classes,
see the Delphi Language Guide.
Using interfaces
Delphi is a single-inheritance language. That means that any class has only a single
direct ancestor. However, there are times you want a new class to inherit properties
and methods from more than one base class so that you can use it sometimes like one
and sometimes like the other. Interfaces let you achieve something like this effect.
An interface is like a class that contains only abstract methods (methods with no
implementation) and a clear definition of their functionality. Interface method
definitions include the number and types of their parameters, their return type, and
their expected behavior. By convention, interfaces are named according to their
behavior and prefaced with a capital I. For example, an IMalloc interface would
allocate, free, and manage memory. Similarly, an IPersist interface could be used as a
general base interface for descendants, each of which defines specific method
prototypes for loading and saving the state of an object to a storage, stream, or file.
An interface has the following syntax:
IMyObject = interface
procedure MyProcedure;
end;
A simple example of an interface declaration is:
type
IEdit = interface
procedure Copy;
procedure Cut;
procedure Paste;
function Undo: Boolean;
end;
Interfaces can never be instantiated. To use an interface, you need to obtain it from an
implementing class.
To implement an interface, define a class that declares the interface in its ancestor list,
indicating that it will implement all of the methods of that interface:
TEditor = class(TInterfacedObject, IEdit)
procedure Copy;
procedure Cut;
procedure Paste;
function Undo: Boolean;
end;
While interfaces define the behavior and signature of their methods, they do not
define the implementations. As long as the class’s implementation conforms to the
interface definition, the interface is fully polymorphic, meaning that accessing and
using the interface is the same for any implementation of it.
For more details about the syntax, language definitions and rules for interfaces, see
the Delphi Language Guide
Later, you could create a class TFilledCircle that implements the IRotate interface to
allow rotation of a pattern that fills the circle without having to add rotation to the
simple circle.
Note For these examples, the immediate base class or an ancestor class is assumed to have
implemented the methods of IInterface, the base interface from which all interfaces
descend. For more information on IInterface, see “Implementing IInterface” on
page 4-14 and “Memory management of interface objects” on page 4-18.
Implementing IInterface
Just as all objects descend, directly or indirectly, from TObject, all interfaces derive
from the IInterface interface. IInterface provides for dynamic querying and lifetime
management of the interface. This is established in the three IInterface methods:
• QueryInterface dynamically queries a given object to obtain interface references for
the interfaces that the object supports.
• _AddRef is a reference counting method that increments the count each time a call
to QueryInterface succeeds. While the reference count is nonzero the object must
remain in memory.
• _Release is used with _AddRef to allow an object to track its own lifetime and
determine when it is safe to delete itself. Once the reference count reaches zero, the
object is freed from memory.
Every class that implements interfaces must implement the three IInterface methods,
as well as all of the methods declared by any other ancestor interfaces, and all of the
methods declared by the interface itself. You can, however, inherit the
implementations of methods of interfaces declared in your class.
By implementing these methods yourself, you can provide an alternative means of
lifetime management, disabling reference-counting. This is a powerful technique that
lets you decouple interfaces from reference-counting.
TInterfacedObject
When defining a class that supports one or more interfaces, it is convenient to use
TInterfacedObject as a base class because it implements the methods of IInterface.
TInterfacedObject class is declared in the System unit as follows:
type
TInterfacedObject = class(TObject, IInterface)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
class function NewInstance: TObject; override;
property RefCount: Integer read FRefCount;
end;
Deriving directly from TInterfacedObject is straightforward. In the following example
declaration, TDerived is a direct descendant of TInterfacedObject and implements a
hypothetical IPaint interface.
type
TDerived = class(TInterfacedObject, IPaint)
ƒ
end;
Because it implements the methods of IInterface, TInterfacedObject automatically
handles reference counting and memory management of interfaced objects. For more
information, see “Memory management of interface objects” on page 4-18, which
also discusses writing your own classes that implement interfaces but that do not
follow the reference-counting mechanism inherent in TInterfacedObject.
For more information about the syntax, implementation details, and language rules
of the implements keyword, see the Delphi Language Guide.
Aggregation
Aggregation offers a modular approach to code reuse through sub-objects that make
up the functionality of a containing object, but that hide the implementation details
from that object. In aggregation, an outer object implements one or more interfaces.
At a minimum, it must implement IInterface. The inner object, or objects, also
implement one or more interfaces. However, only the outer object exposes the
interfaces. That is, the outer object exposes both the interfaces it implements and the
ones that its contained objects implement.
Clients know nothing about inner objects. While the outer object provides access to
the inner object interfaces, their implementation is completely transparent. Therefore,
the outer object class can exchange the inner object class type for any class that
implements the same interface. Correspondingly, the code for the inner object classes
can be shared by other classes that want to use it.
The aggregation model defines explicit rules for implementing IInterface using
delegation. The inner object must implement two versions of the IInterface methods.
• It must implement IInterface on itself, controlling its own reference count. This
implementation of IInterface tracks the relationship between the outer and the
inner object. For example, when an object of its type (the inner object) is created,
the creation succeeds only for a requested interface of type IInterface.
• It also implements a second IInterface for all the interfaces it implements that the
outer object exposes. This second IInterface delegates calls to QueryInterface,
_AddRef, and _Release to the outer object. The outer IInterface is referred to as the
“controlling Unknown.”
Refer to the MS online help for the rules about creating an aggregation. When writing
your own aggregation classes, you can also refer to the implementation details of
IInterface in TComObject. TComObject is a COM class that supports aggregation. If you
are writing COM applications, you can also use TComObject directly as a base class.
Using BaseCLX
Chapter5
5
There are a number of units in the component library that provide the underlying
support for most of the component libraries. These units include the global routines
that make up the runtime library, a number of utility classes such as those that
represent streams and lists, and the classes TObject, TPersistent, and TComponent.
Collectively, these units are called BaseCLX. BaseCLX does not include any of the
components that appear on the Component palette. Rather, the classes and routines
in BaseCLX are used by the components that do appear on the Component palette
and are available for you to use in application code or when you are writing your
own classes.
The following topics discuss many of the classes and routines that make up BaseCLX
and illustrate how to use them.
• Using streams
• Working with files~
• Working with .ini files
• Working with lists
• Working with string lists
• Working with strings~
• Creating drawing spaces
• Printing
• Converting measurements
• Defining custom variants
Note This list of tasks is not exhaustive. The runtime library in BaseCLX contains many
routines to perform tasks that are not mentioned here. These include a host of
mathematical functions (defined in the Math unit), routines for working with date/
time values (defined in the SysUtils and DateUtils units), and routines for working
with Variant values (defined in the Variants unit).
Using streams
Streams are classes that let you read and write data. They provide a common
interface for reading and writing to different media such as memory, strings, sockets,
and BLOB fields in databases. There are several stream classes, which all descend
from TStream. Each stream class is specific to one media type. For example,
TMemoryStream reads from or writes to a memory image; TFileStream reads from or
writes to a file.
Read and Write methods, which can return a byte count that differs from the
requested value. The prototypes for ReadBuffer and WriteBuffer are:
procedure ReadBuffer(var Buffer; Count: Longint);
procedure WriteBuffer(const Buffer; Count: Longint);
These methods call the Read and Write methods to perform the actual reading and
writing.
The Origin parameter indicates how to interpret the Offset parameter. Origin should
be one of the following values:
Seek resets the current stream position, moving it by the indicated offset. Seek returns
the new current position in the stream.
Note When writing cross-platform applications, remember that although the Delphi
language is not case sensitive, the Linux operating system is. When using objects and
routines that work with files, be attentive to the case of file names.
The share mode can be one of the following values with the restrictions listed below:
Note that which share mode you can use depends on which open mode you used.
The following table shows shared modes that are available for each open mode.
The file open and share mode constants are defined in the SysUtils unit.
Manipulating files
Several common file operations are built into the runtime library. The routines for
working with files operate at a high level. For most routines, you specify the name of
the file and the routine makes the necessary calls to the operating system for you. In
some cases, you use file handles instead.
Caution Although the Delphi language is not case sensitive, the Linux operating system is. Be
attentive to case when working with files in cross-platform applications.
Deleting a file
Deleting a file erases the file from the disk and removes the entry from the disk's
directory. There is no corresponding operation to restore a deleted file, so
applications should generally allow users to confirm before deleting files. To delete a
file, pass the name of the file to the DeleteFile function:
DeleteFile(FileName);
DeleteFile returns True if it deleted the file and False if it did not (for example, if the
file did not exist or if it was read-only). DeleteFile erases the file named by FileName
from the disk.
Finding a file
There are three routines used for finding a file: FindFirst, FindNext, and FindClose.
FindFirst searches for the first instance of a filename with a given set of attributes in a
specified directory. FindNext returns the next entry matching the name and attributes
specified in a previous call to FindFirst. FindClose releases memory allocated by
FindFirst. You should always use FindClose to terminate a FindFirst/FindNext
sequence. If you want to know if a file exists, a FileExists function returns True if the
file exists, False otherwise.
The three file find routines take a TSearchRec as one of the parameters. TSearchRec
defines the file information searched for by FindFirst or FindNext. If a file is found, the
fields of the TSearchRec type parameter are modified to describe the found file.
type
TFileName = string;
TSearchRec = record
Time: Integer;//Time contains the time stamp of the file.
Size: Integer;//Size contains the size of the file in bytes.
Attr: Integer;//Attr represents the file attributes of the file.
Name: TFileName;//Name contains the filename and extension.
ExcludeAttr: Integer;
FindHandle: THandle;
FindData: TWin32FindData;//FindData contains additional information such as
//file creation time, last access time, long and short filenames.
end;
On field of TSearchRec that is of particular interest is the Attr field. You can test Attr
against the following attribute constants or values to determine if a file has a specific
attribute:
To test for an attribute, combine the value of the Attr field with the attribute constant
using the and operator. If the file has that attribute, the result will be greater than 0.
For example, if the found file is a hidden file, the following expression will evaluate
to True:
(SearchRec.Attr and faHidden > 0).
Attributes can be combined by OR’ing their constants or values. For example, to
search for read-only and hidden files in addition to normal files, pass the following as
the Attr parameter.
(faReadOnly or faHidden).
The following example illustrates the use of the three file find routines. It uses a label,
a button named Search, and a button named Again on a form. When the user clicks the
Search button, the first file in the specified path is found, and the name and the
number of bytes in the file appear in the label's caption. Each time the user clicks the
Again button, the next matching filename and size is displayed in the label:
var
SearchRec: TSearchRec;
procedure TForm1.SearchClick(Sender: TObject);
begin
FindFirst('c:\Program Files\MyProgram\bin\*.*', faAnyFile, SearchRec);
Label1.Caption := SearchRec.Name + ' is ' + IntToStr(SearchRec.Size) + ' bytes in size';
end;
procedure TForm1.AgainClick(Sender: TObject);
begin
if FindNext(SearchRec) = 0 then
Label1.Caption := SearchRec.Name + ' is ' + IntToStr(SearchRec.Size) + ' bytes in size'
else
FindClose(SearchRec);
end;
Note In cross-platform applications, you should replace any hard-coded pathnames with
the correct pathname for the system or use environment variables (on the
Environment Variables page when you choose Tools|Environment Options) to
represent them.
Renaming a file
To change a file name, use the RenameFile function:
function RenameFile(const OldFileName, NewFileName: string): Boolean;
RenameFile changes a file name, identified by OldFileName, to the name specified by
NewFileName. If the operation succeeds, RenameFile returns True. If it cannot rename
the file (for example, if a file called NewFileName already exists), RenameFile returns
False. For example:
if not RenameFile('OLDNAME.TXT','NEWNAME.TXT') then
ErrorMsg('Error renaming file!');
You cannot rename (move) a file across drives using RenameFile. You would need to
first copy the file and then delete the old one.
Note RenameFile in the runtime library is a wrapper around the Windows API MoveFile
function, so MoveFile will not work across drives either.
As with most of the file manipulating routines, FileAge uses a string filename.
FileGetDate and FileSetDate, however, use a Handle type as a parameter. To get the file
handle either:
• Use the FileOpen or FileCreate function to create a new file or open an existing file.
Both FileOpen and FileCreate return the file handle.
• Instantiate TFileStream to create or open a file. Then use its Handle property. See
“Using file streams” on page 5-6 for more information.
Copying a file
FindingAFile;RenamingAFile;FileDateTimeRoutines;DeletingAFileThe runtime
library does not provide any routines for copying a file. However, if you are writing
Windows-only applications, you can directly call the Windows API CopyFile function
to copy a file. Like most of the runtime library file routines, CopyFile takes a filename
as a parameter, not a file handle. When copying a file, be aware that the file attributes
for the existing file are copied to the new file, but the security attributes are not.
CopyFile is also useful when moving files across drives because neither the RenameFile
function nor the Windows API MoveFile function can rename or move files across
drives. For more information, see the Microsoft Windows online Help.
Each of the Read routines takes three parameters. The first parameter identifies the
section of the ini file. The second parameter identifies the value you want to read,
and the third is a default value in case the section or value doesn't exist in the ini file.
Just as the Read methods gracefully handle the case when a section or value does not
exist, the Write routines create the section and/or value if they do not exist. The
example code creates an ini file the first time it is run that looks like this:
[Form]
Top=100
Left=100
Caption=Default Caption
InitMax=0
On subsequent execution of this application, the ini values are read in when the form
is created and written back out in the OnClose event.
Using TRegistryIniFile
Many 32-bit Windows applications store their information in the system Registry
instead of ini files because the Registry is hierarchical and doesn't suffer from the size
limitations of ini files. If you are accustomed to using ini files and want to move your
configuration information to the Registry instead, you can use the TRegistryIniFile
class. You may also want to use TRegistryIniFile in cross-platform applications if you
want to use the system Registry on Windows and an ini file on Linux. You can write
most of your application so that it uses the TCustomIniFile type. You need only
conditionalize the code that creates an instance of TRegistryIniFile (on Windows) or
TMemIniFile (on Linux) and assigns it to the TCustomIniFile your application uses.
TRegistryIniFile makes Registry entries look like ini file entries. All the methods from
TIniFile and TMemIniFile (read and write) exist in TRegistryIniFile.
When you construct a TRegistryIniFile object, the parameter you pass to the
constructor (corresponding to the filename for an IniFile or TMemIniFile object)
becomes a key value under the user key in the registry. All sections and values
branch from that root. TRegistryIniFile simplifies the Registry interface considerably,
so you may want to use it instead of the TRegistry component even if you aren't
porting existing code or writing a cross-platform application.
Using TRegistry
If you are writing a Windows-only application and are comfortable with the
structure of the system Registry, you can use TRegistry. Unlike TRegistryIniFile, which
uses the same properties and methods of other ini file components, the properties
and methods of TRegistry correspond more directly to the structure of the system
Registry. For example, TRegistry lets you specify both the root key and subkey, while
TRegistryIniFile assumes HKEY_CURRENT_USER as a root key. In addition to
methods for opening, closing, saving, moving, copying, and deleting keys, TRegistry
lets you specify the access level you want to use.
Note TRegistry is not available for cross-platform programming.
Persistent lists
Persistent lists can be saved to a form file. Because of this, they are often used as the
type of a published property on a component. You can add items to the list at design
time, and those items are saved with the object so that they are there when the
component that uses them is loaded into memory at runtime. There are two main
types of persistent lists: string lists and collections.
Examples of string lists include TStringList and THashedStringList. String lists, as the
name implies, contain strings. They provide special support for strings of the form
Name=Value, so that you can look up the value associated with a name. In addition,
most string lists let you associate an object with each string in the list. String lists are
described in more detail in “Working with string lists” on page 5-17.
Collections descend from the class TCollection. Each TCollection descendant is
specialized to manage a specific class of items, where that class descends from
TCollectionItem. Collections support many of the common list operations. All
collections are designed to be the type of a published property, and many can not
function independently of the object that uses them to implement on of its properties.
At design time, the property whose value is a collection can use the collection editor
to let you add, remove, and rearrange items. The collection editor provides a
common user interface for manipulating collections.
This example uses a long-term string list to record the user’s mouse clicks on the
main form, then saves the list to a file before the application terminates.
unit Unit1;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs;
{For CLX apps: uses SysUtils, Variants, Classes, QGraphics, QControls, QForms, QDialogs;}
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
public
{ Public declarations }
ClickList: TStrings;{ declare the field }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
end.
The following functions convert between standard single-byte character strings (or
MBCS strings) and Unicode strings:
• StringToWideChar
• WideCharLenToString
• WideCharLenToStrVar
• WideCharToString
• WideCharToStrVar
In addition, the following functions translate between WideStrings and other
representations:
• UCS4StringToWideString
• WideStringToUCS4String
• VarToWideStr
• VarToWideStrDef
The following routines work directly with WideStrings:
• WideCompareStr
• WideCompareText
• WideSameStr
• WideSameText
• WideSameCaption (CLX applications only)
• WideFmtStr
• WideFormat
• WideLowerCase
• WideUpperCase
Finally, some routines include overloads for working with wide strings:
• UniqueString
• Length
• Trim
• TrimLeft
• TrimRight
Where appropriate, the tables also provide columns indicating whether a routine
satisfies the following criteria.
• Uses case sensitivity: If locale settings are used, it determines the definition of case.
If the routine does not use locale settings, analyses are based upon the ordinal
values of the characters. If the routine is case-insensitive, there is a logical merging
of upper and lower case characters that is determined by a predefined pattern.
• Uses locale settings: Locale settings allow you to customize your application for
specific locales, in particular, for Asian language environments. Most locale
settings consider lowercase characters to be less than the corresponding uppercase
characters. This is in contrast to ASCII order, in which lowercase characters are
greater than uppercase characters. Routines that use the system locale are typically
prefaced with Ansi (that is, AnsiXXX).
• Supports the multi-byte character set (MBCS): MBCSs are used when writing code
for far eastern locales. Multi-byte characters are represented by one or more
character codes, so the length in bytes does not necessarily correspond to the
length of the string. The routines that support MBCS parse one- and multibyte
characters.
ByteType and StrByteType determine whether a particular byte is the lead byte of a
multibyte character. Be careful when using multibyte characters not to truncate a
string by cutting a character in half. Do not pass characters as a parameter to a
function or procedure, since the size of a character cannot be predetermined. Pass,
instead, a pointer to a to a character or string. For more information about MBCS,
see “Enabling application code” on page 17-2.
Situations in which these differences can cause subtle errors are discussed in the
following topics.
String dependencies
Sometimes you need convert a long string to a null-terminated string, for example, if
you are using a function that takes a PChar. If you must cast a string to a PChar, be
aware that you are responsible for the lifetime of the resulting PChar. Because long
strings are reference counted, typecasting a string to a PChar increases the
dependency on the string by one, without actually incrementing the reference count.
When the reference count hits zero, the string will be destroyed, even though there is
an extra dependency on it. The cast PChar will also disappear, while the routine you
passed it to may still be using it. For example:
procedure my_func(x: string);
begin
// do something with x
some_proc(PChar(x)); // cast the string to a PChar
// you now need to guarantee that the string remains
// as long as the some_proc procedure needs to use it
end;
var
i: Integer;
buf: array[0..MAX_SIZE] of char;
S: string;
begin
i := FillBuffer(0, buf, SizeOf(buf));// treats buf as a PChar
S := buf;
//statements
end;
This approach is useful if the size of the buffer is relatively small, since it is allocated
on the stack. It is also safe, since the conversion between an array of char and a string
is automatic. The Length of the string is automatically set to the right value after
assigning buf to the string.
To eliminate the overhead of copying the buffer, you can cast the string to a PChar (if
you are certain that the routine does not need the PChar to remain in memory).
However, synchronizing the length of the string does not happen automatically, as it
does when you assign an array of char to a string. You should reset the string Length
so that it reflects the actual width of the string. If you are using a function that returns
the number of bytes copied, you can do this safely with one line of code:
var
S: string;
begin
SetLength(S, MAX_SIZE;// when casting to a PChar, be sure the string is not empty
SetLength(S, GetModuleFilename( 0, PChar(S), Length(S) ) );
// statements
end;
The TCanvas object defined in the Graphics unit also protects you against common
Windows graphics errors, such as restoring device contexts, pens, brushes, and so on
to the value they had before the drawing operation. TCanvas is used everywhere in
the VCL that drawing is required or possible, and makes drawing graphics both fail-
safe and easy.
See TCanvas in the online Help reference for a complete listing of properties and
methods.
Printing
Like TCanvas, the TPrinter class does not belong to BaseCLX because there are two
separate versions, one for VCL applications (in the Printers unit) and one for CLX
applications (in the QPrinters unit). The VCL TPrinter object encapsulates details of
Windows printers. The CLX TPrinter object is a paint device that paints on a printer.
It generates postscript and sends that to lpr, lp, or another print command. Both
versions of TPrinter, however, are extremely similar.
To get a list of installed and available printers, use the Printers property. Both printer
objects use a TCanvas (which is identical to the form's TCanvas) which means that
anything that can be drawn on a form can be printed as well. To print an image, call
the BeginDoc method followed by whatever canvas graphics you want to print
(including text through the TextOut method) and send the job to the printer by calling
the EndDoc method.
This example uses a button and a memo on a form. When the user clicks the button,
the content of the memo is printed with a 200-pixel border around the page.
To run this example successfully, add Printers to your uses clause.
procedure TForm1.Button1Click(Sender: TObject);
var
r: TRect;
i: Integer;
begin
with Printer do
begin
r := Rect(200,200,(Pagewidth - 200),(PageHeight - 200));
BeginDoc;
Canvas.Brush.Style := bsClear;
for i := 0 to Memo1.Lines.Count do
Canvas.TextOut(200,200 + (i *
Canvas.TextHeight(Memo1.Lines.Strings[i])),
Memo1.Lines.Strings[i]);
Canvas.Brush.Color := clBlack;
Canvas.FrameRect(r);
EndDoc;
end;
end;
For more information on the use of the TPrinter object, look in the online help under
TPrinter.
Converting measurements
The ConvUtils unit declares a general-purpose Convert function that you can use to
convert a measurement from one set of units to another. You can perform
conversions between compatible units of measurement such as feet and inches or
days and weeks. Units that measure the same types of things are said to be in the
same conversion family. The units you’re converting must be in the same conversion
family. For information on doing conversions, see “Performing conversions” on
page 5-33 and refer to Convert in the online Help.
The StdConvs unit defines several conversion families and measurement units
within each family. In addition, you can create customized conversion families and
associated units using the RegisterConversionType and RegisterConversionFamily
functions. For information on extending conversion and conversion units, see
“Adding new measurement types” on page 5-34 and refer to Convert in the online
Help.
Performing conversions
You can use the Convert function to perform both simple and complex conversions. It
includes a simple syntax and a second syntax for performing conversions between
complex measurement types.
The StdConvs unit defines several families of TConvType values. See Conversion
family variables in the online Help for a list of the predefined families of
measurement units and the measurement units in each family.
Declare variables
First, you need to declare variables for the identifiers. The identifiers are used in the
new LongTime conversion family, and the units of measurement that are its
members:
var
cbLongTime: TConvFamily;
ltMonths: TConvType;
ltYears: TConvType;
ltDecades: TConvType;
ltCenturies: TConvType;
ltMillennia: TConvType;
Declare variables
First, declare variables for the identifiers. The identifiers are used in the cbTemperature
conversion family, and the units of measurement are its members:
var
cbTemperature: TConvFamily;
tuCelsius: TConvType;
tuKelvin: TConvType;
tuFahrenheit: TConvType;
Note The units of measurement listed here are a subset of the temperature units actually
registered in the StdConvs unit.
The problem is, this approach requires extra parameters on the conversion function,
which means you can’t simply register the same function with every European
currency. In order to avoid having to write two new conversion functions for every
European currency, you can make use of the same two functions by making them the
members of a class.
Declare variables
Now that you have a conversion class, begin as with any other conversion family, by
declaring identifiers:
var
euEUR: TConvType; { EU euro }
euBEF: TConvType; { Belgian francs }
euDEM: TConvType; { German marks }
euGRD: TConvType; { Greek drachmas }
euESP: TConvType; { Spanish pesetas }
euFFR: TConvType; { French francs }
euIEP: TConvType; { Irish pounds }
euITL: TConvType; { Italian lire }
euLUF: TConvType; { Luxembourg francs }
euNLG: TConvType; { Dutch guilders }
euATS: TConvType; { Austrian schillings }
euPTE: TConvType; { Portuguese escudos }
euFIM: TConvType; { Finnish marks }
cbEuro: TConvFamily;
If your new custom Variant type needs more than 14 bytes to store its data, you can
define a new record type that includes a pointer or object instance. For example, the
VarCmplx unit uses an instance of the class TComplexData to represent the data in a
complex-valued variant. It therefore defines a record type the same size as TVarData
that includes a reference to a TComplexData object:
TComplexVarData = packed record
VType: TVarType;
Reserved1, Reserved2, Reserved3: Word;
VComplex: TComplexData;
Reserved4: LongInt;
end;
Object references are actually pointers (two Words), so this type is the same size as
the TVarData record. As before, a complex custom variant (or its TVarData record),
can be cast to TComplexVarData, and the custom variant type works with the
TVarData record as if it were a TComplexVarData type.
Enabling casting
One of the most important features of the custom variant type for you to implement
is typecasting. The flexibility of variants arises, in part, from their implicit typecasts.
There are two methods for you to implement that enable the custom Variant type to
perform typecasts: Cast, which converts another variant type to your custom variant,
and CastTo, which converts your custom Variant type to another type of Variant.
When implementing either of these methods, it is relatively easy to perform the
logical conversions from the built-in variant types. You must consider, however, the
possibility that the variant to or from which you are casting may be another custom
Variant type. To handle this situation, you can try casting to one of the built-in
Variant types as an intermediate step.
For example, the following Cast method, from the TComplexVariantType class uses the
type Double as an intermediate type:
procedure TComplexVariantType.Cast(var Dest: TVarData; const Source: TVarData);
var
LSource, LTemp: TVarData;
begin
VarDataInit(LSource);
try
VarDataCopyNoInd(LSource, Source);
if VarDataIsStr(LSource) then
TComplexVarData(Dest).VComplex := TComplexData.Create(VarDataToStr(LSource))
else
begin
VarDataInit(LTemp);
try
VarDataCastTo(LTemp, LSource, varDouble);
TComplexVarData(Dest).VComplex := TComplexData.Create(LTemp.VDouble, 0);
finally
VarDataClear(LTemp);
end;
end;
Dest.VType := VarType;
finally
VarDataClear(LSource);
end;
end;
In addition to the use of Double as an intermediate Variant type, there are a few
things to note in this implementation:
• The last step of this method sets the VType member of the returned TVarData
record. This member gives the Variant type code. It is set to the VarType property
of TComplexVariantType, which is the Variant type code assigned to the custom
variant.
• The custom variant’s data (Dest) is typecast from TVarData to the record type that
is actually used to store its data (TComplexVarData). This makes the data easier to
work with.
• The method makes a local copy of the source variant rather than working directly
with its data. This prevents side effects that may affect the source data.
When casting from a complex variant to another type, the CastTo method also uses an
intermediate type of Double (for any destination type other than a string):
procedure TComplexVariantType.CastTo(var Dest: TVarData; const Source: TVarData;
const AVarType: TVarType);
var
LTemp: TVarData;
begin
if Source.VType = VarType then
case AVarType of
varOleStr:
VarDataFromOleStr(Dest, TComplexVarData(Source).VComplex.AsString);
varString:
VarDataFromStr(Dest, TComplexVarData(Source).VComplex.AsString);
else
VarDataInit(LTemp);
try
LTemp.VType := varDouble;
LTemp.VDouble := TComplexVarData(LTemp).VComplex.Real;
VarDataCastTo(Dest, LTemp, AVarType);
finally
VarDataClear(LTemp);
end;
end
else
RaiseCastError;
end;
Note that the CastTo method includes a case where the source variant data does not
have a type code that matches the VarType property. This case only occurs for empty
(unassigned) source variants.
If the custom type does not support the concept of “greater than” or “less than,” only
“equal” or “not equal,” however, it is difficult to implement the Compare method,
because Compare must return crLessThan, crEqual, or crGreaterThan. When the only
valid response is “not equal,” it is impossible to know whether to return crLessThan
or crGreaterThan. Thus, for types that do not support the concept of ordering, you can
override the CompareOp method instead.
CompareOp has three parameters: the value of the left-hand operand, the value of the
right-hand operand, and the comparison operator. Implement this method to
perform the operation and return a boolean that indicates whether the comparison is
True. You can then call the RaiseInvalidOp method when the comparison makes no
sense.
For example, the following CompareOp method comes from the TComplexVariantType
object in the VarCmplx unit. It supports only a test of equality or inequality:
function TComplexVariantType.CompareOp(const Left, Right: TVarData;
const Operator: Integer): Boolean;
begin
Result := False;
if (Left.VType = VarType) and (Right.VType = VarType) then
case Operator of
opCmpEQ:
Result := TComplexVarData(Left).VComplex.Equal(TComplexVarData(Right).VComplex);
opCmpNE:
Result := not TComplexVarData(Left).VComplex.Equal(TComplexVarData(Right).VComplex);
else
RaiseInvalidOp;
end
else
RaiseInvalidOp;
end;
Note that the types of operands that both these implementations support are very
limited. As with binary operations, you can use the RightPromotion and LeftPromotion
methods to limit the cases you must consider by forcing a cast before Compare or
CompareOp is called.
else
RaiseInvalidOp;
end
else
RaiseInvalidOp;
end;
Note that for the logical not operator, which does not make sense for complex values,
this method calls RaiseInvalidOp to cause a runtime error.
You will also need to implement the IsClear method. This way, you can detect any
invalid values or special values that represent “blank” data:
function TComplexVariantType.IsClear(const V: TVarData): Boolean;
begin
Result := (TComplexVarData(V).VComplex = nil) or
TComplexVarData(V).VComplex.IsZero;
end;
Two other standard utilities provided for most custom variants check whether a
given variant is of the custom type and cast an arbitrary variant to the new custom
type. Here is the implementation of those utilities from the VarCmplx unit:
function VarIsComplex(const AValue: Variant): Boolean;
begin
Result := (TVarData(AValue).VType and varTypeMask) = VarComplex;
end;
function VarAsComplex(const AValue: Variant): Variant;
begin
if not VarIsComplex(AValue) then
VarCast(Result, AValue, VarComplex)
else
Result := AValue;
end;
Note that these use standard features of all variants: the VType member of the
TVarData record and the VarCast function, which works because of the methods
implemented in the TCustomVariantType descendant for casting data.
In addition to the standard utilities mentioned above, you can write any number of
utilities specific to your new custom variant type. For example, the VarCmplx unit
defines a large number of functions that implement mathematical operations on
complex-valued variants.
Using TInvokeableVariantType
To provide support for properties and methods, the class you create to enable the
new custom variant type should descend from TInvokeableVariantType instead of
directly from TCustomVariantType.
TInvokeableVariantType defines four methods:
• DoFunction
• DoProcedure
• GetProperty
• SetProperty
that you can implement to support properties and methods on your custom variant
type.
For example, the VarConv unit uses TInvokeableVariantType as the base class for
TConvertVariantType so that the resulting custom variants can support properties.
The following example shows the property getter for these properties:
function TConvertVariantType.GetProperty(var Dest: TVarData;
const V: TVarData; const Name: String): Boolean;
var
LType: TConvType;
begin
// supports...
// 'Value'
// 'Type'
// 'TypeName'
// 'Family'
// 'FamilyName'
// 'As[Type]'
Result := True;
if Name = 'VALUE' then
Variant(Dest) := TConvertVarData(V).VValue
else if Name = 'TYPE' then
Variant(Dest) := TConvertVarData(V).VConvType
else if Name = 'TYPENAME' then
Variant(Dest) := ConvTypeToDescription(TConvertVarData(V).VConvType)
else if Name = 'FAMILY' then
Variant(Dest) := ConvTypeToFamily(TConvertVarData(V).VConvType)
else if Name = 'FAMILYNAME' then
Variant(Dest) := ConvFamilyToDescription(ConvTypeToFamily(TConvertVarData(V).VConvType))
else if System.Copy(Name, 1, 2) = 'AS' then
begin
if DescriptionToConvType(ConvTypeToFamily(TConvertVarData(V).VConvType),
System.Copy(Name, 3, MaxInt), LType) then
VarConvertCreateInto(Variant(Dest), Convert(TConvertVarData(V).VValue,
TConvertVarData(V).VConvType, LType), LType)
else
Result := False;
end
else
Result := False;
end;
The GetProperty method checks the Name parameter to determine what property is
wanted. It then retrieves the information from the TVarData record of the Variant (V),
and returns it as a Variant (Dest). Note that this method supports properties whose
names are dynamically generated at runtime (As[Type]), based on the current value
of the custom variant.
Similarly, the SetProperty, DoFunction, and DoProcedure methods are sufficiently
generic that you can dynamically generate method names, or respond to variable
numbers and types of parameters.
Using TPublishableVariantType
If the custom variant type stores its data using an object instance, then there is an
easier way to implement properties, as long as they are also properties of the object
that represents the variant’s data. If you use TPublishableVariantType as the base class
for your custom variant type, then you need only implement the GetInstance method,
and all the published properties of the object that represents the variant’s data are
automatically implemented for the custom variants.
For example, as was seen in “Storing a custom variant type’s data” on page 5-41,
TComplexVariantType stores the data of a complex-valued variant using an instance of
TComplexData. TComplexData has a number of published properties (Real, Imaginary,
Radius, Theta, and FixedTheta), that provide information about the complex value.
TComplexVariantType descends from TPublishableVariantType, and implements the
GetInstance method to return the TComplexData object (in TypInfo.pas) that is stored
in a complex-valued variant’s TVarData record:
function TComplexVariantType.GetInstance(const V: TVarData): TObject;
begin
Result := TComplexVarData(V).VComplex;
end;
TPublishableVariantType does the rest. It overrides the GetProperty and SetProperty
methods to use the runtime type information (RTTI) of the TComplexData object for
getting and setting property values.
Note For TPublishableVariantType to work, the object that holds the custom variant’s data
must be compiled with RTTI. This means it must be compiled using the {$M+}
compiler directive, or descend from TPersistent.
Most Windows messages (VCL applications) or system events (CLX applications) are
handled by Delphi components. When you want to respond to a message or system
event, you only need to provide an event handler.
Chapter 9, “Developing the application user interface,” provides details on using
forms such as creating modal forms dynamically, passing parameters to forms, and
retrieving data from forms.
Calling methods
Methods are called just like ordinary procedures and functions. For example, visual
controls have a Repaint method that refreshes the control’s image on the screen. You
could call the Repaint method in a draw-grid object like this:
DrawGrid1.Repaint;
As with properties, the scope of a method name determines the need for qualifiers. If
you want, for example, to repaint a form within an event handler of one of the form’s
child controls, you don’t have to prepend the name of the form to the method call:
procedure TForm1.Button1Click(Sender: TObject);
begin
Repaint;
end;
For more information about scope, see “Scope and qualifiers” on page 4-5.
You can add, remove, and rearrange components on the palette, and you can create
component templates and frames that group several components.
For more information about the components on the Component palette, see online
Help. You can press F1 on the Component palette, on the component itself when it is
selected, after it has been dropped onto a form, or anywhere on its name in the Code
editor. If a tab of the Component palette is selected, the Help gives a general
description for all of the components on that tab. Some of the components on the
ActiveX, Servers, and Samples pages, however, are provided as examples only and
are not documented.
For more information on the differences between VCL and CLX applications, see
Chapter 15, “Developing cross-platform applications.”
To start dragging a control manually, call the control’s BeginDrag method. BeginDrag
takes a Boolean parameter called Immediate and, optionally, an integer parameter
called Threshold. If you pass True for Immediate, dragging begins immediately. If you
pass False, dragging does not begin until the user moves the mouse the number of
pixels specified by Threshold. Calling
BeginDrag (False);
allows the control to accept mouse clicks without beginning a drag operation.
You can place other conditions on whether to begin dragging, such as checking
which mouse button the user pressed, by testing the parameters of the mouse-down
event before calling BeginDrag. The following code, for example, handles a mouse-
down event in a file list box by initiating a drag operation only if the left mouse
button was pressed.
procedure TFMForm.FileListBox1MouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then { drag only if left button pressed }
with Sender as TFileListBox do { treat Sender as TFileListBox }
begin
if ItemAtPos(Point(X, Y), True) >= 0 then { is there an item here? }
BeginDrag(False); { if so, drag it }
end;
end;
Dropping items
If a control indicates that it can accept a dragged item, it needs to handle the item
should it be dropped. To handle dropped items, attach an event handler to the
OnDragDrop event of the control accepting the drop. Like the drag-over event, the
drag-and-drop event indicates the source of the dragged item and the coordinates of
the mouse cursor over the accepting control. The latter parameter allows you to
monitor the path an item takes while being dragged; you might, for example, want to
use this information to change the color of components if an item is dropped.
In the following VCL example, a directory tree view, accepting items dragged from a
file list box, responds by moving files to the directory on which they are dropped.
procedure TFMForm.DirectoryOutline1DragDrop(Sender, Source: TObject; X,
Y: Integer);
begin
if Source is TFileListBox then
with DirectoryOutline1 do
ConfirmChange('Move', FileListBox1.FileName, Items[GetItem(X, Y)].FullPath);
end;
Normally, the source parameter of the drag-over and drag-and-drop events is the
control that starts the drag operation. If different kinds of control can start an
operation involving the same kind of data, the source needs to support each kind of
control. When you use a descendant of TDragObject, however, the source is the drag
object itself; if each control creates the same kind of drag object in its OnStartDrag
event, the target needs to handle only one kind of object. The drag-over and drag-
and-drop events can tell if the source is a drag object, as opposed to the control, by
calling the IsDragObject function.
TDragObjectEx descendants (VCL only) are freed automatically whereas descendants
of TDragObject are not. If you have TDragObject descendants that you are not
explicitly freeing, you can change them so they descend from TDragObjectEx instead
to prevent memory loss.
Drag objects let you drag items between a form implemented in the application’s
main executable file and a form implemented using a DLL, or between forms that are
implemented using different DLLs.
OnDockOver occurs on the docking site when the user drags a dockable child over the
control. It is analogous to the OnDragOver event in a drag-and-drop operation. Use it
to signal that the child can be released for docking, by setting the Accept parameter. If
the dockable control is rejected by the OnGetSiteInfo event handler (perhaps because
it is the wrong type of control), OnDockOver does not occur.
property OnDockDrop: TDockDropEvent;
TDockDropEvent = procedure(Sender: TObject; Source: TDragDockObject; X, Y: Integer) of
object;
OnDockDrop occurs on the docking site when the user releases the dockable child
over the control. It is analogous to the OnDragDrop event in a normal drag-and-drop
operation. Use this event to perform any necessary accommodations to accepting the
control as a child control. Access to the child control can be obtained using the
Control property of the TDockObject specified by the Source parameter.
Selecting text
For text in an edit control, before you can send any text to the clipboard, that text
must be selected. Highlighting of selected text is built into the edit components.
When the user selects text, it appears highlighted.
Table 7.1 lists properties commonly used to handle selected text.
For example, the following OnFind event handler searches a Memo component for
the text specified in the FindText property of a find dialog component. If found, the
first occurrence of the text in Memo1 is selected.
procedure TForm1.FindDialog1Find(Sender: TObject);
var
I, J, PosReturn, SkipChars: Integer;
begin
for I := 0 to Memo1.Lines.Count do
begin
PosReturn := Pos(FindDialog1.FindText,Memo1.Lines[I]);
if PosReturn <> 0 then {found!}
begin
Skipchars := 0;
for J := 0 to I - 1 do
Skipchars := Skipchars + Length(Memo1.Lines[J]);
SkipChars := SkipChars + (I*2);
SkipChars := SkipChars + PosReturn - 1;
Memo1.SetFocus;
Memo1.SelStart := SkipChars;
Memo1.SelLength := Length(FindDialog1.FindText);
Break;
end;
end;
end;
To select the entire contents of a rich edit or memo control, call the RichEdit1 control’s
SelectAll method.
For example:
procedure TMainForm.SelectAll(Sender: TObject);
begin
RichEdit1.SelectAll; { select all text in RichEdit }
end;
A form’s PopupMenu property specifies what pop-up menu to display when a user
right-clicks any item on the form. Individual controls also have PopupMenu
properties that can override the form’s property, allowing customized menus for
particular controls.
To add a pop-up menu to a form:
1 Place a pop-up menu component on the form.
2 Use the Menu Designer to define the items for the pop-up menu.
3 Set the PopupMenu property of the form or control that displays the menu to the
name of the pop-up menu component.
4 Attach handlers to the OnClick events of the pop-up menu items.
The following example shows how you might want to add images to a string list.
This is part of a file manager application where, along with a letter for each valid
drive, it adds a bitmap indicating each drive’s type. The OnCreate event handler looks
like this:
procedure TFMForm.FormCreate(Sender: TObject);
var
Drive: Char;
AddedIndex: Integer;
begin
for Drive := 'A' to 'Z' do { iterate through all possible drives }
begin
case GetDriveType(Drive + ':/') of { positive values mean valid drives }
DRIVE_REMOVABLE: { add a tab }
AddedIndex := DriveTabSet.Tabs.AddObject(Drive, Floppy.Picture.Graphic);
DRIVE_FIXED: { add a tab }
AddedIndex := DriveTabSet.Tabs.AddObject(Drive, Fixed.Picture.Graphic);
DRIVE_REMOTE: { add a tab }
AddedIndex := DriveTabSet.Tabs.AddObject(Drive, Network.Picture.Graphic);
end;
if UpCase(Drive) = UpCase(DirectoryOutline.Drive) then { current drive? }
DriveTabSet.TabIndex := AddedIndex; { then make that current tab }
end;
end;
Creating applications
The most common types of applications you can design and build are:
• GUI applications
• Console applications
• Service applications
• Packages and DLLs
GUI applications generally have an easy-to-use interface. Console applications run
from a console window. Service applications are run as Windows services. These
types of applications compile as executables with start-up code.
You can create other types of projects such as packages and DLLs that result in
creating packages or dynamically linkable libraries. These applications produce
executable code without start-up code. Refer to “Creating packages and DLLs” on
page 8-11.
GUI applications
A graphical user interface (GUI) application is one that is designed using graphical
features such as windows, menus, dialog boxes, and features that make the
application easy to use. When you compile a GUI application, an executable file with
start-up code is created. The executable usually provides the basic functionality of
your program, and simple programs often consist of only an executable file. You can
extend the application by calling DLLs, packages, and other support files from the
executable.
The IDE offers two application UI models:
• Single document interface (SDI)
• Multiple document interface (MDI)
In addition to the implementation model of your applications, the design-time
behavior of your project and the runtime behavior of your application can be
manipulated by setting project options in the IDE.
SDI applications
To create a new SDI application:
1 Choose File|New|Other to bring up the New Items dialog.
2 Click on the Projects page and double-click SDI Application.
3 Click OK.
By default, the FormStyle property of your Form object is set to fsNormal, so that the
IDE assumes that all new applications are SDI applications.
MDI applications
To create a new MDI application using a wizard:
1 Choose File|New|Other to bring up the New Items dialog.
2 Click on the Projects page and double-click MDI Application.
3 Click OK.
MDI applications require more planning and are somewhat more complex to design
than SDI applications. MDI applications spawn child windows that reside within the
client window; the main form contains child forms. Set the FormStyle property of the
TForm object to specify whether a form is a child (fsMDIChild) or main form
(fsMDIForm). It is a good idea to define a base class for your child forms and derive
each child form from this class, to avoid having to reset the child form’s properties.
MDI applications often include a Window pop-up on the main menu that has items
such as Cascade and Tile for viewing multiple windows in various styles. When a
child window is minimized, its icon is located in the MDI parent form.
To create a new MDI application without using a wizard:
1 Create the main window form or MDI parent window. Set its FormStyle property
to fsMDIForm.
2 Create a menu for the main window that includes File|Open, File|Save, and
Window which has Cascade, Tile, and Arrange All items.
3 Create the MDI child forms and set their FormStyle properties to fsMDIChild.
Programming templates
Programming templates are commonly used skeleton structures that you can add to
your source code and then fill in. You can also use standard code templates such as
those for array, class, and function declarations, and many statements.
You can also write your own templates for coding structures that you often use. For
example, if you want to use a for loop in your code, you could insert the following
template:
for := to do
begin
end;
To insert a code template in the Code editor, press Ctrl-j and select the template you
want to use. You can also add your own templates to this collection. To add a
template:
1 Choose Tools|Editor Options.
2 Click the Code Insight tab.
3 In the Templates section, click Add.
4 Type a name for the template after Shortcut name, enter a brief description of the
new template, and click OK.
5 Add the template code to the Code text box.
6 Click OK.
Console applications
Console applications are 32-bit programs that run without a graphical interface, in a
console window. These applications typically don’t require much user input and
perform a limited set of functions. Any application that contains:
{$APPTYPE CONSOLE}
in the code opens a console window of its own.
To create a new console application, choose File|New|Other and double-click
Console Application from the New Items dialog box.
The IDE then creates a project file for this type of source file and displays the Code
editor.
Console applications should make sure that no exceptions escape from the program
scope. Otherwise, when the program terminates, the Windows operating system
displays a modal dialog with exception information. For example, your application
should include exception handling such as shown in the following code:
program ConsoleExceptionHandling;
{$APPTYPE CONSOLE}
uses
SysUtils;
procedure ExecuteProgram;
begin
//Program does something
raise Exception.Create('Unforeseen exception');
end;
begin
try
ExecuteProgram;
except
//Handle error condition
WriteIn(‘Program terminated due to an exception’);
//Set ExitCode <> 0 to flag error condition (by convention)
ExitCode := 1;
end;
end.
Users can terminate console applications in one of the following ways:
• Click the Close (X) button.
• Press Ctrl+C.
• Press Ctrl+Break.
• Log off.
Depending on which way the user chooses, the application is terminated forcefully,
the process is not shut down cleanly, and the finalization section isn’t run. Use the
Windows API SetConsoleCtrlHandler function for options for handling these user
termination requests.
Service applications
Service applications take requests from client applications, process those requests,
and return information to the client applications. They typically run in the
background, without much user input. A Web, FTP, or e-mail server is an example of
a service application.
To create an application that implements a Win32 service:
1 Choose File|New|Other, and double-click Service Application in the New Items
dialog box. This adds a global variable named Application to your project, which is
of type TServiceApplication.
2 A Service window appears that corresponds to a service (TService). Implement the
service by setting its properties and event handlers in the Object Inspector.
3 You can add additional services to your service application by choosing File|
New|Other, and double-click Service in the New Items dialog box. Do not add
services to an application that is not a service application. While a TService object
can be added, the application will not generate the requisite events or make the
appropriate Windows calls on behalf of the service.
4 Once your service application is built, you can install its services with the Service
Control Manager (SCM). Other applications can then launch your services by
sending requests to the SCM.
To install your application’s services, run it using the /INSTALL option. The
application installs its services and exits, giving a confirmation message if the
services are successfully installed. You can suppress the confirmation message by
running the service application using the /SILENT option.
To uninstall the services, run it from the command line using the /UNINSTALL
option. (You can also use the /SILENT option to suppress the confirmation message
when uninstalling).
Example This service has a TServerSocket whose port is set to 80. This is the default port for
Web browsers to make requests to Web servers and for Web servers to make
responses to Web browsers. This particular example produces a text document in the
C:\Temp directory called WebLogxxx.log (where xxx is the ThreadID). There should
be only one server listening on any given port, so if you have a Web server, you
should make sure that it is not listening (the service is stopped).
To see the results: open up a Web browser on the local machine and for the address,
type 'localhost' (with no quotes). The browser will time out eventually, but you
should now have a file called Weblogxxx.log in the C:\Temp directory.
1 To create the example, choose File|New|Other and select Service Application
from the New Items dialog box. The Service1 window appears.
2 From the Internet page of the Component palette, add a ServerSocket component
to the service window (Service1).
3 Add a private data member of type TMemoryStream to the TService1 class. The
interface section of your unit should now look like this:
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs,
ScktComp;
type
TService1 = class(TService)
ServerSocket1: TServerSocket;
procedure ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
procedure Service1Execute(Sender: TService);
private
{ Private declarations }
Stream: TMemoryStream; // Add this line here
public
function GetServiceController: PServiceController; override;
{ Public declarations }
end;
var
Service1: TService1;
4 Select ServerSocket1, the component you added in step 1. In the Object Inspector,
double-click the OnClientRead event and add the following event handler:
procedure TService1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
Buffer: PChar;
begin
Buffer := nil;
while Socket.ReceiveLength > 0 do begin
Buffer := AllocMem(Socket.ReceiveLength);
try
Socket.ReceiveBuf(Buffer^, Socket.ReceiveLength);
Stream.Write(Buffer^, StrLen(Buffer));
finally
FreeMem(Buffer);
end;
Stream.Seek(0, soFromBeginning);
Stream.SaveToFile('c:\Temp\Weblog' + IntToStr(ServiceThread.ThreadID) + '.log');
end;
end;
5 Finally, select Service1 by clicking in the window’s client area (but not on the
ServiceSocket). In the Object Inspector, double click the OnExecute event and add
the following event handler:
procedure TService1.Service1Execute(Sender: TService);
begin
Stream := TMemoryStream.Create;
try
ServerSocket1.Port := 80; // WWW port
ServerSocket1.Active := True;
while not Terminated do begin
ServiceThread.ProcessRequests(True);
end;
ServerSocket1.Active := False;
finally
Stream.Free;
end;
end;
When writing your service application, you should be aware of:
• Service threads
• Service name properties
• Debugging service applications
Note Service applications are not available for cross-platform applications.
Service threads
Each service has its own thread (TServiceThread), so if your service application
implements more than one service you must ensure that the implementation of your
services is thread-safe. TServiceThread is designed so that you can implement the
service in the TService OnExecute event handler. The service thread has its own
Execute method which contains a loop that calls the service’s OnStart and OnExecute
handlers before processing new requests.
Because service requests can take a long time to process and the service application
can receive simultaneous requests from more than one client, it is more efficient to
spawn a new thread (derived from TThread, not TServiceThread) for each request and
move the implementation of that service to the new thread’s Execute method. This
allows the service thread’s Execute loop to process new requests continually without
having to wait for the service’s OnExecute handler to finish. The following example
demonstrates.
Example This service beeps every 500 milliseconds from within the standard thread. It handles
pausing, continuing, and stopping of the thread when the service is told to pause,
continue, or stop.
1 Choose File|New|Other and double-click Service Application in the New Items
dialog. The Service1 window appears.
2 In the interface section of your unit, declare a new descendant of TThread named
TSparkyThread. This is the thread that does the work for your service. The
declaration should appear as follows:
TSparkyThread = class(TThread)
public
procedure Execute; override;
end;
3 In the implementation section of your unit, create a global variable for a
TSparkyThread instance:
var
SparkyThread: TSparkyThread;
4 In the implementation section for the TSparkyThread Execute method (the thread
function), add the following code:
procedure TSparkyThread.Execute;
begin
while not Terminated do
begin
Beep;
Sleep(500);
end;
end;
5 Select the Service window (Service1), and double-click the OnStart event in the
Object Inspector. Add the following OnStart event handler:
procedure TService1.Service1Start(Sender: TService; var Started: Boolean);
begin
SparkyThread := TSparkyThread.Create(False);
Started := True;
end;
6 Double-click the OnContinue event in the Object Inspector. Add the following
OnContinue event handler:
procedure TService1.Service1Continue(Sender: TService; var Continued: Boolean);
begin
SparkyThread.Resume;
Continued := True;
end;
7 Double-click the OnPause event in the Object Inspector. Add the following
OnPause event handler:
procedure TService1.Service1Pause(Sender: TService; var Paused: Boolean);
begin
SparkyThread.Suspend;
Paused := True;
end;
8 Finally, double-click the OnStop event in the Object Inspector and add the
following OnStop event handler:
procedure TService1.Service1Stop(Sender: TService; var Stopped: Boolean);
begin
SparkyThread.Terminate;
Stopped := True;
end;
When developing server applications, choosing to spawn a new thread depends on
the nature of the service being provided, the anticipated number of connections, and
the expected number of processors on the computer running the service.
TDependency properties
The TDependency DisplayName is both a display name and the actual name of the
service. It is nearly always the same as the TDependency Name property.
Packages are special DLLs used by Delphi applications, the IDE, or both. There are
two kinds of packages: runtime packages and design-time packages. Runtime
packages provide functionality to a program while that program is running. Design-
time packages extend the functionality of the IDE.
For more information on packages, see Chapter 16, “Working with packages and
components.”
You cannot pass Delphi runtime type information (RTTI) across DLLs or from a DLL
to an executable. If you pass an object from one DLL to another DLL or an executable,
you will not be able to use the is or as operators with the passed object. This is
because the is and as operators need to compare RTTI. If you need to pass objects
from a library, use packages instead, as these can share RTTI. Similarly, you should
use packages instead of DLLs in Web Services because they are rely on Delphi RTTI.
When designing a database application, you must decide which data access
mechanism to use. Each data access mechanism differs in its range of functional
support, the ease of deployment, and the availability of drivers to support different
database servers.
See Part II, “Developing database applications,” for details on how to create both
database client applications and application servers. See “Deploying database
applications” on page 18-6 for deployment information.
Note Not all editions of Delphi include database support.
CGI applications use more system resources on the server, so complex applications
are better created as ISAPI, NSAPI, or Apache DLL applications. When writing cross-
platform applications, you should select CGI stand-alone or Apache Shared Module
(DLL) for Web server development. These are also the same options you see when
creating WebSnap and Web Service applications.
For more information on building Web server applications, see Chapter 33, “Creating
Internet server applications.”
You can also right-click a module to display a context menu for it. The following
table summarizes the context menu options for a data module.
For more information about data modules, see the online Help.
You can define the data source before you drag a field to the form by adding a
TDataSource component to the data module. Set the data source’s DataSet property to
ClientDataSet1. After you drag a field to the form, the edit box appears with its
TDataSource property already set to DataSource1. This method keeps your data access
model cleaner.
Copying an item
Choose Copy to make an exact copy of the selected item and add the copy to your
project. Future changes made to the item in the Object Repository will not be
reflected in your copy, and alterations made to your copy will not affect the original
Object Repository item.
Copy is the only option available for project templates.
Inheriting an item
Choose Inherit to derive a new class from the selected item in the Object Repository
and add the new class to your project. When you recompile your project, any changes
that have been made to the item in the Object Repository will be reflected in your
derived class, in addition to changes you make to the item in your project. Changes
made to your derived class do not affect the shared item in the Object Repository.
Inherit is available for forms, dialog boxes, and data modules, but not for project
templates. It is the only option available for reusing items within the same project.
Using an item
Choose Use when you want the selected item itself to become part of your project.
Changes made to the item in your project will appear in all other projects that have
added the item with the Inherit or Use option. Select this option with caution.
The Use option is available for forms, dialog boxes, and data modules.
Implementing ICustomHelpViewer
The ICustomHelpViewer interface contains three types of methods: methods used to
communicate system-level information (for example, information not related to a
particular Help request) with the Help Manager; methods related to showing Help
based upon a keyword provided by the Help Manager; and methods for displaying a
table of contents.
is called by the Help Manager if more than one viewer can provide Help on a topic.
The viewer is expected to return a TStringList, which is freed by the Help Manager.
The strings in the returned list should map to the pages available for that keyword,
but the characteristics of that mapping can be determined by the viewer. In the case
of the WinHelp viewer on Windows and the HyperHelp viewer on Linux, the string
list always contains exactly one entry. HyperHelp provides its own indexing, and
duplicating that elsewhere would be pointless duplication. In the case of the Man
page viewer (Linux), the string list consists of multiple strings, one for each section of
the manual which contains a page for that keyword.
ICustomHelpViewer.ShowHelp(const HelpString: String)
is called by the Help Manager if it needs the Help viewer to display help for a
particular keyword. This is the last method call in the operation; it is guaranteed to
never be called unless the UnderstandsKeyword method is invoked first.
Implementing IExtendedHelpViewer
ICustomHelpViewer only provides direct support for keyword-based Help. Some
Help systems (especially WinHelp) work by associating numbers (known as context
IDs) with keywords in a fashion which is internal to the Help system and therefore
not visible to the application. Such systems require that the application support
context-based Help in which the application invokes the Help system with that
context, rather than with a string, and the Help system translates the number itself.
Implementing IHelpSelector
IHelpSelector is a companion to ICustomHelpViewer. When more than one registered
viewer claims to provide support for a given keyword, context, or topic, or provides
a table of contents, the Help Manager must choose between them. In the case of
contexts or topics, the Help Manager always selects the first Help viewer that claims
to provide support. In the case of keywords or the table of context, the Help Manager
will, by default, select the first Help viewer. This behavior can be overridden by an
application.
To override the decision of the Help Manager in such cases, an application must
register a class that provides an implementation of the IHelpSelector interface.
IHelpSelector exports two functions: SelectKeyword, and TableOfContents. Both take as
arguments a TStrings containing, one by one, either the possible keyword matches or
the names of the viewers claiming to provide a table of contents. The implementor is
required to return the index (in the TStringList) that represents the selected string; the
TStringList is then freed by the Help Manager.
Note The Help Manager may get confused if the strings are rearranged; it is recommended
that implementors of IHelpSelector refrain from doing this. The Help system only
supports one HelpSelector; when new selectors are registered, any previously
existing selectors are disconnected.
All four functions take the data passed to them and forward it through a data
member of TApplication, which represents the Help system. That data member is
directly accessible through the property HelpSystem.
Using IHelpSystem
IHelpSystem allows an application to do three things:
• Provides path information to the Help Manager.
• Provides a new Help selector.
• Asks the Help Manager to display Help.
Providing path information is important because the Help Manager is platform-
independent and Help system-independent and so is not able to ascertain the
location of Help files. If an application expects Help to be provided by an external
Help system that is not able to ascertain file locations itself, it must provide this
information through the IHelpSystem’s ProvideHelpPath method, which allows the
information to become available through the IHelpManager’s GetHelpPath method.
(This information propagates outward only if the Help viewer asks for it.)
Assigning a Help selector allows the Help Manager to delegate decision-making in
cases where multiple external Help systems can provide Help for the same keyword.
For more information, see the section “Implementing IHelpSelector” on page 8-29.
IHelpSystem exports four procedures and one function to request the Help Manager
to display Help:
• ShowHelp
• ShowContextHelp
• ShowTopicHelp
• ShowTableOfContents
• Hook
Hook is intended entirely for WinHelp compatibility and should not be used in a CLX
application; it allows processing of WM_HELP messages that cannot be mapped
directly onto requests for keyword-based, context-based, or topic-based Help. The
other methods each take two arguments: the keyword, context ID, or topic for which
help is being requested, and the Help file in which it is expected that help can be
found.
In general, unless you are asking for topic-based help, it is equally effective and more
clear to pass help requests to the Help Manager through the InvokeHelp method of
your control.
Setting up forms
TForm is the key class for creating GUI applications. When you open a default project
or create a new project, a form appears on which you can begin your UI design.
Adding forms
To add a form to your project, select File|New|Form. You can see all your project’s
forms and their associated units listed in the Project Manager (View|Project
Manager) and you can display a list of the forms alone by choosing View|Forms.
Linking forms
Adding a form to a project adds a reference to it in the project file, but not to any
other units in the project. Before you can write code that references the new form,
you need to add a reference to it in the referencing forms’ unit files. This is called form
linking.
A common reason to link forms is to provide access to the components in that form.
For example, you’ll often use form linking to enable a form that contains data-aware
components to connect to the data-access components in a data module.
To link a form to another form,
1 Select the form that needs to refer to another.
2 Choose File|Use Unit.
3 Select the name of the form unit for the form to be referenced.
4 Choose OK.
Linking a form to another just means that the uses clauses of one form unit contains a
reference to the other’s form unit, meaning that the linked form and its components
are now in scope for the linking form.
Managing layout
At its simplest, you control the layout of your user interface by where you place
controls in your forms. The placement choices you make are reflected in the control’s
Top, Left, Width, and Height properties. You can change these values at runtime to
change the position and size of the controls in your forms.
Controls have a number of other properties, however, that allow them to
automatically adjust to their contents or containers. This allows you to lay out your
forms so that the pieces fit together into a unified whole.
Two properties affect how a control is positioned and sized in relation to its parent.
The Align property lets you force a control to fit perfectly within its parent along a
specific edge or filling up the entire client area after any other controls have been
aligned. When the parent is resized, the controls aligned to it are automatically
resized and remain positioned so that they fit against a particular edge.
If you want to keep a control positioned relative to a particular edge of its parent, but
don’t want it to necessarily touch that edge or be resized so that it always runs along
the entire edge, you can use the Anchors property.
If you want to ensure that a control does not grow too big or too small, you can use
the Constraints property. Constraints lets you specify the control’s maximum height,
minimum height, maximum width, and minimum width. Set these to limit the size
(in pixels) of the control’s height and width. For example, by setting the MinWidth
and MinHeight of the constraints on a container object, you can ensure that child
objects are always visible.
The value of Constraints propagates through the parent/child hierarchy so that an
object’s size can be constrained because it contains aligned children that have size
constraints. Constraints can also prevent a control from being scaled in a particular
dimension when its ChangeScale method is called.
TControl introduces a protected event, OnConstrainedResize, of type
TConstrainedResizeEvent:
TConstrainedResizeEvent = procedure(Sender: TObject; var MinWidth, MinHeight, MaxWidth,
MaxHeight: Integer) of object;
This event allows you to override the size constraints when an attempt is made to
resize the control. The values of the constraints are passed as var parameters which
can be changed inside the event handler. OnConstrainedResize is published for
container objects (TForm, TScrollBox, TControlBar, and TPanel). In addition,
component writers can use or publish this event for any descendant of TControl.
Controls that have contents that can change in size have an AutoSize property that
causes the control to adjust its size to its font or contained objects.
Using forms
When you create a form from the IDE, Delphi automatically creates the form in
memory by including code in the main entry point of your application function.
Usually, this is the desired behavior and you don’t have to do anything to change it.
That is, the main window persists through the duration of your program, so you
would likely not change the default behavior when creating the form for your main
window.
However, you may not want all your application’s forms in memory for the duration
of the program execution. That is, if you do not want all your application’s dialogs in
memory at once, you can create the dialogs dynamically when you want them to
appear.
Forms can be modal or modeless. Modal forms are forms with which the user must
interact before switching to another form (for example, a dialog box requiring user
input). Modeless forms are windows that are displayed until they are either obscured
by another window or until they are closed or minimized by the user.
private
public
constructor CreateWithButton(whichButton: Integer; Owner: TComponent);
end;
Here’s the manually coded constructor that passes the additional argument,
whichButton. This constructor uses the whichButton parameter to set the Caption
property of a Label control on the form.
constructor CreateWithButton(whichButton: Integer; Owner: TComponent);
begin
inherited Create(Owner);
case whichButton of
1: ResultsLabel.Caption := 'You picked the first button.';
2: ResultsLabel.Caption := 'You picked the second button.';
3: ResultsLabel.Caption := 'You picked the third button.';
end;
end;
When creating an instance of a form with multiple constructors, you can select the
constructor that best suits your purpose. For example, the following OnClick handler
for a button on a form calls creates an instance of TResultsForm that uses the extra
parameter:
procedure TMainForm.SecondButtonClick(Sender: TObject);
var
rf: TResultsForm;
begin
rf := TResultsForm.CreateWithButton(2, self);
rf.ShowModal;
rf.Free;
end;
CurrentColor each time a user selects a new color. The class declaration for the form is
as follows:
TColorForm = class(TForm)
ColorListBox:TListBox;
procedure ColorListBoxClick(Sender: TObject);
private
FColor:String;
public
property CurColor:String read FColor write FColor;
end;
The OnClick event handler for the listbox, ColorListBoxClick, sets the value of the
CurrentColor property each time a new item in the listbox is selected. The event
handler gets the string from the listbox containing the color name and assigns it to
CurrentColor. The CurrentColor property uses the setter function, SetColor, to store the
actual value for the property in the private data member FColor:
procedure TColorForm.ColorListBoxClick(Sender: TObject);
var
Index: Integer;
begin
Index := ColorListBox.ItemIndex;
if Index >= 0 then
CurrentColor := ColorListBox.Items[Index]
else
CurrentColor := '';
end;
Now suppose that another form within the application, called ResultsForm, needs to
find out which color is currently selected on ColorForm whenever a button (called
UpdateButton) on ResultsForm is clicked. The OnClick event handler for UpdateButton
might look like this:
procedure TResultForm.UpdateButtonClick(Sender: TObject);
var
MainColor: String;
begin
if Assigned(ColorForm) then
begin
MainColor := ColorForm.CurrentColor;
{do something with the string MainColor}
end;
end;
The event handler first verifies that ColorForm exists using the Assigned function. It
then gets the value of ColorForm’s CurrentColor property.
Alternatively, if ColorForm had a public function named GetColor, another form could
get the current color without using the CurrentColor property (for example, MainColor
:= ColorForm.GetColor;). In fact, there’s nothing to prevent another form from getting
the ColorForm’s currently selected color by checking the listbox selection directly:
with ColorForm.ColorListBox do
MainColor := Items[ItemIndex];
In the application, the user selects a color from the listbox and presses SelectButton to
save the choice and close the form. The OnClick event handler for SelectButton might
look like this:
procedure TColorForm.SelectButtonClick(Sender: TObject);
begin
with ColorListBox do
if ItemIndex >= 0 then
String(FColor^) := ColorListBox.Items[ItemIndex];
end;
Close;
end;
Notice that the event handler stores the selected color name in the string referenced
by the pointer that was passed to the constructor.
To use ColorForm effectively, the calling form must pass the constructor a pointer to
an existing string. For example, assume ColorForm was instantiated by a form called
ResultsForm in response to a button called UpdateButton on ResultsForm being clicked.
The event handler would look as follows:
procedure TResultsForm.UpdateButtonClick(Sender: TObject);
var
MainColor: String;
begin
GetColor(Addr(MainColor));
if MainColor <> '' then
{do something with the MainColor string}
else
{do something else because no color was picked}
end;
5 In the Palette page edit box, specify the Component palette page where you want
the template to reside. If you specify a page that does not exist, a new page is
created when you save the template.
6 Next to Palette Icon, select a bitmap to represent the template on the palette. The
default proposal will be the bitmap used by the component type of the first
component selected in step 2. To browse for other bitmaps, click Change. The
bitmap you choose must be no larger than 24 pixels by 24 pixels.
7 Click OK.
To remove templates from the Component palette, choose Component|Configure
Palette.
Creating frames
To create an empty frame, choose File|New|Frame, or choose File|New|Other and
double-click Frame. You can then drop components (including other frames) onto
your new frame.
It is usually best—though not necessary—to save frames as part of a project. If you
want to create a project that contains only frames and no forms, choose File|New|
Application, close the new form and unit without saving them, then choose File|
New|Frame and save the project.
Note When you save frames, avoid using the default names Unit1, Project1, and so forth,
since these are likely to cause conflicts when you try to use the frames later.
At design time, you can display any frame included in the current project by
choosing View|Forms and selecting a frame. As with forms and data modules, you
can toggle between the Form Designer and the frame’s form file by right-clicking and
choosing View as Form or View as Text.
If, on the other hand, you put your database components into a frame, later changes
would need to be made in only one place; changes to an original frame automatically
propagate to its embedded descendants when your projects are recompiled. At the
same time, you are free to modify any embedded frame without affecting the original
frame or other embedded descendants of it. The only limitation on modifying
embedded frames is that you cannot add components to them.
Figure 9.1 A frame with data-aware controls and a data source component
In addition to simplifying maintenance, frames can help you to use resources more
efficiently. For example, to use a bitmap or other graphic in an application, you might
load the graphic into the Picture property of a TImage control. If, however, you use
the same graphic repeatedly in one application, each Image object you place on a
form will result in another copy of the graphic being added to the form’s resource
file. (This is true even if you set TImage.Picture once and save the Image control as a
component template.) A better solution is to drop the Image object onto a frame, load
your graphic into it, then use the frame where you want the graphic to appear. This
results in smaller form files and has the added advantage of letting you change the
graphic everywhere it occurs simply by modifying the Image on the original frame.
Sharing frames
You can share a frame with other developers in two ways:
• Add the frame to the Object Repository.
• Distribute the frame’s unit (.pas) and form (.dfm or .xfm) files.
To add a frame to the Repository, open any project that includes the frame, right-
click in the Form Designer, and choose Add to Repository. For more information, see
“Using the Object Repository” on page 8-21.
If you send a frame’s unit and form files to other developers, they can open them and
add them to the Component palette. If the frame has other frames embedded in it,
they will have to open it as part of a project.
What is an action?
As you are developing your application, you can create a set of actions that you can
use on various UI elements. You can organize them into categories that can be
dropped onto a menu as a set (for example, Cut, Copy, and Paste) or one at a time
(for example, Tools|Customize).
An action corresponds to one or more elements of the user interface, such as menu
commands or toolbar buttons. Actions serve two functions: (1) they represent
properties common to the user interface elements, such as whether a control is
enabled or checked, and (2) they respond when a control fires, for example, when the
application user clicks a button or chooses a menu item. You can create a repertoire
of actions that are available to your application through menus, through buttons,
through toolbars, context menus, and so on.
Actions are associated with other components:
• Clients: One or more clients use the action. The client most often represents a
menu item or a button (for example, TToolButton, TSpeedButton, TMenuItem,
TButton, TCheckBox, TRadioButton, and so on). Actions also reside on ActionBand
components such as TActionMainMenuBar and TActionToolBar. When the client
receives a user command (such as a mouse click), it initiates an associated action.
Typically, a client’s OnClick event is associated with its action’s OnExecute event.
• Target: The action acts on the target. The target is usually a control, such as a
memo or a data control. Component writers can create actions specific to the needs
of the controls they design and use, and then package those units to create more
modular applications. Not all actions use a target. For example, the standard help
actions ignore the target and simply launch the help system.
A target can also be a component. For example, data controls change the target to
an associated dataset.
The client influences the action—the action responds when a client fires the action.
The action also influences the client—action properties dynamically update the client
properties. For example, if at runtime an action is disabled (by setting its Enabled
property to False), every client of that action is disabled, appearing grayed.
You can add, delete, and rearrange actions using the Action Manager or the Action
List editor (displayed by double-clicking an action list object, TActionList). These
actions are later connected to client controls.
6 Drag and drop single actions or categories of actions from the Action Manager
editor onto the menu or toolbar you are designing.
To add user-defined actions, create a new TAction by pressing the New Action button
and writing an event handler that defines how it will respond when fired. See “What
happens when an action fires” on page 9-27 for details. Once you’ve defined the
actions, you can drag and drop them onto menus or toolbars like the standard
actions.
5 Use the Action Manager editor to add actions to the Action Manager. You can
associate an image with an action by setting its ImageIndex property to its number
in the image list.
6 Drag and drop single actions or categories of actions from the Action Manager
editor onto the menu or toolbar.
7 For toolbars where you only want to display the icon and no caption: select the
Toolbar action band and double-click its Items property. In the collection editor,
you can select one or more items and set their Caption properties.
8 The images automatically appear on the menu or toolbar.
Note Be careful when customizing a colormap. When you select a new, alternate colormap,
your old settings will be lost. You may want to save a copy of your application if you
want to experiment with alternate settings and possibly return to a previous
customization.
in a particular collection (like the File menu), or all of the items in a given action
band.
The action manager keeps track of the number of times an action has been called by
the user, which is stored in the associated TActionClientItem’s UsageCount field. The
action manager also records the number of times the application has been run, which
we shall call the session number, as well as the session number of the last time an
action was used. The value of UsageCount is used to look up the maximum number of
sessions the item can go unused before it becomes hidden, which is then compared
with the difference between the current session number and the session number of
the last use of the item. If that difference is greater than the number determined in
PrioritySchedule, the item is hidden. The default values of PrioritySchedule are shown
in the table below:
It is possible to disable item hiding at design time. To prevent a specific action (and
all the collections containing it) from becoming hidden, find its TActionClientItem
object and set its UsageCount to -1. To prevent hiding for an entire collection of items,
such as the File menu or even the main menu bar, find the TActionClients object
associated with the collection and set its HideUnused property to False.
4 If you use the predefined actions, the action includes a standard response that
occurs automatically. If creating your own action, you need to write an event
handler that defines how the action responds when fired. See “What happens
when an action fires” on page 9-27 for details.
5 Attach the actions in the action list to the clients that require them:
• Click on the control (such as the button or menu item) on the form or data
module. In the Object Inspector, the Action property lists the available actions.
• Select the one you want.
The standard actions, such as TEditDelete or TDataSetPost, all perform the action you
would expect. You can look at the online reference Help for details on how all of the
standard actions work if you need to. If writing your own actions, you’ll need to
understand more about what happens when the action is fired.
When the user clicks on a client control, Delphi calls the action's Execute method
which defers first to the action list, then the Application object, then the action itself if
neither action list nor Application handles it. To explain this in more detail, Delphi
follows this dispatching sequence when looking for a way to respond to the user
action:
1 If you supply an OnExecute event handler for the action list and it handles the
action, the application proceeds.
The action list’s event handler has a parameter called Handled, that returns False by
default. If the handler is assigned and it handles the event, it returns True, and the
processing sequence ends here. For example:
procedure TForm1.ActionList1ExecuteAction(Action: TBasicAction; var Handled: Boolean);
begin
Handled := True;
end;
If you don’t set Handled to True in the action list event handler, then processing
continues.
2 If you did not write an OnExecute event handler for the action list or if the event
handler doesn’t handle the action, the application’s OnActionExecute event handler
fires. If it handles the action, the application proceeds.
The global Application object receives an OnActionExecute event if any action list in
the application fails to handle an event. Like the action list’s OnExecute event
handler, the OnActionExecute handler has a parameter Handled that returns False
by default. If an event handler is assigned and handles the event, it returns True,
and the processing sequence ends here. For example:
procedure TForm1.ApplicationExecuteAction(Action: TBasicAction; var Handled: Boolean);
begin
{ Prevent execution of all actions in Application }
Handled := True;
end;
3 If the application’s OnExecute event handler doesn’t handle the action, the action’s
OnExecute event handler fires.
You can use built-in actions or create your own action classes that know how to
operate on specific target classes (such as edit controls). When no event handler is
found at any level, the application next tries to find a target on which to execute the
action. When the application locates a target that the action knows how to address, it
invokes the action. See the next section for details on how the application locates a
target that can respond to a predefined action class.
Updating actions
When the application is idle, the OnUpdate event occurs for every action that is linked
to a control or menu item that is showing. This provides an opportunity for
applications to execute centralized code for enabling and disabling, checking and
unchecking, and so on. For example, the following code illustrates the OnUpdate
event handler for an action that is “checked” when the toolbar is visible:
procedure TForm1.Action1Update(Sender: TObject);
begin
{ Indicate whether ToolBar1 is currently visible }
(Sender as TAction).Checked := ToolBar1.Visible;
end;
Warning Do not add time-intensive code to the OnUpdate event handler. This executes
whenever the application is idle. If the event handler takes too much time, it will
adversely affect performance of the entire application.
All of the action objects are described under the action object names in the online
Help.
Registering actions
When you write your own actions, you can register actions to enable them to appear
in the Action List editor. You register and unregister actions by using the global
routines in the Actnlist unit:
procedure RegisterActions(const CategoryName: string; const AClasses: array of
TBasicActionClass; Resource: TComponentClass);
procedure UnRegisterActions(const AClasses: array of TBasicActionClass);
When you call RegisterActions, the actions you register appear in the Action List
editor for use by your applications. You can supply a category name to organize your
actions, as well as a Resource parameter that lets you supply default property values.
For example, the following code registers the standard actions with the IDE:
{ Standard action registration }
RegisterActions('', [TAction], nil);
RegisterActions('Edit', [TEditCut, TEditCopy, TEditPaste], TStandardActions);
RegisterActions('Window', [TWindowClose, TWindowCascade, TWindowTileHorizontal,
TWindowTileVertical, TWindowMinimizeAll, TWindowArrange], TStandardActions);
When you call UnRegisterActions, the actions no longer appear in the Action List
editor.
Keyboard shortcut
Separator bar
For information about hooking up menu items to the code that executes when they
are selected, see “Associating menu events with event handlers” on page 6-6.
MainMenu component
PopupMenu component
A MainMenu component creates a menu that’s attached to the form’s title bar. A
PopupMenu component creates a menu that appears when the user right-clicks in
the form. Pop-up menus do not have a menu bar.
To open the Menu Designer, select a menu component on the form, and then either:
• Double-click the menu component.
or
• From the Properties page of the Object Inspector, select the Items property, and
then either double-click [Menu] in the Value column, or click the ellipsis (...)
button.
The Menu Designer appears, with the first (blank) menu item highlighted in the
Designer, and the Caption property selected in the Object Inspector.
Figure 9.5 Menu Designer for a main menu
Building menus
You add a menu component to your form, or forms, for every menu you want to
include in your application. You can build each menu structure entirely from scratch,
or you can start from one of the predesigned menu templates.
This section discusses the basics of creating a menu at design time. For more
information about menu templates, see “Using menu templates” on page 9-41.
Naming menus
As with all components, when you add a menu component to the form, the form
gives it a default name; for example, MainMenu1. You can give the menu a more
meaningful name that follows language naming conventions.
he menu name is added to the form’s type declaration, and the menu name then
appears in the Component list.
As with the menu component, Delphi adds any menu item names to the form’s type
declaration, and those names then appear in the Component list.
Placeholder for
menu item
When you add a shortcut, it appears next to the menu item caption.
Caution Keyboard shortcuts, unlike accelerator keys, are not checked automatically for
duplicates. You must ensure uniqueness yourself.
Creating submenus
Many application menus contain drop-down lists that appear next to a menu item to
provide additional, related commands. Such lists are indicated by an arrow to the
right of the menu item. Delphi supports as many levels of such submenus as you
want to build into your menu.
Organizing your menu structure this way can save vertical screen space. However,
for optimal design purposes you probably want to use no more than two or three
menu levels in your interface design. (For pop-up menus, you might want to use only
one submenu, if any.)
Figure 9.7 Nested menu structures
Menu item on
the menu bar
Menu item in
a menu list
Nested
menu item
To create a submenu,
1 Select the menu item under which you want to create a submenu.
2 Press Ctrl→ to create the first placeholder, or right-click and choose Create
Submenu.
3 Type a name for the submenu item, or drag an existing menu item into this
placeholder.
4 Press Enter, or ↓, to create the next placeholder.
5 Repeat steps 3 and 4 for each item you want to create in the submenu.
6 Press Esc to return to the previous menu level.
4 Click Add to select the bitmap or bitmap group you want to use in the menu. Click
OK.
5 Set the TMainMenu or TPopupMenu object’s Images property to the ImageList you
just created.
6 Create your menu items and submenu items as described previously.
7 Select the menu item you want to have an image in the Object Inspector and set the
ImageIndex property to the corresponding number of the image in the ImageList
(the default value for ImageIndex is -1, which doesn’t display an image).
Note Use images that are 16 by 16 pixels for proper display in the menu. Although you can
use other sizes for the menu images, alignment and consistency problems may result
when using images greater than or smaller than 16 by 16 pixels.
To close the Menu Designer window and continue editing menu items,
1 Switch focus from the Menu Designer window to the Object Inspector by clicking
the properties page of the Object Inspector.
2 Close the Menu Designer as you normally would.
The focus remains in the Object Inspector, where you can continue editing
properties for the selected menu item. To edit another menu item, select it from the
Component list.
This dialog box lists all the menus associated with the form whose menu is
currently open in the Menu Designer.
2 From the list in the Select Menu dialog box, choose the menu you want to view or
edit.
To use the Object Inspector to switch between menus in a form,
1 Give focus to the form whose menus you want to choose from.
2 From the Component list, select the menu you want to edit.
3 On the Properties page of the Object Inspector, select the Items property for this
menu, and then either click the ellipsis button, or double-click [Menu].
2 Select the menu template you want to insert, then press Enter or choose OK.
This inserts the menu into your form at the cursor’s location. For example, if your
cursor is on a menu item in a list, the menu template is inserted above the selected
item. If your cursor is on the menu bar, the menu template is inserted to the left of
the cursor.
To delete a menu template,
1 Right-click the Menu Designer and choose Delete Templates.
(If there are no templates, the Delete Templates option appears dimmed in the
context menu.)
The Delete Templates dialog box opens, displaying a list of available templates.
2 Select the menu template you want to delete, and press Del.
Delphi deletes the template from the templates list and from your hard disk.
3 In the Template Description edit box, type a brief description for this menu, and
then choose OK.
The Save Template dialog box closes, saving your menu design and returning you
to the Menu Designer window.
Note The description you enter is displayed only in the Save Template, Insert Template,
and Delete Templates dialog boxes. It is not related to the Name or Caption property
for the menu.
Merging menus
For MDI applications, such as the text editor sample application, and for OLE client
applications, your application’s main menu needs to be able to receive menu items
either from another form or from the OLE server object. This is often called merging
menus. Note that OLE technology is limited to Windows applications only and is not
available for use in cross-platform programming.
You prepare menus for merging by specifying values for two properties:
• Menu, a property of the form
• GroupIndex, a property of menu items in the menu
If your application has a default drawing tool, ensure that its button on the toolbar is
pressed when the application starts. To do so, set its GroupIndex property to a value
other than zero and its Down property to True.
Note Using the Flat property of TToolBar requires version 4.70 or later of COMCTL32.DLL.
To force a new row of controls after a specific tool button, Select the tool button that
you want to appear last in the row and set its Wrap property to True.
To turn off the auto-wrap feature of the toolbar, set the toolbar’s Wrapable property to
False.
To assign images to individual bands, select the cool bar and double-click on the
Bands property in the Object Inspector. Then select a band and assign a value to its
ImageIndex property.
Responding to clicks
When the user clicks a control, such as a button on a toolbar, the application
generates an OnClick event which you can respond to with an event handler. Since
OnClick is the default event for buttons, you can generate a skeleton handler for the
event by double-clicking the button at design time. For general information about
events and event handlers, see “Working with events and event handlers” on
page 6-3 and “Generating a handler for a component’s default event” on page 6-4.
Demo programs
For examples of Windows applications that use actions, action lists, menus, and
toolbars, refer to Program Files\Borland\Delphi7\Demos\RichEdit. In addition, the
Application wizard (File|New|Other Projects page), MDI Application, SDI
Application, and Winx Logo Applications can use the action and action list objects.
For examples of cross-platform applications, refer to Demos\CLX.
10
Types of controls
Chapter10
Controls are visual components that help you design your user interface.
This chapter describes the different controls you can use, including text controls,
input controls, buttons, list controls, grouping controls, display controls, grids, value
list editors, and graphic controls. To implement drag and drop in these controls, see
Chapter 7, “Working with controls.”
Text controls
Many applications use text controls to display text to the user. You can use:
• Edit controls, which allow the user to add text.
• Text viewing controls and labels, which do not allow user to add text.
Edit controls
Edit controls display text to the user and allow the user to enter text. The type of
control used for this purpose depends on the size and format of the information.
TEdit and TMaskEdit are simple edit controls that include a single line text edit box in
which you can type information. When the edit box has focus, a blinking insertion
point appears.
You can include text in the edit box by assigning a string value to its Text property.
You control the appearance of the text in the edit box by assigning values to its Font
property. You can specify the typeface, size, color, and attributes of the font. The
attributes affect all of the text in the edit box and cannot be applied to individual
characters.
An edit box can be designed to change its size depending on the size of the font it
contains. You do this by setting the AutoSize property to True. You can limit the
number of characters an edit box can contain by assigning a value to the MaxLength
property.
TMaskEdit is a special edit control that validates the text entered against a mask that
encodes the valid forms the text can take. The mask can also format the text that is
displayed to the user.
TMemo and TRichEdit controls allow the user to add several lines of text.
Edit controls have some of the following important properties:
TTextViewer acts as a simple viewer so that users can read and scroll through
documents. With TTextBrowser, users can also click links to navigate to other
documents and other parts of the same document. Documents visited are stored in a
history list, which can be navigated using the Backward, Forward, and Home methods.
TTextViewer and TTextBrowser are best used to display HTML-based text or to
implement an HTML-based Help system.
TTextBrowser has the same properties as TTextViewer plus Factory. Factory determines
the MIME factory object used to determine file types for embedded images. For
example, you can associate filename extensions—such as .txt, .html, and .xml—with
MIME types and have the factory load this data into the control.
Use the FileName property to add a text file, such as .html, to appear in the control at
runtime.
To see an application using the text browser control, see ..\Delphi7\Demos\Clx\
TextBrowser.
Labels
Labels display text and are usually placed next to other controls.
You place a label on a form when you need to identify or annotate another
component such as an edit box or when you want to include text on a form. The
standard label component, TLabel, is a non-windowed control (widget-based control
in CLX applications), so it cannot receive focus; when you need a label with a
window handle, use TStaticText instead.
Label properties include the following:
• Caption contains the text string for the label.
• Font, Color, and other properties determine the appearance of the label. Each label
can use only one typeface, size, and color.
• FocusControl links the label to another control on the form. If Caption includes an
accelerator key, the control specified by FocusControl receives focus when the user
presses the accelerator key.
• ShowAccelChar determines whether the label can display an underlined accelerator
character. If ShowAccelChar is True, any character preceded by an ampersand (&)
appears underlined and enables an accelerator key.
• Transparent determines whether items under the label (such as graphics) are
visible.
Labels usually display read-only static text that cannot be changed by the application
user. The application can change the text while it is executing by assigning a new
value to the Caption property. To add a text object to a form that a user can scroll or
edit, use TEdit.
Scroll bars
The scroll bar component creates a scroll bar that you can use to scroll the contents of
a window, form, or other control. In the OnScroll event handler, you write code that
determines how the control behaves when the user moves the scroll bar.
The scroll bar component is not used very often, because many visual components
include scroll bars of their own and thus don’t require additional coding. For
Track bars
A track bar can set integer values on a continuous range. It is useful for adjusting
properties like color, volume and brightness. The user moves the slide indicator by
dragging it to a particular location or clicking within the bar.
• Use the Max and Min properties to set the upper and lower range of the track bar.
• Use SelEnd and SelStart to highlight a selection range. See Figure 10.1.
• The Orientation property determines whether the track bar is vertical or horizontal.
• By default, a track bar has one row of ticks along the bottom. Use the TickMarks
property to change their location. To control the intervals between ticks, use the
TickStyle property and SetTick method.
Figure 10.1 Three views of the track bar component
• Position sets a default position for the track bar and tracks the position at runtime.
• By default, users can move one tick up or down by pressing the up and down
arrow keys. Set LineSize to change that increment.
• Set PageSize to determine the number of ticks moved when the user presses Page Up
and Page Down.
Up-down controls
In VCL applications only, an up-down control (TUpDown) consists of a pair of arrow
buttons that allow users to change an integer value in fixed increments. The current
value is given by the Position property; the increment, which defaults to 1, is specified
by the Increment property. Use the Associate property to attach another component
(such as an edit control) to the up-down control.
Splitter controls
A splitter (TSplitter) placed between aligned controls allows users to resize the
controls. Used with components like panels and group boxes, splitters let you divide
a form into several panes with multiple controls on each pane.
After placing a panel or other control on a form, add a splitter with the same
alignment as the control. The last control should be client-aligned, so that it fills up
the remaining space when the others are resized. For example, you can place a panel
at the left edge of a form, set its Alignment to alLeft, then place a splitter (also aligned
to alLeft) to the right of the panel, and finally place another panel (aligned to alLeft or
alClient) to the right of the splitter.
Set MinSize to specify a minimum size the splitter must leave when resizing its
neighboring control. Set Beveled to True to give the splitter’s edge a 3D look.
Action lists let you centralize responses to user commands (actions) for objects such
as menus and buttons that respond to those commands. See “Using action lists” on
page 9-26 for details on how to use action lists with buttons, toolbars, and menus.
You can custom draw buttons individually or application wide. See Chapter 9,
“Developing the application user interface.”
Button controls
Users click button controls with the mouse to initiate actions. Buttons are labeled
with text that represent the action. The text is specified by assigning a string value to
the Caption property. Most buttons can also be selected by pressing a key on the
keyboard as a keyboard shortcut. The shortcut is shown as an underlined letter on
the button.
Users click button controls to initiate actions. You can assign an action to a TButton
component by creating an OnClick event handler for it. Double-clicking a button at
design time takes you to the button’s OnClick event handler in the Code editor.
• Set Cancel to True if you want the button to trigger its OnClick event when the user
presses Esc.
• Set Default to True if you want the Enter key to trigger the button’s OnClick event.
Bitmap buttons
A bitmap button (TBitBtn) is a button control that presents a bitmap image on its face.
• To choose a bitmap for your button, set the Glyph property.
• Use Kind to automatically configure a button with a glyph and default behavior.
• By default, the glyph appears to the left of any text. To move it, use the Layout
property.
• The glyph and text are automatically centered on the button. To move their
position, use the Margin property. Margin determines the number of pixels
between the edge of the image and the edge of the button.
• By default, the image and the text are separated by 4 pixels. Use Spacing to increase
or decrease the distance.
• Bitmap buttons can have 3 states: up, down, and held down. Set the NumGlyphs
property to 3 to show a different bitmap for each state.
Speed buttons
Speed buttons (TSpeedButton), which usually have images on their faces, can function
in groups. They are commonly used with panels to create toolbars.
• To make speed buttons act as a group, give the GroupIndex property of all the
buttons the same nonzero value.
• By default, speed buttons appear in an up (unselected) state. To initially display a
speed button as selected, set the Down property to True.
• If AllowAllUp is True, all of the speed buttons in a group can be unselected. Set
AllowAllUp to False if you want a group of buttons to act like a radio group.
For more information on speed buttons, refer to the section “Adding a toolbar using
a panel component” on page 9-47 and “Organizing actions for toolbars and menus”
on page 9-18.
Check boxes
A check box is a toggle that lets the user select an on or off state. When the choice is
turned on, the check box is checked. Otherwise, the check box is blank. You create
check boxes using TCheckBox.
• Set Checked to True to make the box appear checked by default.
• Set AllowGrayed to True to give the check box three possible states: checked,
unchecked, and grayed.
• The State property indicates whether the check box is checked (cbChecked),
unchecked (cbUnchecked), or grayed (cbGrayed).
Note Check box controls display one of two binary states. The indeterminate state is used
when other settings make it impossible to determine the current value for the check
box.
Radio buttons
Radio buttons, also called option buttons, present a set of mutually exclusive choices.
You can create individual radio buttons using TRadioButton or use the radio group
component (TRadioGroup) to arrange radio buttons into groups automatically. You
can group radio buttons to let the user select one from a limited set of choices. See
“Grouping controls” on page 10-12 for more information.
A selected radio button is displayed as a circle filled in the middle. When not
selected, the radio button shows an empty circle. Assign the value True or False to the
Checked property to change the radio button’s visual state.
Toolbars
Toolbars provide an easy way to arrange and manage visual controls. You can create
a toolbar out of a panel component and speed buttons, or you can use the TToolBar
component, then right-click and choose New Button to add buttons to the toolbar.
The TToolBar component has several advantages: buttons on a toolbar automatically
maintain uniform dimensions and spacing; other controls maintain their relative
position and height; controls can automatically wrap around to start a new row when
they do not fit horizontally; and TToolBar offers display options like transparency,
pop-up borders, and spaces and dividers to group controls.
You can use a centralized set of actions on toolbars and menus, by using action lists or
action bands. See “Using action lists” on page 9-26 for details on how to use action lists
with buttons and toolbars.
Toolbars can also parent other controls such as edit boxes, combo boxes, and so on.
List controls
Lists present the user with a collection of items to select from. Several components
display lists:
Use the nonvisual TStringList and TImageList components to manage sets of strings
and images. For more information about string lists, see “Working with string lists”
on page 5-17.
Combo boxes
A combo box (TComboBox) combines an edit box with a scrollable list. When users
enter data into the control—by typing or selecting from the list—the value of the Text
property changes. If AutoComplete is enabled, the application looks for and displays
the closest match in the list as the user types the data.
Three types of combo boxes are: standard, drop-down (the default), and drop-down
list.
1 Set the Style property to select the type of combo box you need:
• Use csDropDown to create an edit box with a drop-down list. Use
csDropDownList to make the edit box read-only (forcing users to choose from
the list).
• Use csOwnerDrawFixed or csOwnerDrawVariable to create owner-draw combo
boxes that display items graphically or in varying heights. For information on
owner-draw controls, see “Adding graphics to controls” on page 7-13.
• Use csSimple to create a combo box with a fixed list that does not close. Be sure
to resize the combo box so that the list items are displayed (VCL only).
2 Set the DropDownCount property to change the number of items displayed in the
list.
At runtime, CLX combo boxes work differently than VCL combo boxes. With the
CLX combo box, you can add an item to a drop-down list by entering text and
pressing Enter in the edit field of a combo box. You can turn this feature off by setting
InsertMode to ciNone. It is also possible to add empty (no string) items to the list in
the combo box. Also, if you keep pressing the down arrow key, it does not stop at the
last item of the combo box list. It cycles around to the top again.
Tree views
A tree view (TTreeView) displays items in an indented outline. The control provides
buttons that allow nodes to be expanded and collapsed. You can include icons with
items’ text labels and display different icons to indicate whether a node is expanded
or collapsed. You can also include graphics, such as check boxes, that reflect state
information about the items.
• Indent sets the number of pixels horizontally separating items from their parents.
• ShowButtons enables the display of '+' and '–' buttons to indicate whether an item
can be expanded.
• ShowLines enables display of connecting lines to show hierarchical relationships
(VCL only).
• ShowRoot determines whether lines connecting the top-level items are displayed
(VCL only).
To add items to a tree view control at design time, double-click on the control to
display the TreeView Items editor. The items you add become the value of the Items
property. You can change the items at runtime by using the methods of the Items
property, which is an object of type TTreeNodes. TTreeNodes has methods for adding,
deleting, and navigating the items in the tree view.
Tree views can display columns and subitems similar to list views in vsReport mode.
List views
List views, created using TListView, display lists in various formats. Use the
ViewStyle property to choose the kind of list you want:
• vsIcon and vsSmallIcon display each item as an icon with a label. Users can drag
items within the list view window (VCL only).
• vsList displays items as labeled icons that cannot be dragged.
• vsReport displays items on separate lines with information arranged in columns.
The leftmost column contains a small icon and label, and subsequent columns
contain subitems specified by the application. Use the ShowColumnHeaders
property to display headers for the columns.
Grouping controls
A graphical interface is easier to use when related controls and information are
presented in groups. Components for grouping components include:
Panels
The TPanel component provides a generic container for other controls. Panels are
typically used to visually group components together on a form. Panels can be
aligned with the form to maintain the same relative position when the form is
resized. The BorderWidth property determines the width, in pixels, of the border
around a panel.
You can also place other controls onto a panel and use the Align property to ensure
proper positioning of all the controls in the group on the form. You can make a panel
alTop aligned so that its position will remain in place even if the form is resized.
The look of the panel can be changed to a raised or lowered look by using the
BevelOuter and BevelInner properties. You can vary the values of these properties to
create different visual 3-D effects. Note that if you merely want a raised or lowered
bevel, you can use the less resource intensive TBevel control instead.
You can also use one or more panels to build various status bars or information
display areas.
Scroll boxes
Scroll boxes (TScrollBox) create scrolling areas within a form. Applications often need to
display more information than will fit in a particular area. Some controls—such as
list boxes, memos, and forms themselves—can automatically scroll their contents.
Another use of scroll boxes is to create multiple scrolling areas (views) in a window.
Views are common in commercial word-processor, spreadsheet, and project
management applications. Scroll boxes give you the additional flexibility to define
arbitrary scrolling subregions of a form.
Like panels and group boxes, scroll boxes contain other controls, such as TButton and
TCheckBox objects. But a scroll box is normally invisible. If the controls in the scroll
box cannot fit in its visible area, the scroll box automatically displays scroll bars.
Another use of a scroll box is to restrict scrolling in areas of a window, such as a
toolbar or status bar (TPanel components). To prevent a toolbar and status bar from
scrolling, hide the scroll bars, and then position a scroll box in the client area of the
window between the toolbar and status bar. The scroll bars associated with the scroll
box will appear to belong to the window, but will scroll only the area inside the scroll
box.
Tab controls
The tab control component (TTabControl) creates a set of tabs that look like notebook
dividers. You can create tabs by editing the Tabs property in the Object Inspector;
each string in Tabs represents a tab. The tab control is a single panel with one set of
components on it. To change the appearance of the control when the tabs are clicked,
you need to write an OnChange event handler. To create a multipage dialog box, use a
page control instead.
Page controls
The page control component (TPageControl) is a page set suitable for multipage
dialog boxes. A page control displays multiple overlapping pages that are TTabSheet
objects. A page is selected in the user interface by clicking a tab on top of the control.
To create a new page in a page control at design time, right-click the control and
choose New Page. At runtime, you add new pages by creating the object for the page
and setting its PageControl property:
NewTabSheet = TTabSheet.Create(PageControl1);
NewTabSheet.PageControl := PageControl1;
To access the active page, use the ActivePage property. To change the active page, you
can set either the ActivePage or the ActivePageIndex property.
Header controls
A header control (THeaderControl) is a is a set of column headers that the user can
select or resize at runtime. Edit the control’s Sections property to add or modify
headers. You can place the header sections above columns or fields. For example,
header sections might be placed over a list box (TListBox).
Display controls
There are many ways to provide users with information about the state of an
application. For example, some components—including TForm—have a Caption
property that can be set at runtime. You can also create dialog boxes to display
messages. In addition, the following components are especially useful for providing
visual feedback at runtime to identify the object.
Status bars
Although you can use a panel to make a status bar, it is simpler to use the TStatusBar
component. By default, the status bar’s Align property is set to alBottom, which takes
care of both position and size.
If you only want to display one text string at a time in the status bar, set its
SimplePanel property to True and use the SimpleText property to control the text
displayed in the status bar.
You can also divide a status bar into several text areas, called panels. To create
panels, edit the Panels property in the Object Inspector, setting each panel’s Width,
Alignment, and Text properties from the Panels editor. Each panel’s Text property
contains the text displayed in the panel.
Progress bars
When your application performs a time-consuming operation, you can use a
progress bar (TProgressBar) to show how much of the task is completed. A progress
bar displays a dotted line that grows from left to right.
Figure 10.2 A progress bar
The Position property tracks the length of the dotted line. Max and Min determine the
range of Position. To make the line grow, increment Position by calling the StepBy or
StepIt method. The Step property determines the increment used by StepIt.
Grids
Grids display information in rows and columns. If you’re writing a database
application, use the TDBGrid or TDBCtrlGrid component described in Chapter 20,
“Using data controls.” Otherwise, use a standard draw grid or string grid.
Draw grids
A draw grid (TDrawGrid) displays arbitrary data in tabular format. Write an
OnDrawCell event handler to fill in the cells of the grid.
• The CellRect method returns the screen coordinates of a specified cell, while the
MouseToCell method returns the column and row of the cell at specified screen
coordinates. The Selection property indicates the boundaries of the currently
selected cells.
• The TopRow property determines which row is currently at the top of the grid. The
LeftCol property determines the first visible column on the left. VisibleColCount and
VisibleRowCount are the number of columns and rows visible in the grid.
• You can change the width or height of a column or row with the ColWidths and
RowHeights properties. Set the width of the grid lines with the GridLineWidth
property. Add scroll bars to the grid with the ScrollBars property.
• You can choose to have fixed or non-scrolling columns and rows with the
FixedCols and FixedRows properties. Assign a color to the fixed columns and rows
with the FixedColor property.
• The Options, DefaultColWidth, and DefaultRowHeight properties also affect the
appearance and behavior of the grid.
String grids
The string grid component is a descendant of TDrawGrid that adds specialized
functionality to simplify the display of strings. The Cells property lists the strings for
each cell in the grid; the Objects property lists objects associated with each string. All
the strings and associated objects for a particular column or row can be accessed
through the Cols or Rows property.
Graphic controls
The following components make it easy to incorporate graphics into an application.
Notice that these include common paint routines (Repaint, Invalidate, and so on) that
never need to receive focus.
To create a graphic control, see Chapter 10, “Creating a graphic control,” in the
Component Writer’s Guide.
Images
The image component (TImage) displays a graphical image, like a bitmap, icon, or
metafile. The Picture property determines the graphic to be displayed. Use Center,
AutoSize, Stretch, and Transparent to set display options. For more information, see
“Overview of graphics programming” on page 12-1.
Shapes
The shape component displays a geometric shape. It is a nonwindowed control (a
widget-based control in CLX applications) and therefore, cannot receive user input.
The Shape property determines which shape the control assumes. To change the
shape’s color or add a pattern, use the Brush property, which holds a TBrush object.
How the shape is painted depends on the Color and Style properties of TBrush.
Bevels
The bevel component (TBevel) is a line that can appear raised or lowered. Some
components, such as TPanel, have built-in properties to create beveled borders. When
such properties are unavailable, use TBevel to create beveled outlines, boxes, or
frames.
Paint boxes
The paint box (TPaintBox) allows your application to draw on a form. Write an
OnPaint event handler to render an image directly on the paint box's Canvas.
Drawing outside the boundaries of the paint box is prevented. For more information,
see “Overview of graphics programming” on page 12-1.
Animation control
The animation component is a window that silently displays an Audio Video
Interleaved (AVI) clip (VCL applications) or a GIF clip (CLX applications). An AVI
clip is a series of bitmap frames, like a movie. Although AVI clips can have sound,
animation controls work only with silent AVI clips. The files you use must be either
uncompressed AVI files or AVI clips compressed using run-length encoding (RLE).
Following are some of the properties of an animation component:
• ResHandle is the Windows handle for the module that contains the AVI clip as a
resource. Set ResHandle at runtime to the instance handle or module handle of the
module that includes the animation resource. After setting ResHandle, set the
ResID or ResName property to specify which resource in the indicated module is
the AVI clip that should be displayed by the animation control.
• Set AutoSize to True to have the animation control adjust its size to the size of the
frames in the AVI clip.
• StartFrame and StopFrame specify in which frames to start and stop the clip.
• Set CommonAVI to display one of the common Windows AVI clips provided in
Shell32.DLL.
• Specify when to start and interrupt the animation by setting the Active property to
True and False, respectively, and how many repetitions to play by setting the
Repetitions property.
• The Timers property lets you display the frames using a timer. This is useful for
synchronizing the animation sequence with other actions, such as playing a sound
track.
ModelMaker fundamentals
ModelMaker simplifies source code generation and maintenance. To use it
effectively, you must first understand how ModelMaker works, and how it relates to
traditional IDE-based projects.
ModelMaker models
Although ModelMaker ultimately produces source code, it does not manipulate
source code directly for most of its operations. Instead, ModelMaker operates on its
own file sets, known as models. When you are working on a project in ModelMaker,
you are manipulating the structure of the model. ModelMaker converts its model to
source code periodically, either automatically or in response to a user commands.
You use the generated source code to build applications and packages.
Models are not merely a compressed representation of the source code. They can also
contain external information (such as UML diagram data) which isn’t stored in the
generated unit files. Also, models can manage an arbitrary number of source code
units. More often than not, a model doesn’t contain an entire project or package, just
a subset of its units.
Note Since models contain unique information not found in unit code, it is important to
include your model file sets in your storage and version control processes along with
your unit files.
For more information on models and model files, see the ModelMaker User’s Guide.
When you use ModelMaker with the IDE, keep in mind that the IDE cannot change
ModelMaker model files. Any source code changes you make with the IDE editors
will not propagate into the model automatically. Your changes will be destroyed the
next time ModelMaker updates the generated unit code. If you need to make changes
when a model exists, use ModelMaker instead of the IDE to guarantee model-source
synchronization. If that isn’t possible, be sure to reimport the unit into the model
when you’ve finished your changes.
Creating models
There are many ways to create models in ModelMaker. If you are creating entirely
new code, you can start with a new model and design your code (aside from forms)
using ModelMaker. To create a new model, select File|New or click the New model
button on the ModelMaker toolbar. (The New model button is the leftmost button on
the toolbar.)
Figure 11.1 Part of the ModelMaker toolbar
New model Import source
More often, you will need to make a model from units created outside ModelMaker.
There are two buttons on the toolbar which allow you to import source code into
your model. One button (the second from the left) imports the source file into a new
model, the other (fifth from the left) uses the current model. Once you have imported
your source code, you can use any of ModelMaker’s tools on your model.
Collections
pane
Editors
pane
Methods
pane
ModelMaker is always divided into three panes. The collections pane (the top-left
pane by default) can display the Classes view, the Units view, or the Diagrams view.
The members pane (bottom-left by default) always displays the Members view. The
editors pane (rightmost by default) can display the Implementation Editor, Unit
Code Editor, Diagram Editor, Macros view, Patterns view, Unit Difference view,
Documentation view, or Events view.
You can choose particular views through items in the Views menu, or through
buttons on the toolbar. You can also change the view layout using toolbar buttons.
Collections pane
The collections pane displays collections of items used in ModelMaker models.
Models often contain multiple classes, units, and diagrams. The collections pane
shows logical groups of these items.
Classes view
The Classes view displays a hierarchical listing of all the classes and interfaces in
your model. It also shows the ancestry for classes and interfaces in the model.
Ancestors contained in the current model have icons surrounded by solid lines.
Those not contained in the model have icons bordered by dashed lines.
Figure 11.3 The Classes view
Note If both an object and its ancestor are not included in a model, then the hierarchy
between them might not be complete.
You can fold the hierarchies to hide branches you’re not interested in. You can also
add new classes and interfaces to your model through the Classes view.
Units view
The Units view displays a tree or list of all the units contained in the project. The
view also shows all the objects, interfaces, and events contained in each unit.
Figure 11.4 The Units view
You can use buttons in the Units view (above the tree) to change the contents of the
view, add or edit units, or change code generation behavior.
Diagrams view
The Diagrams view shows a list of all the UML-style diagrams contained in the
model. You can modify these diagrams using the Diagram Editor view of the editors
pane.
Figure 11.5 The Diagrams view
Diagrams are often used as a class design tool. You can add properties, methods, and
events to a diagram, which changes your model and, eventually, your source code.
After the diagram design phase, you can use tools in the editors pane (such as the
Implementation view) to fill in the implementations for your new class. You can also
create diagrams for classes designed without UML or ModelMaker.
You can use buttons in the Diagrams view to create many different types of UML-
style diagrams, including:
• Class diagrams
• Sequence diagrams
• Collaboration diagrams
• Use case diagrams
• Robustness diagrams
• Statechart (or state) diagrams
• Activity diagrams
• Implementation diagrams
• Mind map diagrams
• Unit dependency diagrams
Other buttons let you clone or delete an existing diagram.
Note Classes and diagrams are distinct entities in ModelMaker models. The existence of a
class does not infer the existence of a diagram for that class; you must create
diagrams explicitly. Also, deleting a diagram will not delete any classes or interfaces
from your model, or from the source code generated by your model.
Members pane
The members pane contains the Members view. It displays members (fields,
properties, methods, or events) for the class or interface currently selected in the
Class view. Selecting items in the Members view can display their contents in the
editors pane if an appropriate editor is displayed there.
Figure 11.6 The Members view
You can use the Members view to change member names, or to display members in
the Implementation view for editing. You can use some of the buttons in the
Members view to add fields, properties, methods, and events. Other buttons let you
select which members are displayed in the view based on visibility or member type.
Editors pane
The editors pane contains views that you can use to make changes to method
implementations, unit source code, UML diagrams, macros, and design patterns.
You can also use the editors pane to view differences between one of your model’s
unit files before and after changes have been made to the model.
Implementation Editor
The Implementation Editor lets you edit method source code in your model without
using the IDE. After you add methods to your classes and interfaces using the
Members view or UML diagrams, you can write your implementations into your
model using the Implementation Editor. These implementations will appear in
generated source code.
The Implementation Editor can help you modify the method’s interface, add a one-
line description to generated documentation, add local variables or methods, and
edit the method source itself. It includes views which show the local variables and
methods, as well as a view of the final method source code.
Many features of a unit file, such as class implementations, are managed using
separate editors. Such content is denoted in the template by tag lines, which start
with MMWIN. These tag lines must be left alone in the Unit Code Editor (although
they can be moved within the file if they are left intact). You can edit non-tag lines,
such as unit uses clauses and non-class methods, in the Unit Code Editor.
Diagram Editor
The Diagram Editor is used to modify UML diagrams created from the Diagrams
view of the collections pane. It offers a rich collection of tools for making visual
changes to your UML diagrams. You can also expand your ModelMaker model by
adding features (such as properties and methods) to your UML diagrams. Model
changes you make through diagrams will propagate to your source code.
Figure 11.9 The Diagram Editor
ModelMaker diagrams can be exported to formats such as image and XML/XMI. For
more information about using UML diagrams in ModelMaker, see the ModelMaker
User’s Guide.
Other Editors
ModelMaker includes several other editor views, including:
• the Macros view, which helps you manage and manipulate ModelMaker macros
• the Patterns view, which enables you to define code elements using ModelMaker’s
design pattern tools
• the Unit Difference view, which lets you track differences between unit files in
different sources (including ModelMaker models and saved unit files)
• the Documentation view, which you can use to write documentation into your
model for units, classes, and class members
• the Events view, which you can use to manage the events in your project
The ModelMaker User’s Guide contains in-depth information about these other editor
views.
If you use the TImage control to display a graphical image on a form, the painting and
refreshing of the graphic contained in the TImage is handled automatically. The
Picture property specifies the actual bitmap, drawing, or other graphic object that
TImage displays. You can also set the Proportional property to ensure that the image
can be fully displayed in the image control without any distortion. Drawing on a
TImage creates a persistent image. Consequently, you do not need to do anything to
redraw the contained image. In contrast, TPaintBox’s canvas maps directly onto the
screen device (VCL applications) or the painter (CLX applications), so that anything
drawn to the PaintBox’s canvas is transitory. This is true of nearly all controls,
including the form itself. Therefore, if you draw or paint on a TPaintBox in its
constructor, you will need to add that code to your OnPaint event handler in order
for the image to be repainted each time the client area is invalidated.
These properties are described in more detail in “Using the properties of the Canvas
object” on page 12-5.
Table 12.3 is a list of several methods you can use:
These methods are described in more detail in “Using Canvas methods to draw
graphic objects” on page 12-10.
Using pens
The Pen property of a canvas controls the way lines appear, including lines drawn as
the outlines of shapes. Drawing a straight line is really just changing a group of pixels
that lie between two points.
The pen itself has four properties you can change:
• Color property changes the pen color.
• Width property changes the pen width.
• Style property changes the pen style.
• Mode property changes the pen mode.
The values of these properties determine how the pen changes the pixels in the line.
By default, every pen starts out black, with a width of 1 pixel, a solid style, and a
mode called copy that overwrites anything already on the canvas.
You can use TPenRecall for quick saving off and restoring the properties of pens.
To create one click-event handler for six pen-style buttons on a pen’s toolbar, do the
following:
1 Select all six pen-style buttons and select the Object Inspector|Events|OnClick
event and in the Handler column, type SetPenStyle.
The Code editor generates an empty click-event handler called SetPenStyle and
attaches it to the OnClick events of all six buttons.
2 Fill in the click-event handler by setting the pen’s style depending on the value of
Sender, which is the control that sent the click event:
procedure TForm1.SetPenStyle(Sender: TObject);
begin
with Canvas.Pen do
begin
if Sender = SolidPen then Style := psSolid
else if Sender = DashPen then Style := psDash
else if Sender = DotPen then Style := psDot
else if Sender = DashDotPen then Style := psDashDot
else if Sender = DashDotDotPen then Style := psDashDotDot
else if Sender = ClearPen then Style := psClear;
end;
end;
Using brushes
The Brush property of a canvas controls the way you fill areas, including the interior
of shapes. Filling an area with a brush is a way of changing a large number of
adjacent pixels in a specified way.
The brush has three properties you can manipulate:
• Color property changes the fill color.
• Style property changes the brush style.
• Bitmap property uses a bitmap as a brush pattern.
The values of these properties determine the way the canvas fills shapes or other
areas. By default, every brush starts out white, with a solid style and no pattern
bitmap.
You can use TBrushRecall for quick saving off and restoring the properties of brushes.
Drawing lines
To draw a straight line on a canvas, use the LineTo method of the canvas.
LineTo draws a line from the current pen position to the point you specify and makes
the endpoint of the line the current position. The canvas draws the line using its pen.
For example, the following method draws crossed diagonal lines across a form
whenever the form is painted:
procedure TForm1.FormPaint(Sender: TObject);
begin
with Canvas do
begin
MoveTo(0, 0);
LineTo(ClientWidth, ClientHeight);
MoveTo(0, ClientHeight);
LineTo(ClientWidth, 0);
end;
end;
Drawing polylines
In addition to individual lines, the canvas can also draw polylines, which are groups
of any number of connected line segments.
To draw a polyline on a canvas, call the Polyline method of the canvas.
The parameter passed to the Polyline method is an array of points. You can think of a
polyline as performing a MoveTo on the first point and LineTo on each successive
point. For drawing multiple lines, Polyline is faster than using the MoveTo method
and the LineTo method because it eliminates a lot of call overhead.
The following method, for example, draws a rhombus in a form:
procedure TForm1.FormPaint(Sender: TObject);
begin
with Canvas do
Polyline([Point(0, 0), Point(50, 0), Point(75, 50), Point(25, 50), Point(0, 0)]);
end;
Drawing shapes
Canvases have methods for drawing different kinds of shapes. The canvas draws the
outline of a shape with its pen, then fills the interior with its brush. The line that
forms the border for the shape is controlled by the current Pen object.
This section covers:
• Drawing rectangles and ellipses.
• Drawing rounded rectangles.
• Drawing polygons.
Drawing polygons
To draw a polygon with any number of sides on a canvas, call the Polygon method of
the canvas.
Polygon takes an array of points as its only parameter and connects the points with
the pen, then connects the last point to the first to close the polygon. After drawing
the lines, Polygon uses the brush to fill the area inside the polygon.
For example, the following code draws a right triangle in the lower left half of a form:
procedure TForm1.FormPaint(Sender: TObject);
begin
Canvas.Polygon([Point(0, 0), Point(0, ClientHeight),
Point(ClientWidth, ClientHeight)]);
end;
Drawing shapes
Drawing shapes is just as easy as drawing lines. Each one takes a single statement;
you just need the coordinates.
Here’s a rewrite of the OnMouseUp event handler that draws shapes for all four tools:
procedure TForm1.FormMouseUp(Sender: TObject; Button TMouseButton; Shift: TShiftState;
X,Y: Integer);
begin
case DrawingTool of
dtLine:
begin
Canvas.MoveTo(Origin.X, Origin.Y);
Canvas.LineTo(X, Y)
end;
dtRectangle: Canvas.Rectangle(Origin.X, Origin.Y, X, Y);
dtEllipse: Canvas.Ellipse(Origin.X, Origin.Y, X, Y);
dtRoundRect: Canvas.RoundRect(Origin.X, Origin.Y, X, Y,
(Origin.X - X) div 2, (Origin.Y - Y) div 2);
end;
Drawing := False;
end;
Of course, you also need to update the OnMouseMove handler to draw shapes:
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if Drawing then
begin
Canvas.Pen.Mode := pmNotXor;
case DrawingTool of
dtLine: begin
Canvas.MoveTo(Origin.X, Origin.Y);
Canvas.LineTo(MovePt.X, MovePt.Y);
Canvas.MoveTo(Origin.X, Origin.Y);
Canvas.LineTo(X, Y);
end;
dtRectangle: begin
Canvas.Rectangle(Origin.X, Origin.Y, MovePt.X, MovePt.Y);
Canvas.Rectangle(Origin.X, Origin.Y, X, Y);
end;
dtEllipse: begin
Canvas.Ellipse(Origin.X, Origin.Y, X, Y);
Canvas.Ellipse(Origin.X, Origin.Y, X, Y);
end;
dtRoundRect: begin
Canvas.RoundRect(Origin.X, Origin.Y, X, Y,
(Origin.X - X) div 2, (Origin.Y - Y) div 2);
Canvas.RoundRect(Origin.X, Origin.Y, X, Y,
(Origin.X - X) div 2, (Origin.Y - Y) div 2);
end;
end;
MovePt := Point(X, Y);
end;
Canvas.Pen.Mode := pmCopy;
end;
Typically, all the repetitious code that is in the above example would be in a separate
routine. The next section shows all the shape-drawing code in a single routine that all
mouse-event handlers can call.
Drawing on a graphic
You don’t need any components to manipulate your application’s graphic objects.
You can construct, draw on, save, and destroy graphic objects without ever drawing
anything on screen. In fact, your applications rarely draw directly on a form. More
often, an application operates on graphics and then uses an image control component
to display the graphic on a form.
Once you move the application’s drawing to the graphic in the image control, it is
easy to add printing, clipboard, and loading and saving operations for any graphic
objects. graphic objects can be bitmap files, drawings, icons or whatever other
graphics classes that have been installed such as jpeg graphics.
Note Because you are drawing on an offscreen image such as a TBitmap canvas, the image
is not displayed until a control copies from a bitmap onto the control’s canvas. That
is, when drawing bitmaps and assigning them to an image control, the image
appears only when the control has an opportunity to process its paint message. But if
you are drawing directly onto the canvas property of a control, the picture object is
displayed immediately.
In this example, the image is in the application’s main form, Form1, so the code
attaches a handler to Form1’s OnCreate event:
procedure TForm1.FormCreate(Sender: TObject);
var
Bitmap: TBitmap;{ temporary variable to hold the bitmap }
begin
Bitmap := TBitmap.Create;{ construct the bitmap object }
Bitmap.Width := 200;{ assign the initial width... }
Bitmap.Height := 200;{ ...and the initial height }
Image.Picture.Graphic := Bitmap;{ assign the bitmap to the image control }
Bitmap.Free; {We are done with the bitmap, so free it }
end;
Assigning the bitmap to the picture’s Graphic property copies the bitmap to the
picture object. However, the picture object does not take ownership of the bitmap, so
after making the assignment, you must free it.
If you run the application now, you see that client area of the form has a white region,
representing the bitmap. If you size the window so that the client area cannot display
the entire image, you’ll see that the scroll box automatically shows scroll bars to
allow display of the rest of the image. But if you try to draw on the image, you don’t
get any graphics, because the application is still drawing on the form, which is now
behind the image and the scroll box.
canvas.draw(0,0,Bitmap);
finally
Bitmap.free;
end;
end;
Note For CLX applications, change Windows- and VCL-specific code so that your
application can run on Linux. For example, the pathnames in Linux use a forward
slash / as a delimiter. For more information on CLX applications, see Chapter 15,
“Developing cross-platform applications.”
WidthEdit
HeightEdit
This particular dialog box is created in the BMPDlg unit included with the GraphEx
project (in the demos\doc\graphex directory).
With such a dialog box in your project, add it to the uses clause in the unit for your
main form. You can then attach an event handler to the File|New menu item’s
OnClick event. Here’s an example:
procedure TForm1.New1Click(Sender: TObject);
var
Bitmap: TBitmap;{ temporary variable for the new bitmap }
begin
with NewBMPForm do
begin
ActiveControl := WidthEdit;{ make sure focus is on width field }
WidthEdit.Text := IntToStr(Image.Picture.Graphic.Width);{ use current dimensions... }
HeightEdit.Text := IntToStr(Image.Picture.Graphic.Height);{ ...as default }
if ShowModal <> idCancel then{ continue if user doesn't cancel dialog box }
begin
Bitmap := TBitmap.Create;{ create fresh bitmap object }
Bitmap.Width := StrToInt(WidthEdit.Text);{ use specified width }
Bitmap.Height := StrToInt(HeightEdit.Text);{ use specified height }
Image.Picture.Graphic := Bitmap;{ replace graphic with new bitmap }
CurrentFile := '';{ indicate unnamed file }
Bitmap.Free;
end;
end;
end;
Note Assigning a new bitmap to the picture object’s Graphic property causes the picture
object to copy the new graphic, but it does not take ownership of it. The picture object
maintains its own internal graphic object. Because of this, the previous code frees the
bitmap object after making the assignment.
For CLX applications, data that is stored on the clipboard is stored as a MIME type
with an associated TStream object. CLX applications provide predefined constants for
the following MIME types.
When an application detects a mouse action, it calls whatever event handler you’ve
defined for the corresponding event, passing five parameters. Use the information in
those parameters to customize your responses to the events. The five parameters are as
follows:
Most of the time, you need the coordinates returned in a mouse-event handler, but
sometimes you also need to check Button to determine which mouse button caused
the event.
Note Delphi uses the same criteria as Microsoft Windows in determining which mouse
button has been pressed. Thus, if you have switched the default “primary” and
“secondary” mouse buttons (so that the right mouse button is now the primary
button), clicking the primary (right) button will record mbLeft as the value of the
Button parameter.
When the application runs, you can press the mouse button down with the mouse
cursor on the form and have the string, “Here!” appear at the point clicked. This code
sets the current drawing position to the coordinates where the user presses the
button:
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Canvas.MoveTo(X, Y);{ set pen position }
end;
Pressing the mouse button now sets the pen position, setting the line’s starting point.
To draw a line to the point where the user releases the button, you need to respond to
a mouse-up event.
With this code, moving the mouse over the form causes drawing to follow the mouse,
even before the mouse button is pressed.
Mouse-move events occur even when you haven’t pressed the mouse button.
If you want to track whether there is a mouse button pressed, you need to add an
object field to the form object.
begin
Canvas.LineTo(X, Y);
Drawing := False;{ clear the Drawing flag }
end;
Then you can modify the OnMouseMove event handler to draw only when Drawing is
True:
procedure TForm1.FormMouseMove(Sender: TObject;Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Drawing then{ only draw if Drawing flag is set }
Canvas.LineTo(X, Y);
end;
This results in drawing only between the mouse-down and mouse-up events, but
you still get a scribbled line that tracks the mouse movements instead of a straight
line.
The problem is that each time you move the mouse, the mouse-move event handler
calls LineTo, which moves the pen position, so by the time you release the button,
you’ve lost the point where the straight line was supposed to start.
Tracking movement
The problem with this example as the OnMouseMove event handler is currently
written is that it draws the line to the current mouse position from the last mouse
position, not from the original position. You can correct this by moving the drawing
position to the origin point, then drawing to the current point:
procedure TForm1.FormMouseMove(Sender: TObject;Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Drawing then
begin
Canvas.MoveTo(Origin.X, Origin.Y);{ move pen to starting point }
Canvas.LineTo(X, Y);
end;
end;
The above tracks the current mouse position, but the intermediate lines do not go
away, so you can hardly see the final line. The example needs to erase each line
before drawing the next one, by keeping track of where the previous one was. The
MovePt field allows you to do this.
MovePt must be set to the endpoint of each intermediate line, so you can use MovePt
and Origin to erase that line the next time a line is drawn:
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Drawing := True;
Canvas.MoveTo(X, Y);
Origin := Point(X, Y);
MovePt := Point(X, Y);{ keep track of where this move was }
end;
procedure TForm1.FormMouseMove(Sender: TObject;Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Drawing then
begin
Canvas.Pen.Mode := pmNotXor;{ use XOR mode to draw/erase }
Canvas.MoveTo(Origin.X, Origin.Y);{ move pen back to origin }
Canvas.LineTo(MovePt.X, MovePt.Y);{ erase the old line }
Canvas.MoveTo(Origin.X, Origin.Y);{ start at origin again }
Canvas.LineTo(X, Y);{ draw the new line }
end;
MovePt := Point(X, Y);{ record point for next move }
Canvas.Pen.Mode := pmCopy;
end;
Now you get a “rubber band” effect when you draw the line. By changing the pen’s
mode to pmNotXor, you have it combine your line with the background pixels. When
you go to erase the line, you’re actually setting the pixels back to the way they were.
By changing the pen mode back to pmCopy (its default value) after drawing the lines,
you ensure that the pen is ready to do its final drawing when you release the mouse
button.
4 Set the Repetitions property to the number of times you want to the AVI or GIF clip
to play. If this value is 0, then the sequence is repeated until the Stop method is
called.
5 Make any other changes to the animation control settings. For example, if you
want to change the first frame displayed when animation control opens, then set
the StartFrame property to the desired frame value.
6 Set the Active property to True using the drop-down list or write an event handler
to run the AVI or GIF clip when a specific event takes place at runtime. For
example, to activate the AVI or GIF clip when a button object is clicked, write the
button’s OnClick event specifying that. You may also call the Play method to
specify when to play the AVI (VCL only).
Note If you make any changes to the form or any of the components on the form after
setting Active to True, the Active property becomes False and you have to reset it to
True. Do this either just before runtime or at runtime.
9 Click the animation control to bring focus back to it. Double-click its OnStop event
and write the following code to close the form when the AVI file stops:
LogoForm1.Close;
10 Select Run|Run to execute the animated logo window.
7 Position the media player control bar on the form by either clicking and dragging
it to the appropriate place on the form or by selecting the Align property and
choosing the appropriate align position from the drop down list.
If you want the media player to be invisible at runtime, set the Visible property to
False and control the device by calling the appropriate methods (Play, Pause, Stop,
Next, Previous, Step, Back, Start Recording, Eject).
8 Make any other changes to the media player control settings. For example, if the
media requires a display window, set the Display property to the control that
displays the media. If the device uses multiple tracks, set the Tracks property to the
desired track.
6 Double-click the Animate icon from the Win32 page of the Component palette. Set
its AutoSize property to False, its Height property to 175 and Width property to
200. Click and drag the animation control to the top left corner of the form.
7 Click the media player to bring back focus to it. Select its Display property and
choose Animate1 from the drop down list.
8 Click the form to bring focus to it and select its Name property and enter
Delphi_Ad. Now resize the form to the size of the animation control.
9 Double-click the form’s OnActivate event and write the following code to run the
AVI video when the form is in focus:
VideoPlayer1.Play;
10 Choose Run|Run to execute the AVI video.
Note For CLX applications, you must use separate code for assigning priorities on
Windows and Linux. On Linux, Priority is a numeric value that depends on the
threading policy which can only be changed by root. See the CLX version of TThread
and Priority online Help for details.
Warning Boosting the thread priority of a CPU intensive operation may “starve” other threads
in the application. Only apply priority boosts to threads that spend most of their time
waiting for external events.
The following code shows the constructor of a low-priority thread that performs
background tasks which should not interfere with the rest of the application’s
performance:
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
Priority := tpIdle;
end;
Synchronize waits for the main thread to enter the message loop and then executes the
passed method.
Note Because Synchronize uses the message loop, it does not work in console applications.
You must use other mechanisms, such as critical sections, to protect access to VCL or
CLX objects in console applications.
You do not always need to use the main thread. Some objects are thread-aware.
Omitting the use of the Synchronize method when you know an object’s methods are
thread-safe will improve performance because you don’t need to wait for the VCL or
CLX thread to enter its message loop. You do not need to use the Synchronize method
for the following objects:
• Data access components are thread-safe as follows: For BDE-enabled datasets,
each thread must have its own database session component. The one exception to
this is when you are using Microsoft Access drivers, which are built using a
Microsoft library that is not thread-safe. For dbExpress, as long as the vendor
client library is thread-safe, the dbExpress components will be thread-safe. ADO
and InterBaseExpress components are thread-safe.
When using data access components, you must still wrap all calls that involve
data-aware controls in the Synchronize method. Thus, for example, you need to
synchronize calls that link a data control to a dataset by setting the DataSet
property of the data source object, but you don’t need to synchronize to access the
data in a field of the dataset.
For more information about using database sessions with threads in BDE-enabled
applications, see “Managing multiple sessions” on page 26-29.
• Controls are not thread-safe.
• Graphics objects are thread-safe. You do not need to use the main VCL or CLX
thread to access TFont, TPen, TBrush, TBitmap, TMetafile (VCL only), TDrawing
(CLX only), or TIcon. Canvas objects can be used outside the Synchronize method
by locking them (see “Locking objects” on page 13-8).
• While list objects are not thread-safe, you can use a thread-safe version,
TThreadList, instead of TList.
Call the CheckSynchronize routine periodically within the main thread of your
application so that background threads can synchronize their execution with the
main thread. The best place to call CheckSynchronize is when the application is idle
(for example, from an OnIdle event handler). This ensures that it is safe to make
method calls in the background thread.
To catch the exceptions that occur inside your thread function, add a try...except
block to the implementation of the Execute method:
procedure TMyThread.Execute;
begin
try
while not Terminated do
PerformSomeTask;
except
{ do something with exceptions }
end;
end;
Coordinating threads
When writing the code that runs when your thread is executed, you must consider
the behavior of other threads that may be executing simultaneously. In particular,
care must be taken to avoid two threads trying to use the same global object or
variable at the same time. In addition, the code in one thread can depend on the
results of tasks performed by other threads.
Locking objects
Some objects have built-in locking that prevents the execution of other threads from
using that object instance.
For example, canvas objects (TCanvas and descendants) have a Lock method that
prevents other threads from accessing the canvas until the Unlock method is called.
VCL and CLX applications also include a thread-safe list object, TThreadList. Calling
TThreadList.LockList returns the list object while also blocking other execution threads
from using the list until the UnlockList method is called. Calls to TCanvas.Lock or
TThreadList.LockList can be safely nested. The lock is not released until the last locking
call is matched with a corresponding unlock call in the same thread.
When you have some global memory that is read often, but to which threads
occasionally write, you can protect it using TMultiReadExclusiveWriteSynchronizer.
This object acts like a critical section, but allows multiple threads to read the memory
it protects as long as no thread is writing to it. Threads must have exclusive access to
write to memory protected by TMultiReadExclusiveWriteSynchronizer.
To use a multi-read exclusive-write synchronizer, create a global instance of
TMultiReadExclusiveWriteSynchronizer that is associated with the global memory you
want to protect. Every thread that reads from this memory must first call the
BeginRead method. BeginRead ensures that no other thread is currently writing to the
memory. When a thread finishes reading the protected memory, it calls the EndRead
method. Any thread that writes to the protected memory must call BeginWrite first.
BeginWrite ensures that no other thread is currently reading or writing to the
memory. When a thread finishes writing to the protected memory, it calls the
EndWrite method, so that threads waiting to read the memory can begin.
Warning Like critical sections, the multi-read exclusive-write synchronizer only works if every
thread uses it to access the associated global memory. Threads that ignore the
synchronizer and access the global memory without calling BeginRead or BeginWrite
introduce problems of simultaneous access.
The following code shows the end of the OnTerminate event handler for all of the
threads that must complete. CounterGuard is a global critical section object that
prevents multiple threads from using the counter at the same time. Counter is a global
variable that counts the number of threads that have completed.
procedure TDataModule.TaskThreadTerminate(Sender: TObject);
begin
ƒ
CounterGuard.Acquire; { obtain a lock on the counter }
Dec(Counter); { decrement the global counter variable }
if Counter = 0 then
Event1.SetEvent; { signal if this is the last thread }
CounterGuard.Release; { release the lock on the counter }
ƒ
end;
The main thread initializes the Counter variable, launches the task threads, and waits
for the signal that they are all done by calling the WaitFor method. WaitFor waits for a
specified time period for the signal to be set, and returns one of the values from Table
13.2.
The following shows how the main thread launches the task threads and then
resumes when they have all completed:
Event1.ResetEvent; { clear the event before launching the threads }
for i := 1 to Counter do
TaskThread.Create(False); { create and launch task threads }
if Event1.WaitFor(20000) <> wrSignaled then
raise Exception;
{ now continue with the main thread. All task threads have finished }
Note If you do not want to stop waiting for an event after a specified time period, pass the
WaitFor method a parameter value of INFINITE. Be careful when using INFINITE,
because your thread will hang if the anticipated signal is never received.
Naming a thread
Because it is difficult to tell which thread ID refers to which thread in the Thread
Status box, you can name your thread classes. When you are creating a thread class in
the Thread Object dialog box, besides entering a class name, also check the Named
Thread check box, enter a thread name, and click OK.
Naming the thread class adds a method to your thread class called SetName. When
the thread starts running, it calls the SetName method first.
Note You can name threads in VCL applications only.
2 Add the SetName method to your thread class in the interface section:
//---------------------------------------------------------------------------
type
TMyThread = class(TThread)
private
procedure SetName;
protected
procedure Execute; override;
end;
//---------------------------------------------------------------------------
3 Add the TThreadNameInfo record and SetName method in the implementation
section:
//---------------------------------------------------------------------------
{$IFDEF MSWINDOWS}
type
TThreadNameInfo = record
FType: LongWord; // must be 0x1000
FName: PChar; // pointer to name (in user address space)
FThreadID: LongWord; // thread ID (-1 indicates caller thread)
FFlags: LongWord; // reserved for future use, must be zero
end;
{$ENDIF}
{ TMyThread }
procedure TMyThread.SetName;
{$IFDEF MSWINDOWS}
var
ThreadNameInfo: TThreadNameInfo;
{$ENDIF}
begin
{$IFDEF MSWINDOWS}
ThreadNameInfo.FType := $1000;
ThreadNameInfo.FName := 'MyThreadName';
ThreadNameInfo.FThreadID := $FFFFFFFF;
ThreadNameInfo.FFlags := 0;
try
RaiseException( $406D1388, 0, sizeof(ThreadNameInfo) div sizeof(LongWord),
@ThreadNameInfo );
except
end;
{$ENDIF}
end;
//---------------------------------------------------------------------------
Note Set TThreadNameInfo to the name of your thread class.
The debugger sees the exception and looks up the thread name in the structure
you pass in. When debugging, the debugger displays the name of the thread in the
Thread Status box’s thread ID field.
4 Add a call to the new SetName method at the beginning of your thread’s Execute
method:
//---------------------------------------------------------------------------
procedure TMyThread.Execute;
begin
SetName;
{ Place thread code here }
end;
//---------------------------------------------------------------------------
14
Exception handling
Chapter14
Exceptions are exceptional conditions that require special handling. They include
errors that occur at runtime, such as divide by zero, and the exhaustion of free store.
Exception handling provides a standard way of dealing with errors, discovering both
anticipated and unanticipated problems, and enables developers to recognize, track
down, and fix bugs.
When an error occurs, the program raises an exception, meaning it creates an
exception object and rolls back the stack to the first point it finds where you have
code to handle the exception. The exception object usually contains information
about what happened. This allows another part of the program to diagnose the cause
of the exception.
To make your applications robust, your code needs to recognize exceptions when
they occur and respond to them. If you don't specify a response, the application
presents a message box describing the error. Your job, then, is to recognize places
where errors might happen, and define responses, particularly in areas where errors
could cause the loss of data or system resources.
When you create a response to an exception, you do so on blocks of code. When you
have a series of statements that all require the same kind of response to errors, you
can group them into a block and define error responses that apply to the whole block.
Blocks with specific responses to exceptions are called protected blocks because they
can guard against errors that might otherwise either terminate the application or
damage data.
Raising an exception
To indicate a disruptive error condition, you can raise an exception by constructing
an instance of an exception object that describes the error condition and calling the
reserved word raise.
To raise an exception, call the reserved word raise, followed by an instance of an
exception object. This establishes the exception as coming from a particular address.
When an exception handler actually handles the exception, it finishes by destroying
the exception instance, so you never need to do that yourself.
For example, given the following declaration,
type
EPasswordInvalid = class(Exception);
you can raise a “password invalid” exception at any time by calling raise with an
instance of EPasswordInvalid, like this:
if Password <> CorrectPassword then
raise EPasswordInvalid.Create('Incorrect password entered');
Raising an exception sets the ErrorAddr variable in the System unit to the address
where the application raised the exception. You can refer to ErrorAddr in your
exception handlers, for example, to notify the user where the error occurred. You can
also specify a value in the raise clause that appears in ErrorAddr when an exception
occurs.
Warning Do not assign a value to ErrorAddr yourself. It is intended as read-only.
To specify an error address for an exception, add the reserved word at after the
exception instance, followed by an address expression such as an identifier.
Exception-handling statements
The exception handling block starts with the except keyword and ends with the
keyword end. These two keywords are actually part of the same statement as the try
block. That is, both the try block and the exception handling block are considered
part of a single try...except statement.
Inside the exception handling block, you include one or more exception handlers. An
exception handler is a statement of the form
on <type of exception> do <statement>;
For example, the following exception handling block includes multiple exception
handlers for different exceptions that can arise from an arithmetic computation:
try
{ calculation statements }
except
on EZeroDivide do Value := MAXINT;
on EIntOverflow do Value := 0;
on EIntUnderflow do Value := 0;
end;
Much of the time, as in the previous example, the exception handler doesn't need any
information about an exception other than its type, so the statements following
on..do are specific only to the type of exception. In some cases, however, you might
need some of the information contained in the exception instance.
To read specific information about an exception instance in an exception handler,
you use a special variation of on..do that gives you access to the exception instance.
The special form requires that you provide a temporary variable to hold the instance.
For example:
on E: EIntegerRange do
ShowMessage(Format('Expected value between %d and %d', E.Min, E.Max));
The temporary variable (E in this example) is of the type specified after the colon
(EIntegerRange in this example). You can use the as operator to typecast the exception
into a more specific type if needed.
Warming Never destroy the temporary exception object. Handling an exception automatically
destroys the exception object. If you destroy the object yourself, the application
attempts to destroy the object again, generating an access violation.
You can provide a single default exception handler to handle any exceptions for
which you haven't provided specific handlers. To do that, add an else part to the
exception-handling block:
try
{ statements }
except
on ESomething do
{ specific exception-handling code };
else
{ default exception-handling code };
end;
Adding default exception handling to a block guarantees that the block handles
every exception in some way, thereby overriding all handling from any containing
block.
Warning It is not advisable to use this all-encompassing default exception handler. The else
clause handles all exceptions, including those you know nothing about. In general,
your code should handle only exceptions you actually know how to handle. If you
want to handle cleanup and leave the exception handling to code that has more
information about the exception and how to handle it, then you can do so using a
finally block. For details about finally blocks, see “Writing finally blocks” on
page 14-8.
Thus, you can nest your exception handling code. That is, you can use nested blocks
to define local handling for specific exceptions that overrides the handling in the
surrounding block. For example:
try
{ statements }
try
{ special statements }
except
on ESomething do
begin
{ handling for only the special statements }
end;
end;
{ more statements }
except
on ESomething do
begin
{handling for statements and more statements, but not special statements}
end;
end;
Note This type of nesting is not limited to exception-handling blocks. You can also use it
with finally blocks (described in “Writing finally blocks” on page 14-8) or a mix of
exception-handling and finally blocks.
Reraising exceptions
Sometimes when you handle an exception locally, you want to augment the handling
in the enclosing block, rather than replace it. Of course, when your local handler
finishes its handling, it destroys the exception instance, so the enclosing block's
handler never gets to act. You can, however, prevent the handler from destroying the
exception, giving the enclosing handler a chance to respond. You do this by using the
raise command with no arguments. This is called reraising or rethrowing the
exception. The following example illustrates this technique:
try
{ statements }
try
{ special statements }
except
on ESomething do
begin
{ handling for only the special statements }
raise;{ reraise the exception }
end;
end;
except
on ESomething do ...;{ handling you want in all cases }
end;
If code in the statements part raises an ESomething exception, only the handler in the
outer exception-handling block executes. However, if code in the special statements
part raises ESomething, the handling in the inner exception-handling block executes,
followed by the more general handling in the outer exception-handling block. By
reraising exceptions, you can easily provide special handling for exceptions in special
cases without losing (or duplicating) the existing handlers.
If the handler wants to throw a different exception, it can use the raise or throw
statement in the normal way, as described in “Raising an exception” on page 14-3.
If you do not handle the exception, VCL handles it in a default manner. Typically, a
message displays describing the type of error that occurred. While debugging your
application, you can look up the exception class in online Help. The information
provided will often help you to determine where the error occurred and its cause.
A common source of errors in components is range errors in indexed properties. For
example, if a list box has three items in its list (0..2) and your application attempts to
access item number 3, the list box raises a “List index out of bounds” exception.
The following event handler contains an exception handler to notify the user of
invalid index access in a list box:
procedure TForm1.Button1Click(Sender: TObject);
begin
ListBox1.Items.Add('a string');{ add a string to list box }
ListBox1.Items.Add('another string');{ add another string... }
ListBox1.Items.Add('still another string');{ ...and a third string }
try
Caption := ListBox1.Items[3];{ set form caption to fourth string }
except
on EStringListError do
ShowMessage('List box contains fewer than four strings');
end;
end;
If you click the button once, the list box has only three strings, so accessing the fourth
string raises an exception. Clicking a second time adds more strings to the list, so it
no longer causes the exception.
There are other times when you will need to create your own exception classes to
handle unique situations. You can declare a new exception class by making it a
descendant of type Exception and creating as many constructors as you need (or copy
the constructors from an existing class in the SysUtils unit).
There are certain circumstances where HandleException does not get called.
Exceptions that occur before or after the execution of the application’s Run method
are not caught and handled by HandleException. When you write a callback function
or a library (.dll or shared object) with functions that can be called by an external
application, exceptions can escape the Application object. To prevent exceptions from
escaping in this manner, you can insert your own call to the HandleException method:
try
{ special statements }
except
on Exception do
begin
Application.HandleException(Self);{ call HandleException }
end;
end;
Warning Do not call HandleException from a thread’s exception handling code.
Silent exceptions
VCL applications handle most exceptions that your code doesn't specifically handle
by displaying a message box that shows the message string from the exception object.
You can also define “silent” exceptions that do not, by default, cause the application
to show the error message.
Silent exceptions are useful when you don't intend to report an exception to the user,
but you want to abort an operation. Aborting an operation is similar to using the
Break or Exit procedures to break out of a block, but can break out of several nested
levels of blocks.
Silent exceptions all descend from the standard exception type EAbort. The default
exception handler for VCL applications displays the error-message dialog box for all
exceptions that reach it except those descended from EAbort.
Note For console applications, an error-message dialog is displayed on any unhandled
EAbort exceptions.
There is a shortcut for raising silent exceptions. Instead of manually constructing the
object, you can call the Abort procedure. Abort automatically raises an EAbort
exception, which breaks out of the current operation without displaying an error
message.
Note There is a distinction between Abort and abort. abort kills the application.
Developing cross-platform
Chapter15
15
applications
You can develop cross-platform 32-bit applications that run on both the Windows
and Linux operating systems. Cross-platform applications use CLX components
from the Borland Component Library for Cross-Platform (CLX) and don’t make any
operating system-specific API calls.
This chapter describes how to change Delphi applications so they can compile on
Windows or Linux and how to write code that is platform-independent and portable
between the two environments. It also includes information on the differences
between developing applications on Windows and Linux.
To develop a cross-platform application, either:
• Create a new CLX application.
• Modify an existing VCL application.
Then compile, test, and deploy it on the platform you are running it on. For Windows
cross-platform applications, use Delphi. For Linux cross-platform applications, use
Kylix. Kylix is Borland’s Delphi and C++ software that allows you to develop and
deploy applications on Linux.
You can also develop a cross-platform application by starting on Kylix instead of
Windows and transfer it to Windows
Note CLX applications are not available in all editions of Delphi.
Porting techniques
The following are different approaches you can take to port an application from one
platform to another:
Platform-specific ports
Platform-specific ports tend to be time-consuming, expensive, and only produce a
single targeted result. They create different code bases, which makes them
particularly difficult to maintain. However, each port is designed for a specific
operating system and can take advantage of platform-specific functionality. Thus, the
application typically runs faster.
Cross-platform ports
Cross-platform ports tend to be time-saving because the ported applications target
multiple platforms. However, the amount of work involved in developing cross-
platform applications is highly dependent on the existing code. If code has been
developed without regard for platform independence, you may run into scenarios
where platform-independent logic and platform-dependent implementation are
mixed together.
The cross-platform approach is the preferable approach because business logic is
expressed in platform-independent terms. Some services are abstracted behind an
internal interface that looks the same on all platforms, but has a specific
implementation on each. The runtime library is an example of this. The interface is
very similar on both platforms, although the implementation may be vastly different.
You should separate cross-platform parts, then implement specific services on top. In
the end, this approach is the least expensive solution, because of reduced
maintenance costs due to a largely shared source base and an improved application
architecture.
For example, you can use conditional compiler directives for platform-specific
code in your source files:
{$IFDEF MSWINDOWS}
IniFile.LoadfromFile(‘c:\x.txt’);
{$ENDIF}
{$IFDEF LINUX}
IniFile.LoadfromFile(‘/home/name/x.txt’);
{$ENDIF}
7 Search for references to pathnames in all the project files.
• Pathnames in Linux use a forward slash / as a delimiter (such as /usr/lib) and
files may be located in different directories on the Linux system. Use the
PathDelim constant (in SysUtils) to specify the path delimiter that is
appropriate for the system. Determine the correct location for any files on
Linux.
• Change references to drive letters (for example, C:\) and code that looks for
drive letters by looking for a colon at position 2 in the string. Use the
DriveDelim constant (in SysUtils) to specify the location in terms that are
appropriate for the system.
• In places where you specify multiple paths, change the path separator from
semicolon (;) to colon (:). Use the PathSep constant (in SysUtils) to specify the
path separator that is appropriate for the system.
• Because file names are case-sensitive in Linux, make sure that your application
doesn’t change the case of file names or assume a certain case.
See “Programming differences on Linux” on page 15-16.
appear in the QControls unit file to simplify sharing of source code. TWidgetControl
and all its descendants have a Handle property that references the Qt object and a
Hooks property that references the hook object that handles the event mechanism.
Unit names and locations of some classes are different in CLX. You will need to
modify the uses clauses you include in your source files to eliminate references to
units that don’t exist in VisualCLX and to change the names to CLX units. Most
project files and the interface sections of most units contain a uses clauses. The
implementation section of a unit can also contain its own uses clause.
Additional differences exist. Refer to the CLX online documentation for details on all
of the CLX objects or in editions of Delphi that include the source code, located in
{install directory}\Delphi\Source\Clx.
The following Windows-only units are not included in CLX applications mostly
because they concern Windows-specific features that are not available on Linux. For
example, CLX applications do not use ADO units, BDE units, COM units, or
Windows units such as CtlPanel, Messages, Registry, and Windows.
References to these units and the classes within these units must be eliminated from
applications you want to run on Linux. If you try to compile a program with units
that do not exist in a cross-platform application, you will receive the following error
message:
File not found: ‘unitname.dcu’
Delete that unit from the uses clause and try again.
Follow these guidelines for using conditional compiler directives within cross-
platform applications:
• Try not to use $IFDEFs unless absolutely necessary. $IFDEFs in a source file are
only evaluated when source code is compiled. Delphi does not require unit
sources to compile a project. Full rebuilds of all source code is an uncommon event
for most Delphi projects.
• Do not use $IFDEFs in package (.dpk) files. Limit their use to source files.
Component writers need to create two design-time packages when doing cross-
platform development, not one package using $IFDEFs.
• In general, use $IFDEF MSWINDOWS to test for any Windows platform
including WIN32. Reserve the use of $IFDEF WIN32 for distinguishing between
specific Windows platforms, such as 32-bit versus 64-bit Windows. Don’t limit
your code to WIN32 unless you know for sure that it will not work in WIN64.
• Avoid negative tests like $IFNDEF unless absolutely required. $IFNDEF LINUX
is not equivalent to $IFDEF MSWINDOWS.
• Avoid $IFNDEF/$ELSE combinations. Use a positive test instead ($IFDEF) for
better readability.
• Avoid $ELSE clauses on platform-sensitive $IFDEFs. Use separate $IFDEF blocks
for Linux- and Windows-specific code instead of $IFDEF LINUX/$ELSE or
$IFDEF MSWINDOWS/$ELSE.
For example, old code may contain:
{$IFDEF WIN32}
(32-bit Wi1ndows code)
{$ELSE}
(16-bit Windows code) //!! By mistake, Linux could fall into this code.
{$ENDIF}
For any non-portable code in $IFDEFs, it is better for the source code to fail to
compile than to have the platform fall into an $ELSE clause and fail mysteriously
at runtime. Compile failures are easier to find than runtime failures.
• Use the $IF syntax for complicated tests. Replace nested $IFDEFs with a boolean
expression in an $IF directive. You should terminate the $IF directive using
$IFEND, not $ENDIF. This allows you to place $IF expressions within $IFDEFs to
hide the new $IF syntax from previous compilers.
All of the conditional directives are documented in the online Help. Also, see the
topic “conditional directives” in Help for more information.
Note When nesting an $IF inside of $IFDEF/$ENDIF, do not use $ELSE with the $IF.
Older compilers will see the $ELSE and think it is part of the $IFDEF, producing a
compile error down the line. You can use {$ELSE True} as a substitute for {$ELSE} in
this situation, since the $ELSE won't be taken if the $IF is taken first, and the older
compilers won't know $ELSEIF. Hiding $IF for backwards compatibility is primarily
an issue for third party vendors and application developers who want their code to
run on several different versions.
$ELSEIF is a combination of $ELSE and $IF. The $ELSEIF directive allows you to
write multi-part conditional blocks where only one of the conditional blocks will be
taken. For example:
{$IFDEF doit}
do_doit
{$ELSEIF RTLVersion >= 14}
goforit
{$ELSEIF somestring = 'yes'}
beep
{$ELSE}
last chance
{$IFEND}
Of these four cases, only one is taken. If none of the first three conditions is true, the
$ELSE clause is taken. $ELSEIF must be terminated by $IFEND. $ELSEIF cannot
appear after $ELSE. Conditions are evaluated top to bottom like a normal
$IF...$ELSE sequence. In the example, if doit is not defined, then RTLVersion is 15
and somestring is 'yes.' Only the “goforit” block is taken and not the “beep” block,
even though the conditions for both are true.
If you forget to use an $ENDIF to end one of your $IFDEFs, the compiler reports the
following error message at the end of the source file:
Missing ENDIF
If you have more than a few $IF/$IFDEF directives in your source file, it can be
difficult to determine which one is causing the problem. The following error message
appears on the source line of the last $IF/$IFDEF compiler directive with no
matching $ENDIF/$IFEND:
Unterminated conditional directive
You can start looking for the problem at that location.
Precompiled units are available in both PIC and non-PIC formats. PIC units have a
.dpu extension (instead of .dcu).
You may want to code assembler routines differently depending on whether you'll
be compiling to an executable or a shared library; use {$IFDEF PIC} to branch the two
versions of your assembler code. Or you can consider rewriting the routine in the
Delphi language to avoid the issue.
Following are the PIC rules for inline assembler code:
• PIC requires all memory references be made relative to the EBX register, which
contains the current module's base address pointer (in Linux called the Global
Offset Table or GOT). So, instead of
MOV EAX,GlobalVar
use
MOV EAX,[EBX].GlobalVar
• PIC requires that you preserve the EBX register across calls into your assembly
code (same as on Win32), and also that you restore the EBX register before making
calls to external functions (different from Win32).
• While PIC code will work in base executables, it may slow the performance and
generate more code. You don't have any choice in shared objects, but in
executables you probably still want to get the highest level of performance that
you can.
Table 15.6 Differences in the Linux and Windows operating environments (continued)
Difference Description
Drive letters Linux doesn't have drive letters. An example Linux pathname is
/lib/security. See DriveDelim in the runtime library.
Exceptions Operating system exceptions are called signals on Linux.
Executable files On Linux, executable files require no extension. On Windows,
executable files have an exe extension.
File name extensions Linux does not use file name extensions to identify file types or to
associate files with applications.
File permissions On Linux, files (and directories) are assigned read, write, and execute
permissions for the file owner, group, and others. For example,
-rwxr-xr-x means, from left to right:
• - is the file type (- = ordinary file, d = directory, l = link)
• rwx are the permissions for the file owner (read, write, execute)
• r-x are the permissions for the group of the file owner (read,
execute)
• r-x are the permissions for all other users (read, execute)
The root user (superuser) can override these permissions.
You need to make sure that your application runs under the correct
user and has proper access to required files.
Make utility Borland's make utility is not available on the Linux platform. Instead,
you can use Linux's GNU make utility.
Multitasking Linux fully supports multitasking. You can run several programs (in
Linux, called processes) at the same time. You can launch processes in
the background (using & after the command) and continue working
straight away. Linux also lets you have several sessions.
Pathnames Linux uses a forward slash (/) wherever DOS uses a backslash (\). A
PathDelim constant can be used to specify the appropriate character
for the platform. See PathDelim in the runtime library. See “Directory
structure on Linux” on page 15-20.
Search path When executing programs, Windows always checks the current
directory first, then looks at the PATH environment variable. Linux
never looks in the current directory but searches only the directories
listed in PATH. To run a program in the current directory, you
usually have to type ./ before it.
You can also modify your PATH to include ./ as the first path to
search.
Search path separator Windows uses the semicolon as a search path separator. Linux uses a
colon. See PathDelim in the runtime library.
Symbolic links On Linux, a symbolic link is a special file that points to another file on
disk. Place symbolic links in the global bin directory that points to
your application's main files and you don't have to modify the system
search path. A symbolic link is created with the ln (link) command.
Windows has shortcuts for the GUI desktop. To make a program
available at the command line, Windows install programs typically
modify the system search path.
Registry
Linux does not use a registry to store configuration information. Instead, you use text
configuration files and environment variables rather than the registry. System
configuration files on Linux are often located in /etc, such as /etc/hosts. Other user
profiles are located in hidden files (preceded with a dot), such as .bashrc, which
holds bash shell settings or .XDefaults, which is used to set defaults for X programs.
Registry-dependent code may be changed to using a local configuration text file
instead. Settings that users can change must be saved in their home directory so that
they have permission to write to it. Configuration options that need to be set by the
root are stored in /etc. Writing a unit containing all the registry functions but
diverting all output to a local configuration file is one way you could handle a former
dependency on the registry.
To place information in a global location on Linux, you can store a global
configuration file in the /etc directory or the user’s home directory as a hidden file.
Therefore, all of your applications can access the same configuration file. However,
you must be sure that the file permissions and access rights are set up correctly.
You can also use .ini files in cross-platform applications. However, in CLX, you need
to use TMemIniFile instead of TRegIniFile.
Note Different distributions of Linux sometimes place files in different locations. A utility
program may be placed in /bin in a Red Hat distribution but in /usr/local/bin in a
Debian distribution.
Refer to www.pathname.com for additional details on the organization of the UNIX/
Linux hierarchical file system and to read the Filesystem Hierarchy Standard.
dbExpress differences
On Linux, dbExpress manages the communication with database servers. dbExpress
consists of a set of lightweight drivers that implement a set of common interfaces.
Each driver is a shared object (.so file) that must be linked to your application.
Because dbExpress is designed to be cross-platform, it is also available on Windows
as a set of dynamic-link libraries (.dlls).
As with any data-access layer, dbExpress requires the client-side software provided
by the database vendor. In addition, it uses a database-specific driver, plus two
configuration files, dbxconnections and dbxdrivers. This is markedly less than you
need for, say, the BDE, which requires the main Borland Database Engine library
(Idapi32.dll) plus a database-specific driver and a number of other supporting
libraries.
There are other differences between dbExpress and the other data-access layers from
which you need to port your application. For example, dbExpress:
• Allows for a simpler and faster path to remote databases. As a result, you can
expect a noticeable performance increase for simple, straight-through data access.
• Processes queries and stored procedures, but does not support the concept of
opening tables.
• Returns only unidirectional cursors.
• Has no built-in update support other than the ability to execute an INSERT,
DELETE, or UPDATE query.
• Does no metadata caching; the design time metadata access interface is
implemented using the core data-access interface.
• Executes only queries requested by the user, thereby optimizing database access
by not introducing any extra queries.
• Manages a record buffer or a block of record buffers internally. This differs from
the BDE, where clients are required to allocate the memory used to buffer records.
• Supports only local tables that are SQL-based, such as InterBase and Oracle.
• Uses drivers for DB2, Informix, InterBase, MSSQL, MySQL, and Oracle. If you are
using a different database server, you must either convert your data to one of
these databases, write a dbExpress driver for the database server you are using, or
obtain a third-party dbExpress driver for your database server.
Component-level differences
When you write a dbExpress application, it requires a different set of data access
components than those used in your existing database applications. The dbExpress
components share the same base classes as other data access components (TDataSet
and TCustomConnection), which means that many of the properties, methods, and
events are the same as the components used in your existing applications.
Table 15.8 lists some of the important database components used in InterBase
Express, BDE, and ADO in the Windows environment and shows the comparable
dbExpress components for use on Linux and in cross-platform applications.
If you have connected the dbExpress dataset to a client dataset, then the user
interface elements associated with editing and navigation should still work. You
need only reconnect them to the client dataset. The main consideration in this case is
handling how updates are written to the database. By default, most datasets on
Windows write updates to the database server automatically when they are posted
(for example, when the user moves to a new record). Client datasets, on the other
hand, always cache updates in memory. For information on how to accommodate
this difference, see “Updating data in dbExpress applications” on page 15-26.
• You can adjust your user interface to deal with cached updates. This approach has
certain advantages, such as reducing the amount of network traffic and
minimizing transaction times. However, if you switch to using cached updates,
you must decide when to apply those updates back to the database server, and
probably make user interface changes to let users initiate the application of
updates or inform them about whether their edits have been written to the
database. Further, because update errors are not detected when the user posts a
record, you will need to change the way you report such errors to the user, so that
they can see which update caused a problem as well as what type of problem
occurred.
If your original application used the support provided by the BDE or ADO for
caching updates, you will need to make some adjustments in your code to switch to
using a client dataset. The following table lists the properties, events, and methods
that support cached updates on BDE and ADO datasets, and the corresponding
properties, methods and events on TClientDataSet:
You can include VCL and CLX components in a package. Packages meant to be cross-
platform should include CLX components only.
Note Packages share their global data with other modules in an application.
For more information about DLLs and packages, see the Delphi Language Guide.
Runtime packages
Runtime packages are deployed with your applications. They provide functionality
when a user runs the application.
To run an application that uses packages, a computer must have both the
application’s executable file and all the packages (.bpl files) that the application uses.
The .bpl files must be on the system path for an application to use them. When you
deploy an application, you must make sure that users have correct versions of any
required .bpls.
Packages listed in the Runtime Packages edit box are automatically linked to your
application when you compile. Duplicate package names are ignored, and if the
Build with runtime packages check box is unchecked, the application is compiled
without packages.
Runtime packages are selected for the current project only. To make the current
choices into automatic defaults for new projects, select the Defaults check box at the
bottom of the dialog.
Note When you create an application with packages, you must include the names of the
original Delphi units in the uses clause of your source files. For example, the source
file for your main form might begin like this:
unit MainForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs; //Some units in CLX applications differ.
The units referenced in this example are contained in the vcl and rtl packages.
Nonetheless, you must keep these references in the uses clause, even if you use vcl
and rtl in your application, or you will get compiler errors. In generated source files,
the Form Designer adds these units to the uses clause automatically.
To create a client/server database application that uses packages, you need several
runtime packages, including vcl, vcldb, rtl, and dbrtl. If you want to use visual
components in your application, you also need vclx. To use these packages, choose
Project|Options, select the Packages tab, and make sure the following list is included
in the Runtime Packages edit box. You need netclx for Web server applications, as
well as baseclx and probably visualclx.
vcl;rtl;vcldb;vclx;
Note You don’t have to include vcl and rtl, because they are referenced in the Requires
clause of vcldb. (See “Requires clause” on page 16-9.) Your application compiles just
the same whether or not vcl and rtl are included in the Runtime Packages edit box.
Another way you can determine which packages are called by an application is to
run it then review the event log (choose View|Debug Windows|Event Log). The
event log displays every module that is loaded including all packages. The full
package names are listed. So, for example, for vcl70.bpl, you would see a line similar
to the following:
Module Load: vcl70.bpl Has Debug Info. Base Address $400B0000. Process Project1.exe ($22C)
Custom packages
A custom package is either a .bpl you code and compile yourself or an existing
package from a third-party vendor. To use a custom runtime package with an
application, choose Project|Options and add the name of the package to the Runtime
Packages edit box on the Packages page.
For example, suppose you have a statistical package called stats.bpl. To use it in an
application, the line you enter in the Runtime Packages edit box might look like this:
vcl;rtl;vcldb;stats
If you create your own packages, add them to the list as needed.
Design-time packages
Design-time packages are used to install components on the IDE’s Component
palette and to create special property editors for custom components. Which ones are
installed depends on which edition of Delphi you are using and whether or not you
have customized it. You can view a list of what packages are installed on your system
by choosing Component|Install Packages.
The design-time packages work by calling runtime packages, which they reference in
their Requires clause. (See “Requires clause” on page 16-9.) For example, dclstd
references vcl. The dclstd itself contains additional functionality that makes many of
the standard components available on the Component palette.
In addition to preinstalled packages, you can install your own component packages,
or component packages from third-party developers, in the IDE. The dclusr design-
time package is provided as a default container for new components.
Creating a package
To create a package, follow the procedure below. Refer to “Understanding the
structure of a package” on page 16-8 for more information about the steps outlined
here.
1 Choose File|New|Other, select the Package icon, and click OK. The generated
package appears in the Package editor. The Package editor displays a Requires
node and a Contains node for the new package.
2 To add a unit to the contains clause, click the Package editor’s Add button. In the
Add Unit page, type a .pas file name in the Unit file name edit box, or click Browse
to browse for the file, and then click OK. The unit you’ve selected appears under
the Contains node in the Package editor. You can add additional units by
repeating this step.
3 To add a package to the requires clause, click the Add button. In the Requires
page, type a .dcp file name in the Package name edit box, or click Browse to
browse for the file, and then click OK.The package you’ve selected appears under
the Requires node in the Package editor. You can add additional packages by
repeating this step.
4 Click the Options button, and decide what kind of package you want to build.
• To create a design-time only package (a package that cannot be used at
runtime), check the Designtime only radio button. (Or add the
{$DESIGNONLY} compiler directive to your dpk file.)
• To create a runtime-only package (a package that cannot be installed), select the
Runtime only radio button. (Or add the {$RUNONLY} compiler directive to the
dpk file.)
• To create a package that is available at both design time and runtime, select the
Designtime and runtime radio button.
5 In the Package editor, click the Compile button to compile your package.
Note You can also click the Install button to force a make.
Do not use IFDEFs in a package file (.dpk) when writing cross-platform applications.
You can use them in the source code, however.
Naming packages
Package names must be unique within a project. If you name a package Stats, the
Package editor generates a source file for it called Stats.dpk; the compiler generates
an executable and a binary image called Stats.bpl and Stats.dcp, respectively. Use
Stats to refer to the package in the requires clause of another package, or when using
the package in an application.
You can also add a prefix, suffix, and version number to your package name. While
the Package editor is open, click the Options button. On the Description page of the
Project Options dialog box, enter text or a value for LIB Suffix, LIB Prefix, or LIB
Version. For example, to add a version number to your package project, enter 7 after
LIB Version so that Package1 generates Package1.bpl.7.
Requires clause
The requires clause specifies other, external packages that are used by the current
package. An external package included in the requires clause is automatically linked
at compile time into any application that uses both the current package and one of
the units contained in the external package.
If the unit files contained in your package make references to other packaged units,
the other packages should appear in your package’s requires clause or you should
add them. If the other packages are omitted from the requires clause, the compiler
will import them into your package ‘implicitly contained units.’
Note Most packages that you create require rtl. If using VCL components, you’ll also need
to include the vcl package. If using CLX components for cross-platform
programming, you need to include VisualCLX.
Contains clause
The contains clause identifies the unit files to be bound into the package. If you are
writing your own package, put your source code in pas files and include them in the
contains clause.
Compiling packages
You can compile a package from the IDE or from the command line. To recompile a
package by itself from the IDE:
1 Choose File|Open and select a package (.dpk).
2 Click Open.
3 When the Package editor opens:
• Click the Package editor’s Compile button.
• In the IDE, choose Project|Build.
Note You can also choose File|New|Other and double-click the Package icon. Click the
Install button to make the package project. Right-click the package project nodes
for options to install, compile, or build.
You can insert compiler directives into your package source code. For more
information, see “Package-specific compiler directives” below.
If you compile from the command line, you can use several package-specific
switches. For more information, see “Compiling and linking from the command line”
on page 16-13.
Note Including {$DENYPACKAGEUNIT ON} in your source code prevents the unit file
from being packaged. Including {$G-} or {$IMPORTEDDATA OFF} may prevent a
package from being used in the same application with other packages. Packages
compiled with the {$DESIGNONLY ON} directive should not ordinarily be used in
applications, since they contain extra code required by the IDE. Other compiler
directives may be included, if appropriate, in package source code. See Compiler
directives in the online Help for information on compiler directives not discussed
here.
See Chapter 9, “Libraries and packages,” in the Delphi Language Guide for more
information on package-specific compiler directives.
Refer to “Creating packages and DLLs” on page 8-11 for additional directives that
can be used in all libraries.
Weak packaging
The $WEAKPACKAGEUNITdirective affects the way a .dcu file is stored in a
package’s .dcp and .bpl files. (For information about files generated by the compiler,
see “Package files created when compiling” on page 16-13.) If
{$WEAKPACKAGEUNIT ON} appears in a unit file, the compiler omits the unit
from bpls when possible, and creates a non-packaged local copy of the unit when it is
required by another application or package. A unit compiled with this directive is
said to be weakly packaged.
For example, suppose you’ve created a package called pack1 that contains only one
unit, unit1. Suppose unit1 does not use any additional units, but it makes calls to
rare.dll. If you put the {$WEAKPACKAGEUNIT ON} directive in unit1.pas (Delphi)
or unit1.cpp (C++) when you compile your package, unit1 will not be included in
pack1.bpl; you will not have to distribute copies of rare.dll with pack1. However,
unit1 will still be included in pack1.dcp. If unit1 is referenced by another package or
application that uses pack1, it will be copied from pack1.dcp and compiled directly
into the project.
Now suppose you add a second unit, unit2, to pack1. Suppose that unit2 uses unit1.
This time, even if you compile pack1 with {$WEAKPACKAGEUNIT ON} in
unit1.pas, the compiler will include unit1 in pack1.bpl. But other packages or
applications that reference unit1 will use the (non-packaged) copy taken from
pack1.dcp.
Note Unit files containing the {$WEAKPACKAGEUNIT ON} directive must not have
global variables, initialization sections, or finalization sections.
The {$WEAKPACKAGEUNIT ON} directive is an advanced feature intended for
developers who distribute their packages to other programmers. It can help you to
avoid distribution of infrequently used DLLs, and to eliminate conflicts among
packages that may depend on the same external library.
For example, the PenWin unit references PenWin.dll. Most projects don’t use
PenWin, and most computers don’t have PenWin.dll installed on them. For this
reason, the PenWin unit is weakly packaged in vcl. When you compile a project that
uses PenWin and the vcl package, PenWin is copied from vcl70.dcp and bound
directly into your project; the resulting executable is statically linked to PenWin.dll.
If PenWin were not weakly packaged, two problems would arise. First, vcl itself
would be statically linked to PenWin.dll, and so you could not load it on any
computer which didn’t have PenWin.dll installed. Second, if you tried to create a
package that contained PenWin, a compiler error would result because the PenWin
unit would be contained in both vcl and your package. Thus, without weak
packaging, PenWin could not be included in standard distributions of vcl.
Note Using the -$G- switch may prevent a package from being used in the same
application with other packages. Other command-line options may be used, if
appropriate, when compiling packages. See “The Command-line compiler” in the
online Help for information on command-line options not discussed here.
Deploying packages
You deploy packages much like you deploy other applications. The files you
distribute with a deployed package may vary. The bpl and any packages or dlls
required by the bpl must be distributed.
For general deployment information, refer to Chapter 18, “Deploying applications.”
3 Select the Collection node at the top of the tree diagram. On the right side of the
Package Collection editor, two fields appear:
• In the Author/Vendor Name edit box, you can enter optional information
about your package collection that appear in the Installation dialog when users
install packages.
• Under Directory list, list the default directories where you want the files in your
package collection to be installed. Use the Add, Edit, and Delete buttons to edit
this list. For example, suppose you want all source code files to be copied to the
same directory. In this case, you might enter Source as a Directory name with C:\
MyPackage\Source as the Suggested path. The Installation dialog box will display
C:\MyPackage\Source as the suggested path for the directory.
4 In addition to bpls, your package collection can contain .dcp, .dcu, and .pas (unit)
files, documentation, and any other files you want to include with the distribution.
Ancillary files are placed in file groups associated with specific packages (bpls);
the files in a group are installed only when their associated bpl is installed. To
place ancillary files in your package collection, select a bpl in the tree diagram and
click the Add a file group button; type a name for the file group. Add more file
groups, if desired, in the same way. When you select a file group, new fields will
appear on the right in the Package Collection editor.
• In the Install Directory list box, select the directory where you want files in this
group to be installed. The drop-down list includes the directories you entered
under Directory list in step 3, above.
• Check the Optional Group check box if you want installation of the files in this
group to be optional.
• Under Include Files, list the files you want to include in this group. Use the
Add, Delete, and Auto buttons to edit the list. The Auto button allows you to
select all files with specified extensions that are listed in the contains clause of
the package; the Package Collection editor uses the global Library Path to
search for these files.
5 You can select installation directories for the packages listed in the requires clause
of any package in your collection. When you select a bpl in the tree diagram, four
new fields appear on the right side of the Package Collection editor:
• In the Required Executables list box, select the directory where you want the
.bpl files for packages listed in the requires clause to be installed. (The drop-
down list includes the directories you entered under Directory list in step 3,
above.) The Package Collection editor searches for these files using Delphi’s
global Library Path and lists them under Required Executable Files.
• In the Required Libraries list box, select the directory where you want the .dcp
files for packages listed in the requires clause to be installed. (The drop-down
list includes the directories you entered under Directory List in step 3, above.)
The Package Collection editor searches for these files using the global Library
Path and lists them under Required Library Files.
6 To save your package collection source file, choose File|Save. Package collection
source files should be saved with the .pce extension.
7 To build your package collection, click the Compile button. The Package
Collection editor generates a .dpc file with the same name as your source (.pce)
file. If you have not yet saved the source file, the editor queries you for a file name
before compiling.
To edit or recompile an existing .pce file, select File|Open in the Package Collection
editor and locate the file you want to work with.
Internationalization
Internationalization is the process of enabling your program to work in multiple
locales. A locale is the user’s environment, which includes the cultural conventions of
the target country as well as the language. Windows supports many locales, each of
which is described by a language and country pair.
Localization
Localization is the process of translating an application so that it functions in a
specific locale. In addition to translating the user interface, localization may include
functionality customization. For example, a financial application may be modified for
the tax laws in different countries.
Internationalizing applications
You need to complete the following steps to create internationalized applications:
• Enable your code to handle strings from international character sets.
• Design your user interface to accommodate the changes that result from
localization.
• Isolate all resources that need to be localized.
~Character sets
The Western editions (including English, French, and German) of Windows use the
ANSI Latin-1 (1252) character set. However, other editions of Windows use different
character sets. For example, the Japanese version of Windows uses the Shift-JIS
character set (code page 932), which represents Japanese characters as multibyte
character codes.
There are generally three types of characters sets:
• Single-byte
• Multibyte
• Wide characters
Windows and Linux both support single-byte and multibyte character sets as well as
Unicode. With a single-byte character set, each byte in a string represents one
character. The ANSI character set used by many western operating systems is a
single-byte character set.
In a multibyte character set, some characters are represented by one byte and others
by more than one byte. The first byte of a multibyte character is called the lead byte.
In general, the lower 128 characters of a multibyte character set map to the 7-bit
ASCII characters, and any byte whose ordinal value is greater than 127 is the lead
byte of a multibyte character. Only single-byte characters can contain the null value
(#0). Multibyte character sets—especially double-byte character sets (DBCS)—are
widely used for Asian languages.
Remember that the length of the strings in bytes does not necessarily correspond to
the length of the string in characters. Be careful not to truncate strings by cutting a
multibyte character in half. Do not pass characters as a parameter to a function or
procedure, since the size of a character can’t be known up front. Instead, always pass
a pointer to a character or a string.
Wide characters
Another approach to working with ideographic character sets is to convert all
characters to a wide character encoding scheme such as Unicode. Unicode characters
and strings are also called wide characters and wide character strings. In the Unicode
character set, each character is represented by two bytes. Thus a Unicode string is a
sequence not of individual bytes but of two-byte words.
The first 256 Unicode characters map to the ANSI character set. The Windows
operating system supports Unicode (UCS-2). The Linux operating system supports
UCS-4, a superset of UCS-2. Delphi supports UCS-2 on both platforms. Because wide
characters are two bytes instead of one, the character set can represent many more
different characters.
Using a wide character encoding scheme has the advantage that you can make many
of the usual assumptions about strings that do not work for MBCS systems. There is a
direct relationship between the number of bytes in the string and the number of
characters in the string. You do not need to worry about cutting characters in half or
mistaking the second half of a character for the start of a different character.
The biggest disadvantage of working with wide characters is that Windows supports
a few wide character API function calls. Because of this, the VCL components
represent all string values as single byte or MBCS strings. Translating between the
wide character system and the MBCS system every time you set a string property or
read its value would require additional code and slow your application down.
However, you may want to translate into wide characters for some special string
processing algorithms that need to take advantage of the 1:1 mapping between
characters and WideChars.
BiDiMode property
The BiDiMode property controls the reading order for the text, the placement of the
vertical scrollbar, and whether the alignment is changed. Controls that have a text
property, such as Name, display the BiDiMode property on the Object Inspector.
The BiDiMode property is a new enumerated type, TBiDiMode, with four states:
bdLeftToRight, bdRightToLeft, bdRightToLeftNoAlign, and bdRightToLeftReadingOnly.
Note THintWindow picks up the BiDiMode of the control that activated the hint.
bdLeftToRight
bdLeftToRight draws text using left to right reading order. The alignment and scroll
bars are not changed. For instance, when entering right to left text, such as Arabic or
Hebrew, the cursor goes into push mode and the text is entered right to left. Latin
text, such as English or French, is entered left to right. bdLeftToRight is the default
value.
Figure 17.1 TListBox set to bdLeftToRight
bdRightToLeft
bdRightToLeft draws text using right to left reading order, the alignment is changed
and the scroll bar is moved. Text is entered as normal for right-to-left languages such
as Arabic or Hebrew. When the keyboard is changed to a Latin language, the cursor
goes into push mode and the text is entered left to right.
Figure 17.2 TListBox set to bdRightToLeft
bdRightToLeftNoAlign
bdRightToLeftNoAlign draws text using right to left reading order, the alignment is
not changed, and the scroll bar is moved.
Figure 17.3 TListBox set to bdRightToLeftNoAlign
bdRightToLeftReadingOnly
bdRightToLeftReadingOnly draws text using right to left reading order, and the
alignment and scroll bars are not changed.
Figure 17.4 TListBox set to bdRightToLeftReadingOnly
ParentBiDiMode property
ParentBiDiMode is a Boolean property. When True (the default) the control looks to its
parent to determine what BiDiMode to use. If the control is a TForm object, the form
uses the BiDiMode setting from Application. If all the ParentBiDiMode properties are
True, when you change Application’s BiDiMode property, all forms and controls in the
project are updated with the new setting.
FlipChildren method
The FlipChildren method allows you to flip the position of a container control’s
children. Container controls are controls that can accept other controls, such as
TForm, TPanel, and TGroupBox. FlipChildren has a single boolean parameter, AllLevels.
When False, only the immediate children of the container control are flipped. When
True, all the levels of children in the container control are flipped.
Delphi flips the controls by changing the Left property and the alignment of the
control. If a control’s left side is five pixels from the left edge of its parent control,
after it is flipped the edit control’s right side is five pixels from the right edge of the
parent control. If the edit control is left aligned, calling FlipChildren will make the
control right aligned.
To flip a control at design-time select Edit|Flip Children and select either All or
Selected, depending on whether you want to flip all the controls, or just the children
of the selected control. You can also flip a control by selecting the control on the form,
right-clicking, and selecting Flip Children from the context menu.
Note Selecting an edit control and issuing a Flip Children|Selected command does
nothing. This is because edit controls are not containers.
Additional methods
There are several other methods useful for developing applications for bi-directional
users.
Locale-specific features
You can add extra features to your application for specific locales. In particular, for
Asian language environments, you may want your application to control the input
method editor (IME) that is used to convert the keystrokes typed by the user into
character strings.
Controls offer support in programming the IME. Most windowed controls that work
directly with text input have an ImeName property that allows you to specify a
particular IME that should be used when the control has input focus. They also
provide an ImeMode property that specifies how the IME should convert keyboard
input. TWinControl introduces several protected methods that you can use to control
the IME from classes you define. In addition, the global Screen variable provides
information about the IMEs available on the user’s system.
The global Screen variable also provides information about the keyboard mapping
installed on the user’s system. You can use this to obtain locale-specific information
about the environment in which your application is running.
The IME is available in VCL applications only.
Text
All text that appears in the user interface must be translated. English text is almost
always shorter than its translations. Design the elements of your user interface that
display text so that there is room for the text strings to grow. Create dialogs, menus,
status bars, and other user interface elements that display text so that they can easily
display longer strings. Avoid abbreviations—they do not exist in languages that use
ideographic characters.
Short strings tend to grow in translation more than long phrases. Table 17.3 provides
a rough estimate of how much expansion you should plan for given the length of
your English strings:
Graphic images
Ideally, you will want to use images that do not require translation. Most obviously,
this means that graphic images should not include text, which will always require
translation. If you must include text in your images, it is a good idea to use a label
object with a transparent background over an image rather than including the text as
part of the image.
There are other considerations when creating graphic images. Try to avoid images
that are specific to a particular culture. For example, mailboxes in different countries
look very different from each other. Religious symbols are not appropriate if your
application is intended for countries that have different dominant religions. Even
color can have different symbolic connotations in different cultures.
Keyboard mappings
Be careful with key-combinations shortcut assignments. Not all the characters
available on the US keyboard are easily reproduced on all international keyboards.
Where possible, use number keys and function keys for shortcuts, as these are
available on virtually all keyboards.
Isolating resources
The most obvious task in localizing an application is translating the strings that
appear in the user interface. To create an application that can be translated without
altering code everywhere, the strings in the user interface should be isolated into a
single module. Delphi automatically creates a .dfm (.xfm in CLX applications) file
that contains the resources for your menus, dialogs, and bitmaps.
In addition to these obvious user interface elements, you will need to isolate any
strings, such as error messages, that you present to the user. String resources are not
included in the form file. You can isolate them by declaring constants for them using
the resourcestring keyword. For more information about resource string constants,
see the Delphi Language Guide. It is best to include all resource strings in a single,
separate unit.
var
LCID: Integer;
begin
LCID := StrToInt('$' + Copy(Name, 5, 4));
Form1.LocaleList.Items.Add(GetLocaleData(LCID, LOCALE_SLANGUAGE));
Result := Bool(1);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
begin
with Languages do
begin
for I := 0 to Count - 1 do
begin
ListBox1.Items.Add(Name[I]);
end;
end;
end;
For example, the following procedure can be used in an install or setup program to
set the registry key value that indicates the locale to use when loading applications:
procedure SetLocalOverrides(FileName: string, LocaleOverride: string);
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
try
if Reg.OpenKey(‘Software\Borland\Locales’, True) then
Reg.WriteString(LocalOverride, FileName);
finally
Reg.Free;
end;
end;
Within your application, use the global FindResourceHInstance function to obtain the
handle of the current resource module. For example:
LoadStr(FindResourceHInstance(HInstance), IDS_AmountDueName, szQuery, SizeOf(szQuery));
You can ship a single application that adapts itself automatically to the locale of the
system it is running on, simply by providing the appropriate resource DLLs.
Localizing applications
Once your application is internationalized, you can create localized versions for the
different foreign markets in which you want to distribute it.
Localizing resources
Ideally, your resources have been isolated into a resource DLL that contains form
files (.dfm in VCL applications or .xfm in CLX applications) and a resource file. You
can open your forms in the IDE and translate the relevant properties.
Note In a resource DLL project, you cannot add or delete components. It is possible,
however, to change properties in ways that could cause runtime errors, so be careful
to modify only those properties that require translation. To avoid mistakes, you can
configure the Object Inspector to display only Localizable properties; to do so, right-
click in the Object Inspector and use the View menu to filter out unwanted property
categories.
You can open the RC file and translate relevant strings. Use the StringTable editor by
opening the RC file from the Project Manager.
18
Deploying applications
Chapter18
Once your application is up and running, you can deploy it. That is, you can make it
available for others to run. A number of steps must be taken to deploy an application
to another computer so that the application is completely functional. The steps
required by a given application vary, depending on the type of application. The
following sections describe these steps when deploying the following applications:
• Deploying general applications
• Deploying CLX applications
• Deploying database applications
• Deploying Web applications
• Programming for varying host environments
• Software license requirements
Note Information included in these sections is for deploying applications on Windows. To
deploy a cross-platform applications on Linux, refer to your Kylix documentation.
Database and Web applications require additional installation steps. For additional
information on installing database applications, see “Deploying database
applications” on page 18-6. For more information on installing Web applications, see
“Deploying Web applications” on page 18-9. For more information on installing
ActiveX controls, see “Deploying an ActiveX control on the Web” on page 45-15.
Application files
The following types of files may need to be distributed with an application.
Package files
If the application uses runtime packages, those package files need to be distributed
with the application. InstallShield Express handles the installation of package files
the same as DLLs, copying the files and making necessary entries in the Windows
registry. You can also use merge modules for deploying runtime packages with MSI-
based setup tools including InstallShield Express. See the next section for details.
Borland recommends installing the runtime package files supplied by Borland in the
Windows\System directory. This serves as a common location so that multiple
applications would have access to a single instance of the files. For packages you
created, it is recommended that you install them in the same directory as the
application. Only the .bpl files need to be distributed.
Note If deploying packages with CLX applications, you need to include clx70.bpl rather
than vcl70.bpl.
If you are distributing packages to other developers, supply the .bpl and .dcp files.
Merge modules
InstallShield Express 3.0 is based on Windows Installer (MSI) technology. With MSI-
based setup tools such as InstallShield Express, you can use merge modules for
deploying runtime packages. Merge modules provide a standard method that you
can use to deliver shared code, files, resources, Registry entries, and setup logic to
applications as a single compound file.
The runtime libraries have some interdependencies because of the way they are
grouped together. The result of this is that when one package is added to an install
project, the install tool automatically adds or reports a dependency on one or more
other packages. For example, if you add the VCLInternet merge module to an install
project, the install tool should also automatically add or report a dependency on the
VCLDatabase and StandardVCL modules.
The dependencies for each merge module are listed in the table below. The various
install tools may react to these dependencies differently. The InstallShield for
Windows Installer automatically adds the required modules if it can find them.
Other tools may simply report a dependency or may generate a build failure if all
required modules are not included in the project.
ActiveX controls
Certain components bundled with Delphi are ActiveX controls. The component
wrapper is linked into the application’s executable file (or a runtime package), but
the .ocx file for the component also needs to be deployed with the application. These
components include:
• Chart FX, copyright SoftwareFX Inc.
• VisualSpeller Control, copyright Visual Components, Inc.
• Formula One (spreadsheet), copyright Visual Components, Inc.
• First Impression (VtChart), copyright Visual Components, Inc.
• Graph Custom Control, copyright Bits Per Second Ltd.
ActiveX controls that you create need to be registered on the deployment computer
before use. Installation programs such as InstallShield Express automate this
registration process. To manually register an ActiveX control, choose Run|ActiveX
Server in the IDE, use the TRegSvr demo application in \Demos\ActiveX or use the
Microsoft utility REGSRV32.EXE (not included with Windows 9x versions).
DLLs that support an ActiveX control also need to be distributed with an application.
Helper applications
Helper applications are separate programs without which your application would be
partially or completely unable to function. Helper applications may be those
supplied with the operating system, by Borland, or by third-party products. An
example of a helper application is the InterBase utility program Server Manager,
which administers InterBase databases, users, and security.
If an application depends on a helper program, be sure to deploy it with your
application, where possible. Distribution of helper programs may be governed by
redistribution license agreements. Consult the helper program documentation for
specific information.
DLL locations
You can install DLL files used only by a single application in the same directory as
the application. DLLs that will be used by a number of applications should be
installed in a location accessible to all of those applications. A common convention
for locating such community DLLs is to place them either in the Windows or the
Windows\System directory. A better way is to create a dedicated directory for the
common .DLL files, similar to the way the Borland Database Engine is installed.
Note For database applications using Informix or MSSQL, you cannot deploy a stand-
alone executable. Instead, deploy an executable file with the driver DLL (listed in the
table following).
If you are not deploying a stand-alone executable, you can deploy associated
dbExpress drivers and DataSnap DLLs with your executable. The following table
lists the appropriate DLLs and when to include them:
See Chapter 28, “Using unidirectional datasets” for more information about using the
dbExpress components.
You should use InstallShield Express (or other certified installation program) for
installing the BDE. InstallShield Express creates the necessary registry entries and
defines any aliases the application may require. Using a certified installation
program to deploy the BDE files and subsets is important because:
• Improper installation of the BDE or BDE subsets can cause other applications
using the BDE to fail. Such applications include not only Borland products, but
many third-party programs that use the BDE.
• Under 32-bit Windows 95/NT and later, BDE configuration information is stored
in the Windows registry instead of .ini files, as was the case under 16-bit
Windows. Making the correct entries and deletions for install and uninstall is a
complex task.
It is possible to install only as much of the BDE as an application actually needs. For
instance, if an application only uses Paradox tables, it is only necessary to install that
portion of the BDE required to access Paradox tables. This reduces the disk space
needed for an application. Certified installation programs, like InstallShield Express,
are capable of performing partial BDE installations. Be sure to leave BDE system files
that are not used by the deployed application, but that are needed by other
programs.
• Security for the directories should be set so that the application can access all
needed database files.
• The directory containing an application must have read and execute attributes.
• The application should not use hard-coded paths for accessing database or other
files.
• The location of an ActiveX control is indicated by the CODEBASE parameter of
the <OBJECT> HTML tag.
For information on deploying database Web applications, see “Deploying database
applications” on page 18-6.
Enabling modules
Your DLLs should be physically located in the Apache Modules subdirectory.
Two modifications to httpd.conf are required to enable a module.
1 Add a LoadModule entry to let Apache locate and load your DLL. For example:
LoadModule MyApache_module modules/Project1.dll
Replace MyApache_module with the exported module name from your DLL. To find the
module name, in your project source, look for the exports line. For example:
exports
apache_module name ‘MyApache_module’;
2 Add a resource locator entry (may be added anywhere in httpd.conf after the
LoadModule entry). For example:
# Sample location specification for a project named project1.
<Location /project1>
SetHandler project1-handler
</Location>
This allows all requests to http://www.somedomain.com/project1 to be passed on
to the Apache module.
The SetHandler directive specifies the Web server application that handles the
request. The SetHandler argument should be set to the value of the ContentType
global variable.
CGI applications
When creating CGI applications, the physical directory (specified in the Directory
directive of the httpd.conf file) must have the ExecCGI option and the SetHandler
clause set to allow execution of programs so the CGI script can be executed. To
ensure that permissions are set up properly, use the Alias directive with both
Options ExecCGI and SetHandler enabled.
Note An alternative approach is to use the ScriptAlias directive (without Options
ExecCGI), but using this approach can prevent your CGI application from reading
any files in the ScriptAlias directory.
The following httpd.conf line is an example of using the Alias directive to create a
virtual directory on your server and mark the exact location of your CGI script:
Alias/MyWeb/"c:/httpd/docs/MyWeb/"
This would allow requests such as /MyWeb/mycgi.exe to be satisfied by running the
script c:\httpd\docs\MyWeb\mycgi.exe.
You can also set Options to All or to ExecCGI using the Directory directive in
httpd.conf. The Options directive controls which server features are available in a
particular directory.
Directory directives are used to enclose a set of directives that apply to the named
directory and its subdirectories. An example of the Directory directive is shown
below:
<Directory "c:/httpd/docs/MyWeb">
AllowOverride None
Options ExecCGI
Order allow,deny
Allow from all
AddHandler cgi-script exe cgi
</Directory>
In this example, Options is set to ExecCGI permitting execution of CGI scripts in the
directory MyWeb. The AddHandler clause lets Apache know that files with
extensions such as exe and cgi are CGI scripts (executables).
Note Apache executes locally on the server within the account specified in the User
directive in the httpd.conf file. Make sure that the user has the appropriate rights to
access the resources needed by the application.
See the Apache LICENSE file, included with your Apache distribution, for additional
deployment information. For additional Apache configuration information, see
http://www.apache.org.
• Some visual controls, such as TLabel and TEdit, dynamically resize when the size
of the font for the control changes. This can affect deployed applications when
forms and controls are dynamically resized. The resizing of the control due to font
size changes are in addition to size changes due to proportional resizing for screen
resolutions. This effect is offset by setting the AutoSize property of these controls to
False.
• Avoid making use of explicit pixel coordinates, such as when drawing directly to a
canvas. Instead, modify the coordinates by a ratio proportionate to the screen
resolution difference ratio between the development and user computers. For
example, if the application draws a rectangle to a canvas ten pixels high by twenty
wide, instead multiply the ten and twenty by the screen resolution difference ratio.
This ensures that the rectangle visually appears the same size under different
screen resolutions.
Fonts
Windows comes with a standard set of TrueType and raster fonts. Linux comes with
a standard set of fonts, depending on the distribution. When designing an
application to be deployed on other computers, realize that not all computers have
fonts outside the standard sets.
Text components used in the application should all use fonts that are likely to be
available on all deployment computers.
When use of a nonstandard font is absolutely necessary in an application, you need
to distribute that font with the application. Either the installation program or the
application itself must install the font on the deployment computer. Distribution of
third-party fonts may be subject to limitations imposed by the font creator.
Windows has a safety measure to account for attempts to use a font that does not
exist on the computer. It substitutes another, existing font that it considers the closest
match. While this may circumvent errors concerning missing fonts, the end result
may be a degradation of the visual appearance of the application. It is better to
prepare for this eventuality at design time.
To make a nonstandard font available to a Windows application, use the Windows
API functions AddFontResource and DeleteFontResource. Deploy the .fot file for the
nonstandard font with the application.
DEPLOY
The DEPLOY document covers the some of the legal aspects of distributing of
various components and utilities, and other product areas that can be part of or
associated with a Delphi application. The DEPLOY document is installed in the main
Delphi directory. The topics covered include:
• .exe, .dll, and .bpl files
• Components and design-time packages
• Borland Database Engine (BDE)
• ActiveX controls
• Sample images
README
The README document contains last minute information about Delphi, possibly
including information that could affect the redistribution rights for components, or
utilities, or other product areas. The README document is installed in the main
Delphi directory.
II
Developing database applications
Part II
Using databases
Delphi includes many components for accessing databases and representing the
information they contain. They are grouped according to the data access mechanism:
• The BDE page of the Component palette contains components that use the Borland
Database Engine (BDE). The BDE defines a large API for interacting with
databases. Of all the data access mechanisms, the BDE supports the broadest range
of functions and comes with the most supporting utilities. It is the best way to
work with data in Paradox or dBASE tables. However, it is also the most
complicated mechanism to deploy. For more information about using the BDE
components, see Chapter 26, “Using the Borland Database Engine.”
• The ADO page of the Component palette contains components that use ActiveX
Data Objects (ADO) to access database information through OLEDB. ADO is a
Microsoft Standard. There is a broad range of ADO drivers available for
connecting to different database servers. Using ADO-based components lets you
Types of databases
Relational database servers vary in the way they store information and in the way
they allow multiple users to access that information simultaneously. Delphi provides
support for two types of relational database server:
• Remote database servers reside on a separate machine. Sometimes, the data from
a remote database server does not even reside on a single machine, but is
distributed over several servers. Although remote database servers vary in the
way they store information, they provide a common logical interface to clients.
This common interface is Structured Query Language (SQL). Because you access
them using SQL, they are sometimes called SQL servers. (Another name is Remote
Database Management system, or RDBMS.) In addition to the common commands
that make up SQL, most remote database servers support a unique “dialect” of
SQL. Examples of SQL servers include InterBase, Oracle, Sybase, Informix,
Microsoft SQL server, and DB2.
• Local databases reside on your local drive or on a local area network. They often
have proprietary APIs for accessing the data. When they are shared by several
users, they use file-based locking mechanisms. Because of this, they are sometimes
called file-based databases. Examples of local databases include Paradox, dBASE,
FoxPro, and Access.
Applications that use local databases are called single-tiered applications because
the application and the database share a single file system. Applications that use
remote database servers are called two-tiered applications or multi-tiered
applications because the application and the database operate on independent
systems (or tiers).
Choosing the type of database to use depends on several factors. For example, your
data may already be stored in an existing database. If you are creating the database
tables your application uses, you may want to consider the following questions:
• How many users will be sharing these tables? Remote database servers are
designed for access by several users at the same time. They provide support for
multiple users through a mechanism called transactions. Some local databases
(such as Local InterBase) also provide transaction support, but many only provide
file-based locking mechanisms, and some (such as client dataset files) provide no
multi-user support at all.
• How much data will the tables hold? Remote database servers can hold more data
than local databases. Some remote database servers are designed for warehousing
large quantities of data while others are optimized for other criteria (such as fast
updates).
• What type of performance (speed) do you require from the database? Local
databases are usually faster than remote database servers because they reside on
the same system as the database application. Different remote database servers are
optimized to support different types of operations, so you may want to consider
performance when choosing a remote database server.
• What type of support will be available for database administration? Local
databases require less support than remote database servers. Typically, they are
less expensive to operate because they do not require separately installed servers
or expensive site licenses.
Database security
Databases often contain sensitive information. Different databases provide security
schemes for protecting that information. Some databases, such as Paradox and
dBASE, only provide security at the table or field level. When users try to access
protected tables, they are required to provide a password. Once users have been
authenticated, they can see only those fields (columns) for which they have
permission.
Most SQL servers require a password and user name to use the database server at all.
Once the user has logged in to the database, that username and password determine
which tables can be used. For information on providing passwords to SQL servers,
see “Controlling server login” on page 23-4.
When designing database applications, you must consider what type of
authentication is required by your database server. Often, applications are designed
to hide the explicit database login from users, who need only log in to the application
itself. If you do not want to require your users to provide a database password, you
must either use a database that does not require one or you must provide the
password and username to the server programmatically. When providing the
password programmatically, care must be taken that security can’t be breached by
reading the password from the application.
If you require your user to supply a password, you must consider when the
password is required. If you are using a local database but intend to scale up to a
larger SQL server later, you may want to prompt for the password at the point when
you will eventually log in to the SQL database, rather than when opening individual
tables.
If your application requires multiple passwords because you must log in to several
protected systems or databases, you can have your users provide a single master
password that is used to access a table of passwords required by the protected
systems. The application then supplies passwords programmatically, without
requiring the user to provide multiple passwords.
In multi-tiered applications, you may want to use a different security model
altogether. You can use HTTPs, CORBA, or COM+ to control access to middle tiers,
and let the middle tiers handle all details of logging into database servers.
Transactions
A transaction is a group of actions that must all be carried out successfully on one or
more tables in a database before they are committed (made permanent). If any of the
actions in the group fails, then all actions are rolled back (undone).
Transactions ensure that
• All updates in a single transaction are either committed or aborted and rolled back
to their previous state. This is referred to as atomicity.
• A transaction is a correct transformation of the system state, preserving the state
invariants. This is referred to as consistency.
Database architecture
Database applications are built from user interface elements, components that
represent database information (datasets), and components that connect these to each
other and to the source of the database information. How you organize these pieces is
the architecture of your database application.
General structure
While there are many distinct ways to organize the components in a database
application, most follow the general scheme illustrated in Figure 19.1:
Figure 19.1 Generic Database Architecture
Data module
UI
Data source Dataset Connection
to data
The dataset
The heart of your database application is the dataset. This component represents a set
of records from the underlying database. These records can be the data from a single
database table, a subset of the fields or records in a table, or information from more
than one table joined into a single view. By using datasets, your application logic is
buffered from restructuring of the physical tables in your databases. When the
underlying database changes, you might need to alter the way the dataset
component specifies the data it contains, but the rest of your application can continue
to work without alteration. For more information on the common properties and
methods of datasets, see Chapter 24, “Understanding datasets.”
Client application
Data module
UI
Data source Connection
Dataset
component
Database server
Each type of dataset uses its own type of connection component, which represents a
single data access mechanism:
• If the dataset is a BDE dataset such as TTable, TQuery, or TStoredProc, the
connection component is a TDataBase object. You connect the dataset to the
database component by setting its Database property. You do not need to explicitly
add a database component when using BDE dataset. If you set the dataset’s
DatabaseName property, a database component is created for you automatically at
runtime.
• If the dataset is an ADO dataset such as TADODataSet, TADOTable, TADOQuery,
or TADOStoredProc, the connection component is a TADOConnection object. You
connect the dataset to the ADO connection component by setting its Connection
property. As with BDE datasets, you do not need to explicitly add the connection
component: instead you can set the dataset’s ConnectionString property.
Data module
UI
Data source Client dataset File
When using this file-based approach, your application writes changes to disk using
the client dataset’s SaveToFile method. SaveToFile takes one parameter, the name of
the file which is created (or overwritten) containing the table. When you want to read
a table previously written using the SaveToFile method, use the LoadFromFile method.
LoadFromFile also takes one parameter, the name of the file containing the table.
If you always load to and save from the same file, you can use the FileName property
instead of the SaveToFile and LoadFromFile methods. When FileName is set to a valid
file name, the data is automatically loaded from the file when the client dataset is
opened and saved to the file when the client dataset is closed.
This simple file-based architecture is a single-tiered application. The logic that
manipulates database information is in the same application that implements the
user interface, although isolated into a data module.
The file-based approach has the benefit of simplicity. There is no database server to
install, configure, or deploy (If you do not statically link in midaslib.dcu, the client
dataset does require midas.dll). There is no need for site licenses or database
administration.
In addition, some versions of Delphi let you convert between arbitrary XML
documents and the data packets that are used by a client dataset. Thus, the file-based
approach can be used to work with XML documents as well as dedicated datasets.
For information about converting between XML documents and client dataset data
packets, see Chapter 32, “Using XML in database applications.”
The file-based approach offers no support for multiple users. The dataset should be
dedicated entirely to the application. Data is saved to files on disk, and loaded at a
later time, but there is no built-in protection to prevent multiple users from
overwriting each other’s data files.
For more information about using a client dataset with data stored on disk, see
“Using a client dataset with file-based data” on page 29-33.
• Client datasets can apply edits directly to a database server when the dataset is
read-only. When using dbExpress, this is the only way to edit the data in the dataset
(it is also the only way to navigate freely in the data when using dbExpress). Even
when not using dbExpress, the results of some queries and all stored procedures
are read-only. Using a client dataset provides a standard way to make such data
editable.
• Because client datasets can work directly with dedicated files on disk, using a
client dataset can be combined with a file-based model to allow for a flexible
“briefcase” application. For information on the briefcase model, see “Combining
approaches” on page 19-14.
In addition to these specialized client datasets, there is a generic client dataset
(TClientDataSet), which does not include an internal dataset and dataset provider.
Although TClientDataSet has no built-in database access mechanism, you can connect
it to another, external, dataset from which it fetches data and to which it sends
updates. Although this approach is a bit more complicated, there are times when it is
preferable:
• Because the source dataset and dataset provider are external, you have more
control over how they fetch data and apply updates. For example, the provider
component surfaces a number of events that are not available when using a
specialized client dataset to access data.
• When the source dataset is external, you can link it in a master/detail relationship
with another dataset. An external provider automatically converts this
arrangement into a single dataset with nested details. When the source dataset is
internal, you can’t create nested detail sets this way.
• Connecting a client dataset to an external dataset is an architecture that easily
scales up to multiple tiers. Because the development process can get more
involved and expensive as the number of tiers increases, you may want to start
developing your application as a single-tiered or two-tiered application. As the
amount of data, the number of users, and the number of different applications
accessing the data grows, you may later need to scale up to a multi-tiered
architecture. If you think you may eventually use a multi-tiered architecture, it can
be worthwhile to start by using a client dataset with an external source dataset.
This way, when you move the data access and manipulation logic to a middle tier,
you protect your development investment because the code can be reused as your
application grows.
• TClientDataSet can link to any source dataset. This means you can use custom
datasets (third-party components) for which there is no corresponding specialized
client dataset. Some versions of Delphi even include special provider components
that connect a client dataset to an XML document rather than another dataset.
(This works the same way as connecting a client dataset to another (source)
dataset, except that the XML provider uses an XML document rather than a
dataset. For information about these XML providers, see “Using an XML
document as the source for a provider” on page 32-8.)
There are two versions of the architecture that connects a client dataset to an external
dataset:
• Connecting a client dataset to another dataset in the same application.
• Using a multi-tiered architecture.
Client application
Data module
UI
Data source Client dataset
Connection
Dataset Provider
component
Database server
For more information on using a client dataset with a provider, see “Using a client
dataset with a provider” on page 29-24.
UI
Application server
Unidirectional SQL
Provider dataset connection
Database server
The preceding figure represents three-tiered application. The logic that manipulates
database information is on a separate system, or tier. This middle tier centralizes the
logic that governs your database interactions so there is centralized control over data
relationships. This allows different client applications to use the same data, while
ensuring consistent data logic. It also allows for smaller client applications because
much of the processing is off-loaded onto the middle tier. These smaller client
applications are easier to install, configure, and maintain. Multi-tiered applications
can also improve performance by spreading data-processing over several systems.
The multi-tiered architecture is very similar to the previous model. It differs mainly
in that source dataset that connects to the database server and the provider that acts
as an intermediary between that source dataset and the client dataset have both
moved to a separate application. That separate application is called the application
server (or sometimes the “remote data broker”).
Because the provider has moved to a separate application, the client dataset can no
longer connect to the source dataset by simply setting its ProviderName property. In
addition, it must use some type of connection component to locate and connect to the
application server.
There are several types of connection components that can connect a client dataset to
an application server. They are all descendants of TCustomRemoteServer, and differ
primarily in the communication protocol they use (TCP/IP, HTTP, DCOM, SOAP, or
CORBA). Link the client dataset to its connection component by setting the
RemoteServer property.
The connection component establishes a connection to the application server and
returns an interface that the client dataset uses to call the provider specified by its
ProviderName property. Each time the client dataset calls the application server, it
passes the value of ProviderName, and the application server forwards the call to the
provider.
For more information about connecting a client dataset to an application server, see
Chapter 31, “Creating multi-tiered applications.”
Combining approaches
The previous sections describe several architectures you can use when writing
database applications. There is no reason, however, why you can’t combine two or
more of the available architectures in a single application. In fact, some combinations
can be extremely powerful.
For example, you can combine the disk-based architecture described in “Using a
dedicated file on disk” on page 19-9 with another approach such as those described
in “Connecting a client dataset to another dataset in the same application” on
page 19-12 or “Using a multi-tiered architecture” on page 19-13. These combinations
are easy because all models use a client dataset to represent the data that appears in
the user interface. The result is called the briefcase model (or sometimes the
disconnected model, or mobile computing).
The briefcase model is useful in a situation such as the following: An onsite company
database contains customer contact data that sales representatives can use and
update in the field. While onsite, sales representatives download information from
the database. Later, they work with it on their laptops as they fly across the country,
and even update records at existing or new customer sites. When the sales
representatives return onsite, they upload their data changes to the company
database for everyone to use.
When operating on site, the client dataset in a briefcase model application fetches its
data from a provider. The client dataset is therefore connected to the database server
and can, through the provider, fetch server data and send updates back to the server.
Before disconnecting from the provider, the client dataset saves its snapshot of the
information to a file on disk. While offsite, the client dataset loads its data from the
file, and saves any changes back to that file. Finally, back onsite, the client dataset
reconnects to the provider so that it can apply its updates to the database server or
refresh its snapshot of the data.
Analyzing data
Some database applications do not present database information directly to the user.
Instead, they analyze and summarize information from databases so that users can
draw conclusions from the data.
The TDBChart component on the Data Controls page of the Component palette lets
you present database information in a graphical format that enables users to quickly
grasp the import of database information.
In addition, some versions of Delphi include a Decision Cube page on the
Component palette. It contains six components that let you perform data analysis
and cross-tabulations on data when building decision support applications. For more
information about using the Decision Cube components, see Chapter 22, “Using
decision support components.”
If you want to build your own components that display data summaries based on
various grouping criteria, you can use maintained aggregates with a client dataset.
For more information about using maintained aggregates, see “Using maintained
aggregates” on page 29-11.
Writing reports
If you want to let your users print database information from the datasets in your
application, you can use Rave Reports, as described in Chapter 21, “Creating reports
with Rave Reports.”
20
Using data controls
Chapter20
The Data Controls page of the Component palette provides a set of data-aware
controls that represent data from fields in a database record, and, if the dataset allows
it, enable users to edit that data and post changes back to the database. By placing
data controls onto the forms in your database application, you can build your
database application’s user interface (UI) so that information is visible and accessible
to users.
The data-aware controls you add to your user interface depend on several factors,
including the following:
• The type of data you are displaying. You can choose between controls that are
designed to display and edit plain text, controls that work with formatted text,
controls for graphics, multimedia elements, and so on. Controls that display
different types of information are described in “Displaying a single record” on
page 20-7.
• How you want to organize the information. You may choose to display
information from a single record on the screen, or list the information from
multiple records using a grid. “Choosing how to organize the data” on page 20-7
describes some of the possibilities.
• The type of dataset that supplies data to the controls. You want to use controls that
reflect the limitations of the underlying dataset. For example, you would not use a
grid with a unidirectional dataset because unidirectional datasets can only supply
a single record at a time.
• How (or if) you want to let users navigate through the records of datasets and add
or edit data. You may want to add your own controls or mechanisms to navigate
and edit, or you may want to use a built-in control such as a data navigator. For
more information about using a data navigator, see “Navigating and
manipulating records” on page 20-29.
Note More complex data-aware controls for decision support are discussed in Chapter 22,
“Using decision support components.”
Regardless of the data-aware controls you choose to add to your interface, certain
common features apply. These are described below.
Data controls are data-aware at design time. When you associate the data control
with an active dataset while building an application, you can immediately see live
data in the control. You can use the Fields editor to scroll through a dataset at design
time to verify that your application displays data correctly without having to compile
and run the application. For more information about the Fields editor, see “Creating
persistent fields” on page 25-4.
At runtime, data controls display data and, if your application, the control, and the
dataset all permit it, a user can edit data through the control.
The OnStateChange event occurs when the state of the dataset changes. When this
event occurs, you can examine the dataset’s State property to determine its current
state.
For example, the following OnStateChange event handler enables or disables buttons
or menu items based on the current state:
procedure Form1.DataSource1.StateChange(Sender: TObject);
begin
CustTableEditBtn.Enabled := (CustTable.State = dsBrowse);
CustTableCancelBtn.Enabled := CustTable.State in [dsInsert, dsEdit, dsSetKey];
CustTableActivateBtn.Enabled := CustTable.State in [dsInactive];
ƒ
end;
Note For more information about dataset states, see “Determining dataset states” on
page 24-3.
Finally, you can control whether the user can even enter edits to the data that is
displayed in the control. The ReadOnly property of the data control determines if a
user can edit the data displayed by the control. If False (the default), users can edit
data. Clearly, you will want to ensure that the control’s ReadOnly property is True
when the dataset’s CanModify property is False. Otherwise, you give users the false
impression that they can affect the data in the underlying database table.
In all data controls except TDBGrid, when you modify a field, the modification is
copied to the underlying dataset when you Tab from the control. If you press Esc
before you Tab from a field, the data control abandons the modifications, and the
value of the field reverts to the value it held before any modifications were made.
In TDBGrid, modifications are posted when you move to a different record; you can
press Esc in any record of a field before moving to another record to cancel all
changes to the record.
When a record is posted, Delphi checks all data-aware controls associated with the
dataset for a change in status. If there is a problem updating any fields that contain
modified data, Delphi raises an exception, and no modifications are made to the
record.
Note If your application caches updates (for example, using a client dataset), all
modifications are posted to an internal cache. These modifications are not applied to
the underlying database table until you call the dataset’s ApplyUpdates method.
finally
CustTable.EnableControls;
end;
Applications that display a single record are usually easy to read and understand,
because all database information is about the same thing (in the previous case, the
same order). The data-aware controls in these user interfaces represent a single field
from a database record. The Data Controls page of the Component palette provides a
wide selection of controls to represent different kinds of fields. These controls are
typically data-aware versions of other controls that are available on the Component
palette. For example, the TDBEdit control is a data-aware version of the standard
TEdit control which enables users to see and edit a text string.
Which control you use depends on the type of data (text, formatted text, graphics,
boolean information, and so on) contained in the field.
Because the TDBRichEdit can display large amounts of data, it can take time to
populate the display at runtime. To reduce the time it takes to scroll through data
records, TDBRichEdit has an AutoDisplay property that controls whether the accessed
data should be displayed automatically. If you set AutoDisplay to False, TDBRichEdit
displays the field name rather than actual data. Double-click inside the control to
view the actual data.
Note At runtime, users can use an incremental search to find list box items. When the
control has focus, for example, typing ‘ROB’ selects the first item in the list box
beginning with the letters ‘ROB’. Typing an additional ‘E’ selects the first item
starting with ‘ROBE’, such as ‘Robert Johnson’. The search is case-insensitive.
Backspace and Esc cancel the current search string (but leave the selection intact), as
does a two second pause between keystrokes.
d Choose a field to use as a lookup key from the drop-down list for the KeyField
property. The drop-down list displays fields for the dataset associated with
data source you specified in Step 3. The field you choose need not be part of an
index, but if it is, lookup performance is even faster.
e Choose a field whose values to return from the drop-down list for the ListField
property. The drop-down list displays fields for the dataset associated with the
data source you specified in Step 3.
When you activate a table associated with a lookup control, the control recognizes
that its list items are derived from a secondary source, and displays the
appropriate values from that source.
To specify the number of items that appear at one time in a TDBLookupListBox
control, use the RowCount property. The height of the list box is adjusted to fit this
row count exactly.
To specify the number of items that appear in the drop-down list of
TDBLookupComboBox, use the DropDownRows property instead.
Note You can also set up a column in a data grid to act as a lookup combo box. For
information on how to do this, see “Defining a lookup list column” on page 20-21.
Set the ValueUnchecked property to a value the control should post to the database if
the control is not checked when the user moves to another record. By default, this
value is set to “false,” but you can make it any alphanumeric value appropriate to
your needs. You can also enter a semicolon-delimited list of items as the value of
ValueUnchecked. If any of the items matches the contents of that field in the current
record, the check box is unchecked.
A data-aware check box is disabled whenever the field for the current record does
not contain one of the values listed in the ValueChecked or ValueUnchecked properties.
If the field with which a check box is associated is a logical field, the check box is
always checked if the contents of the field is True, and it is unchecked if the contents
of the field is False. In this case, strings entered in the ValueChecked and
ValueUnchecked properties have no effect on logical fields.
To display multiple records, use a grid control. Grid controls provide a multi-field,
multi-record view of data that can make your application’s user interface more
compelling and effective. They are discussed in “Viewing and editing data with
TDBGrid” on page 20-15 and “Creating a grid that contains other data-aware
controls” on page 20-28.
Note You can’t display multiple records when using a unidirectional dataset.
You may want to design a user interface that displays both fields from a single record
and grids that represent multiple records. There are two models that combine these
two approaches:
• Master-detail forms: You can represent information from both a master table and
a detail table by including both controls that display a single field and grid
controls. For example, you could display information about a single customer with
a detail grid that displays the orders for that customer. For information about
linking the underlying tables in a master-detail form, see “Creating master/detail
relationships” on page 24-35 and “Establishing master/detail relationships using
parameters” on page 24-47.
• Drill-down forms: In a form that displays multiple records, you can include single
field controls that display detailed information from the current record only. This
approach is particularly useful when the records include long memos or graphic
information. As the user scrolls through the records of the grid, the memo or
graphic updates to represent the value of the current record. Setting this up is very
easy. The synchronization between the two displays is automatic if the grid and
the memo or image control share a common data source.
Tip It is generally not a good idea to combine these two approaches on a single form. It is
usually confusing for users to understand the data relationships in such forms.
Record
indicator
If a grid’s dataset consists of dynamic field components, the fields are destroyed each
time the dataset is closed. When the field components are destroyed, all dynamic
columns associated with them are destroyed as well. If a grid’s dataset consists of
persistent field components, the field components exist even when the dataset is
closed, so the columns associated with those fields also retain their properties when
the dataset is closed.
Note Changing a grid’s Columns.State property to csDefault at runtime deletes all column
objects in the grid (even persistent columns), and rebuilds dynamic columns based
on the visible fields of the grid’s dataset.
cells. For example, you can use a blank column to display aggregated values on the
last record of a group of records that the aggregate summarizes. Another possibility
is to display a bitmap or bar chart that graphically depicts some aspect of the record’s
data.
Two or more persistent columns can be associated with the same field in a dataset.
For example, you might display a part number field at the left and right extremes of a
wide grid to make it easier to find the part number without having to scroll the grid.
Note Because persistent columns do not have to be associated with a field in a dataset, and
because multiple columns can reference the same field, a customized grid’s
FieldCount property can be less than or equal to the grid’s column count. Also note
that if the currently selected column in a customized grid is not associated with a
field, the grid’s SelectedField property is NULL and the SelectedIndex property is –1.
Persistent columns can be configured to display grid cells as a combo box drop-down
list of lookup values from another dataset or from a static pick list, or as an ellipsis
button (…) in a cell that can be clicked upon to launch special data viewers or dialogs
related to the current cell.
You can also change the column order at runtime by clicking on the column title and
dragging the column to a new position.
Note Reordering persistent fields in the Fields editor also reorders columns in a default
grid, but not a custom grid.
Important You cannot reorder columns in grids containing both dynamic columns and dynamic
fields at design time, since there is nothing persistent to record the altered field or
column order.
At runtime, a user can use the mouse to drag a column to a new location in the grid if
its DragMode property is set to dmManual. Reordering the columns of a grid with a
State property of csDefault state also reorders field components in the dataset
underlying the grid. The order of fields in the physical table is not affected. To
prevent a user from rearranging columns at runtime, set the grid’s DragMode
property to dmAutomatic.
At runtime, the grid’s OnColumnMoved event fires after a column has been moved.
The following table summarizes the options you can specify for the Title property.
• It can display composite fields in a single column, reflecting the fact that they are a
single field. When displaying composite fields in a single column, the column can
be expanded and collapsed by clicking on the arrow in the title bar of the field, or
by setting the Expanded property of the column:
• When a column is expanded, each child field appears in its own sub-column
with a title bar that appears below the title bar of the parent field. That is, the
title bar for the grid increases in height, with the first row giving the name of
the composite field, and the second row subdividing that for the individual
parts. Fields that are not composites appear with title bars that are extra high.
This expansion continues for constituents that are in turn composite fields (for
example, a detail table nested in a detail table), with the title bar growing in
height accordingly.
• When the field is collapsed, only one column appears with an uneditable
comma delimited string containing the child fields.
To display a composite field in an expanding and collapsing column, set the
dataset’s ObjectView property to True. The dataset stores the composite field as a
single field component that contains a set of nested sub-fields. The grid reflects
this in a column that can expand or collapse
Figure 20.2 shows a grid with an ADT field and an array field. The dataset’s
ObjectView property is set to False so that each child field has a column.
Figure 20.2 TDBGrid control with ObjectView set to False
ADT child fields Array child fields
Figure 20.3 and 20.4 show the grid with an ADT field and an array field. Figure 20.3
shows the fields collapsed. In this state they cannot be edited. Figure 20.4 shows the
fields expanded. The fields are expanded and collapsed by clicking on the arrow in
the fields title bar.
Figure 20.3 TDBGrid control with Expanded set to False
The following table lists the properties that affect the way ADT and array fields
appear in a TDBGrid:
Table 20.4 Properties that affect the way composite fields appear
Property Object Purpose
Expandable TColumn Indicates whether the column can be expanded to show child
fields in separate, editable columns. (read-only)
Expanded TColumn Specifies whether the column is expanded.
MaxTitleRows TDBGrid Specifies the maximum number of title rows that can appear in
the grid
ObjectView TDataSet Specifies whether fields are displayed flattened out, or in object
mode, where each object field can be expanded and collapsed.
ParentColumn TColumn Refers to the TColumn object that owns the child field’s column.
Note In addition to ADT and array fields, some datasets include fields that refer to another
dataset (dataset fields) or a record in another dataset (reference) fields. Data-aware
grids display such fields as “(DataSet)” or “(Reference)”, respectively. At runtime an
ellipsis button appears to the right. Clicking on the ellipsis brings up a new form with
a grid displaying the contents of the field. For dataset fields, this grid displays the
dataset that is the field’s value. For reference fields, this grid contains a single row
that displays the record from another dataset.
The following table lists the Options properties that can be set, and describes how
they affect the grid at runtime.
There are many uses for these events. For example, you might write a handler for the
OnDblClick event that pops up a list from which a user can choose a value to enter in
a column. Such a handler would use the SelectedField property to determine to
current row and column.
The following table summarizes some of the unique properties for database control
grids that you can set at design time:
For more information about database control grid properties and methods, see the
online VCL Reference.
Overview
Rave Reports is a component-based visual report design tool that simplifies the
process of adding reports to an application. You can use Rave Reports to create a
variety of reports, from simple banded reports to more complex, highly customized
reports. Report features include:
• Word wrapped memos
• Full graphics
• Justification
• Precise page positioning
• Printer configuration
• Font control
• Print preview
• Reuse of report content
• PDF, HTML, RTF, and text report renditions
Getting started
You can use Rave Reports in both VCL and CLX applications to generate reports
from database and non-database data. The following procedure explains how to add
a simple report to an existing database application.
1 Open a database application in Delphi.
2 From the Rave page of the Component palette, add the TRvDataSetConnection
component to a form in the application.
3 In the Object Inspector, set the DataSet property to a dataset component that is
already defined in your application.
4 Use the Rave Visual Designer to design your report and create a report project file
(.rav file).
a Choose Tools|Rave Designer to launch the Rave Visual Designer.
b Choose File|New Data Object to display the Data Connections dialog box.
c In the Data Object Type list, select Direct Data View and click Next.
d In the Active Data Connections list, select RVDataSetConnection1 and click
Finish.
In the Project Tree on the left side of the Rave Visual Designer window, expand
the Data View Dictionary node, then expand the newly created DataView1
node. Your application data fields are displayed under the DataView1 node.
e Choose Tools|Report Wizards|Simple Table to display the Simple Table
wizard.
f Select DataView1 and click Next.
g Select two or three fields that you want to display in the report and click Next.
h Follow the prompts on the subsequent wizard pages to set the order of the
fields, margins, heading text, and fonts to be used in the report.
i On the final wizard page, click Generate to complete the wizard and display the
report in the Page Designer.
j Choose File|Save as to display the Save As dialog box. Navigate to the
directory in which your Delphi application is located and save the Rave project
file as MyRave.rav.
k Minimize the Rave Visual Designer window and return to Delphi.
5 From the Rave page of the Component palette, add the Rave project component,
TRvProject, to the form.
6 In the Object Inspector, set the ProjectFile property to the report project file
(MyRave.rav) that you created in step j.
7 From the Standard page of the Component palette, add the TButton component.
8 In the Object Inspector, click the Events tab and double-click the OnClick event.
9 Write an event handler that uses the ExecuteReport method to execute the Rave
project component.
10 Press F9 to run the application.
11 Click the button that you added in step 7.
12 The Output Options dialog box is displayed. Click OK to display the report.
For a more information on using the Rave Visual Designer, use the Help menu or see
the Rave Reports documentation listed in “Getting more information” on page 21-6.
For a detailed information on using the Rave Visual Designer, use the Help menu or
see the Rave Reports documentation listed in “Getting more information” on
page 21-6.
Component overview
This section provides an overview of the Rave Reports components. For detailed
component information, see the documentation listed in “Getting more information”
on page 21-6.
VCL/CLX components
The VCL/CLX components are non-visual components that you add to a form in
your VCL or CLX application. They are available on the Rave page of the Component
palette. There are four categories of components: engine, render, data connection and
Rave project.
Engine components
The Engine components are used to generate reports. Reports can be generated from
a pre-defined visual definition (using the Engine property of TRvProject) or by
making calls to the Rave code-based API library from within the OnPrint event. The
engine components are:
TRvNDRWriter
TRvSystem
Render components
The Render components are used to convert an NDR file (Rave snapshot report file)
or a stream generated from TRvNDRWriter to a variety of formats. Rendering can be
done programmatically or added to the standard setup and preview dialogs of
TRvSystem by dropping a render component on an active form or data module
within your application. The render components are:
TRvCustomConnection TRvTableConnection
TRvDataSetConnection TRvQueryConnection
Reporting components
The following components are available in the Rave Visual Designer.
Project components
The Project toolbar provides the essential building blocks for all reports. The project
components are:
TRavePage
TRaveProjectManager
TRaveReport
Data objects
Data objects connect to data or control access to reports from the Rave Reporting
Server. The File|New Data Object menu command displays the Data Connections
dialog box, which you can use to create each of the data objects. The data object
components are:
Standard components
The Standard toolbar provides components that are frequently used when designing
reports. The standard components are:
Drawing components
The Drawing toolbar provides components to create lines and shapes in a report. To
color and style the components, use the Fills, Lines, and Colors toolbars. The drawing
components are:
Report components
The Report toolbar provides components that are used most often in data-aware
reports. The report components are:
These books are distributed as PDF files on the Delphi Companion Tools CD.
Most of the information in the PDF files is also available in the online Help. To
display online Help for a Rave Reports component on a form, select the component
and press F1. To display online Help for the Rave Visual Designer, use the Help
menu.
Overview
The decision support components appear on the Decision Cube page of the
Component palette:
• The decision cube, TDecisionCube, is a multidimensional data store.
• The decision source, TDecisionSource, defines the current pivot state of a decision
grid or a decision graph.
• The decision query, TDecisionQuery, is a specialized form of TQuery used to define
the data in a decision cube.
• The decision pivot, TDecisionPivot, lets you open or close decision cube
dimensions, or fields, by pressing buttons.
• The decision grid, TDecisionGrid, displays single- and multidimensional data in
table form.
• The decision graph, TDecisionGraph, displays fields from a decision grid as a
dynamic graph that changes when data dimensions are modified.
Figure 22.1 shows all the decision support components placed on a form at design
time.
Figure 22.1 Decision support components at design time
Decision query
Decision cube
Decision source
Decision pivot
Decision grid
Decision graph
About crosstabs
Cross-tabulations, or crosstabs, are a way of presenting subsets of data so that
relationships and trends are more visible. Table fields become the dimensions of the
crosstab while field values define categories and summaries within a dimension.
You can use the decision support components to set up crosstabs in forms.
TDecisionGrid shows data in a table, while TDecisionGraph charts it graphically.
TDecisionPivot has buttons that make it easier to display and hide dimensions and
move them between columns and rows.
Crosstabs can be one-dimensional or multidimensional.
One-dimensional crosstabs
One-dimensional crosstabs show a summary row (or column) for the categories of a
single dimension. For example, if Payment is the chosen column dimension and
Amount Paid is the summary category, the crosstab in Figure 22.2 shows the amount
paid using each method.
Figure 22.2 One-dimensional crosstab
Multidimensional crosstabs
Multidimensional crosstabs use additional dimensions for the rows and/or columns.
For example, a two-dimensional crosstab could show amounts paid by payment
method for each country.
A three-dimensional crosstab could show amounts paid by payment method and
terms by country, as shown in Figure 22.3.
Figure 22.3 Three-dimensional crosstab
7 Use the decision grid and graph to show and chart different data dimensions. See
“Using decision grids” on page 22-11 and “Using decision graphs” on page 22-14
for instructions and suggestions.
For an illustration of all decision support components on a form, see Figure 22.1 on
page 22-2.
5 In the Decision Query editor dialog box, select fields in the Available Fields list
box and assign them to be either Dimensions or Summaries by clicking the
appropriate right arrow button. As you add fields to the Summaries list, select
from the menu displayed the type of summary to use: sum, count, or average.
6 By default, all fields and summaries defined in the SQL property of the decision
query appear in the Active Dimensions and Active Summaries list boxes. To
remove a dimension or summary, select it in the list and click the left arrow beside
the list, or double-click the item to remove. To add it back, select it in the Available
Fields list box and click the appropriate right arrow.
Once you define the contents of the decision cube, you can further manipulate
dimension display with its DimensionMap property and the buttons of TDecisionPivot.
For more information, see the next section, “Using decision cubes,” “Using decision
sources” on page 22-9, and “Using decision pivots” on page 22-10.
Note When you use the Decision Query editor, the query is initially handled in ANSI-92
SQL syntax, then translated (if necessary) into the dialect used by the server. The
Decision Query editor reads and displays only ANSI standard SQL. The dialect
translation is automatically assigned to the TDecisionQuery’s SQL property. To
modify a query, edit the ANSI-92 version in the Decision Query rather then the SQL
property.
• OnBeforePivot occurs when changes are committed but not yet reflected in the
user interface. Developers have an opportunity to make changes, for example,
in capacity or pivot state, before application users see the result of their
previous action.
• OnAfterPivot fires after a change in pivot state. Developers can capture
information at that time.
indicates whether to display subtotals for that dimension. With summary fields,
these same properties are used to changed the appearance of the data that appears
in the summary area of the grid. When you’re through setting dimension
properties, either click a component in the form or choose a component in the
drop-down list box at the top of the Object Inspector.
• The Options property of TDecisionGrid lets you control display of grid lines
(cgGridLines = True), enabling of outline features (collapse and expansion of
dimensions with + and - indicators; cgOutliner = True), and enabling of drag-and-
drop pivoting (cgPivotable = True).
• The OnDecisionDrawCell event of TDecisionGrid gives you a chance to change the
appearance of each cell as it is drawn. The event passes the String, Font, and Color
of the current cell as reference parameters. You are free to alter those parameters to
achieve effects such as special colors for negative values. In addition to the
DrawState which is passed by TCustomGrid, the event passes TDecisionDrawState,
which can be used to determine what type of cell is being drawn. Further
information about the cell can be fetched using the Cells, CellValueArray, or
CellDrawState functions.
• The OnDecisionExamineCell event of TDecisionGrid lets you hook the right-click-on-
event to data cells, and is intended to allow a program to display information
(such as detail records) about that particular data cell. When the user right-clicks a
data cell, the event is supplied with all the information which is was used to
compose the data value, including the currently active summary value and a
ValueArray of all the dimension values which were used to create the summary
value.
3 Continue with steps 5–7 listed under “Guidelines for using decision support
components.”
4 Finally, right-click the graph and choose Edit Chart to modify the appearance of
the graph series. You can set template properties for each graph dimension, then
set individual series properties to override these defaults. For details, see
“Customizing decision graphs” on page 22-16.
For a description of what appears in the decision graph and how to use it, see the
next section, “Using decision graphs.”
To add a decision grid—or crosstab table—to the form, follow the instructions in
“Creating and using decision grids” on page 22-11.
For more information about what appears in a decision graph, see the next section,
“The decision graph display.”
To create a decision graph, see the previous section, “Creating decision graphs.”
For a discussion of decision graph properties and how to change the appearance and
behavior of decision graphs, see “Customizing decision graphs” on page 22-16.
If you only want one column and one row to be active at a time, you can set the
ControlType property for TDecisionSource to xtRadio. Then, there can be only one
active field at a time for each decision cube axis, and the decision pivot’s
functionality will correspond to the graph’s behavior. xtRadioEx works the same as
xtRadio, but does not allow the state where all row or all columns dimensions are
closed.
When you have both a decision grid and graph connected to the same
TDecisionSource, you’ll probably want to set ControlType back to xtCheck to
correspond to the more flexible behavior of TDecisionGrid.
Connecting to databases
Chapter23
23
Most dataset components can connect directly to a database server. Once connected,
the dataset communicates with the server automatically. When you open the dataset,
it populates itself with data from the server, and when you post records, they are sent
back the server and applied. A single connection component can be shared by
multiple datasets, or each dataset can use its own connection.
Each type of dataset connects to the database server using its own type of connection
component, which is designed to work with a single data access mechanism. The
following table lists these data access mechanisms and the associated connection
components:
Note For a discussion of some pros and cons of each of these mechanisms, see “Using
databases” on page 19-1.
The connection component provides all the information necessary to establish a
database connection. This information is different for each type of connection
component:
• For information about describing a BDE-based connection, see “Identifying the
database” on page 26-14.
• For information about describing an ADO-based connection, see “Connecting to a
data store using TADOConnection” on page 27-3.
Controlling connections
Before you can establish a connection to a database server, your application must
provide certain key pieces of information that describe the desired server. Each type
of connection component surfaces a different set of properties to let you identify the
server. In general, however, they all provide a way for you to name the server you
want and supply a set of connection parameters that control how the connection is
formed. Connection parameters vary from server to server. They can include
information such as user name and password, the maximum size of BLOB fields,
SQL roles, and so on.
Once you have identified the desired server and any connection parameters, you can
use the connection component to explicitly open or close a connection. The
connection component generates events when it opens or closes a connection that
you can use to customize the response of your application to changes in the database
connection.
• Supply the login information before the login attempt. Each type of connection
component uses a different mechanism for specifying the user name and
password:
• For BDE, dbExpress, and InterBase express datasets, the user name and
password connection parameters can be accessed through the Params property.
(For BDE datasets, the parameter values can also be associated with a BDE alias,
while for dbExpress datasets, they can also be associated with a connection
name).
• For ADO datasets, the user name and password can be included in the
ConnectionString property (or provided as parameters to the Open method).
If you specify the user name and password before the server requests them, be
sure to set the LoginPrompt to False, so that the default login dialog does not
appear. For example, the following code sets the user name and password on a
SQL connection component in the BeforeConnect event handler, decrypting an
encrypted password that is associated with the current connection name:
procedure TForm1.SQLConnectionBeforeConnect(Sender: TObject);
begin
with Sender as TSQLConnection do
begin
if LoginPrompt = False then
begin
Params.Values['User_Name'] := 'SYSDBA';
Params.Values['Password'] := Decrypt(Params.Values['Password']);
end;
end;
end;
Note that setting the user name and password at design-time or using hard-coded
strings in code causes the values to be embedded in the application’s executable
file. This still leaves them easy to find, compromising server security.
• Provide your own custom handling for the login event. The connection
component generates an event when it needs the user name and password.
• For TDatabase, TSQLConnection, and TIBDatabase, this is an OnLogin event. The
event handler has two parameters, the connection component, and a local copy
of the user name and password parameters in a string list. (TSQLConnection
includes the database parameter as well). You must set the LoginPrompt
property to True for this event to occur. Having a LoginPrompt value of False and
assigning a handler for the OnLogin event creates a situation where it is
impossible to log in to the database because the default dialog does not appear
and the OnLogin event handler never executes.
• For TADOConnection, the event is an OnWillConnect event. The event handler
has five parameters, the connection component and four parameters that return
values to influence the connection (including two for user name and password).
This event always occurs, regardless of the value of LoginPrompt.
Write an event handler for the event in which you set the login parameters. Here is
an example where the values for the USER NAME and PASSWORD parameters
are provided from a global variable (UserName) and a method that returns a
password given a user name (PasswordSearch)
procedure TForm1.Database1Login(Database: TDatabase; LoginParams: TStrings);
begin
LoginParams.Values['USER NAME'] := UserName;
LoginParams.Values['PASSWORD'] := PasswordSearch(UserName);
end;
As with the other methods of providing login parameters, when writing an
OnLogin or OnWillConnect event handler, avoid hard coding the password in your
application code. It should appear only as an encrypted value, an entry in a secure
database your application uses to look up the value, or be dynamically obtained
from the user.
Managing transactions
A transaction is a group of actions that must all be carried out successfully on one or
more tables in a database before they are committed (made permanent). If one of the
actions in the group fails, then all actions are rolled back (undone). By using
transactions, you ensure that the database is not left in an inconsistent state when a
problem occurs completing one of the actions that make up the transaction.
For example, in a banking application, transferring funds from one account to
another is an operation you would want to protect with a transaction. If, after
decrementing the balance in one account, an error occurred incrementing the balance
in the other, you want to roll back the transaction so that the database still reflects the
correct total balance.
It is always possible to manage transactions by sending SQL commands directly to
the database. Most databases provide their own transaction management model,
although some have no transaction support at all. For servers that support it, you
may want to code your own transaction management directly, taking advantage of
advanced transaction management capabilities on a particular database server, such
as schema caching.
If you do not need to use any advanced transaction management capabilities,
connection components provide a set of methods and properties you can use to
manage transactions without explicitly sending any SQL commands. Using these
properties and methods has the advantage that you do not need to customize your
application for each type of database server you use, as long as the server supports
transactions. (The BDE also provides limited transaction support for local tables with
no server transaction support. When not using the BDE, trying to start transactions
on a database that does not support them causes connection components to raise an
exception.)
Warning When a dataset provider component applies updates, it implicitly generates
transactions for any updates. Be careful that any transactions you explicitly start do
not conflict with those generated by the provider.
Starting a transaction
When you start a transaction, all subsequent statements that read from or write to the
database occur in the context of that transaction, until the transaction is explicitly
terminated or (in the case of overlapping transactions) until another transaction is
started. Each statement is considered part of a group. Changes must be successfully
committed to the database, or every change made in the group must be undone.
While the transaction is in process, your view of the data in database tables is
determined by your transaction isolation level. For information about transaction
isolation levels, see “Specifying the transaction isolation level” on page 23-9.
For TADOConnection, start a transaction by calling the BeginTrans method:
Level := ADOConnection1.BeginTrans;
BeginTrans returns the level of nesting for the transaction that started. A nested
transaction is one that is nested within another, parent, transaction. After the server
starts the transaction, the ADO connection receives an OnBeginTransComplete event.
For TDatabase, use the StartTransactionmethod instead. TDataBase does not support
nested or overlapped transactions: If you call a TDatabase component’s
StartTransaction method while another transaction is underway, it raises an
exception. To avoid calling StartTransaction, you can check the InTransaction
property:
if not Database1.InTransaction then
Database1.StartTransaction;
TSQLConnection also uses the StartTransactionmethod, but it uses a version that gives
you a lot more control. Specifically, StartTransaction takes a transaction descriptor,
which lets you manage multiple simultaneous transactions and specify the
transaction isolation level on a per-transaction basis. (For more information on
transaction levels, see “Specifying the transaction isolation level” on page 23-9.) In
order to manage multiple simultaneous transactions, set the TransactionID field of the
transaction descriptor to a unique value. TransactionID can be any value you choose,
as long as it is unique (does not conflict with any other transaction currently
underway). Depending on the server, transactions started by TSQLConnection can be
nested (as they can be when using ADO) or they can be overlapped.
var
TD: TTransactionDesc;
begin
TD.TransactionID := 1;
TD.IsolationLevel := xilREADCOMMITTED;
SQLConnection1.StartTransaction(TD);
By default, with overlapped transactions, the first transaction becomes inactive when
the second transaction starts, although you can postpone committing or rolling back
the first transaction until later. If you are using TSQLConnection with an InterBase
database, you can identify each dataset in your application with a particular active
transaction, by setting its TransactionLevel property. That is, after starting a second
transaction, you can continue to work with both transactions simultaneously, simply
by associating a dataset with the transaction you want.
Note Unlike TADOConnection, TSQLConnection and TDatabase do not receive any events
when the transactions starts.
InterBase express offers you even more control than TSQLConnection by using a
separate transaction component rather than starting transactions using the
connection component. You can, however, use TIBDatabase to start a default
transaction:
if not IBDatabase1.DefaultTransaction.InTransaction then
IBDatabase1.DefaultTransaction.StartTransaction;
You can have overlapped transactions by using two separate transaction
components. Each transaction component has a set of parameters that let you
configure the transaction. These let you specify the transaction isolation level, as well
as other properties of the transaction.
Ending a transaction
Ideally, a transaction should only last as long as necessary. The longer a transaction is
active, the more simultaneous users that access the database, and the more
concurrent, simultaneous transactions that start and end during the lifetime of your
transaction, the greater the likelihood that your transaction will conflict with another
when you attempt to commit any changes.
The syntax for the Execute method varies with the connection type:
• For TDatabase, Execute takes four parameters: a string that specifies a single SQL
statement that you want to execute, a TParams object that supplies any parameter
values for that statement, a boolean that indicates whether the statement should be
cached because you will call it again, and a pointer to a BDE cursor that can be
returned (It is recommended that you pass nil).
• For TADOConnection, there are two versions of Execute. The first takes a
WideString that specifies the SQL statement and a second parameter that specifies
a set of options that control whether the statement is executed asynchronously and
whether it returns any records. This first syntax returns an interface for the
returned records. The second syntax takes a WideString that specifies the SQL
statement, a second parameter that returns the number of records affected when
the statement executes, and a third that specifies options such as whether the
statement executes asynchronously. Note that neither syntax provides for passing
parameters.
• For TSQLConnection, Execute takes three parameters: a string that specifies a single
SQL statement that you want to execute, a TParams object that supplies any
parameter values for that statement, and a pointer that can receive a
TCustomSQLDataSet that is created to return records.
Note Execute can only execute one SQL statement at a time. It is not possible to execute
multiple SQL statements with a single call to Execute, as you can with SQL scripting
utilities. To execute more than one statement, call Execute repeatedly.
It is relatively easy to execute a statement that does not include any parameters. For
example, the following code executes a CREATE TABLE statement (DDL) without
any parameters on a TSQLConnection component:
procedure TForm1.CreateTableButtonClick(Sender: TObject);
var
SQLstmt: String;
begin
SQLConnection1.Connected := True;
SQLstmt := 'CREATE TABLE NewCusts ' +
'( ' +
' CustNo INTEGER, ' +
' Company CHAR(40), ' +
' State CHAR(2), ' +
' PRIMARY KEY (CustNo) ' +
')';
SQLConnection1.Execute(SQLstmt, nil, nil);
end;
To use parameters, you must create a TParams object. For each parameter value, use
the TParams.CreateParam method to add a TParam object. Then use properties of
TParam to describe the parameter and set its value.
This process is illustrated in the following example, which uses TDatabase to execute
an INSERT statement. The INSERT statement has a single parameter named:
StateParam. A TParams object (called stmtParams) is created to supply a value of “CA”
for that parameter.
procedure TForm1.INSERT_WithParamsButtonClick(Sender: TObject);
var
SQLstmt: String;
stmtParams: TParams;
begin
stmtParams := TParams.Create;
try
Database1.Connected := True;
stmtParams.CreateParam(ftString, 'StateParam', ptInput);
stmtParams.ParamByName('StateParam').AsString := 'CA';
SQLstmt := 'INSERT INTO "Custom.db" '+
'(CustNo, Company, State) ' +
'VALUES (7777, "Robin Dabank Consulting", :StateParam)';
Database1.Execute(SQLstmt, stmtParams, False, nil);
finally
stmtParams.Free;
end;
end;
If the SQL statement includes a parameter but you do not supply a TParam object to
provide its value, the SQL statement may cause an error when executed (this
depends on the particular database back-end used). If a TParam object is provided
but there is no corresponding parameter in the SQL statement, an exception is raised
when the application attempts to use the TParam.
Obtaining metadata
All database connection components can retrieve lists of metadata on the database
server, although they vary in the types of metadata they retrieve. The methods that
retrieve metadata fill a string list with the names of various entities available on the
server. You can then use this information, for example, to let your users dynamically
select a table at runtime.
You can use a TADOConnection component to retrieve metadata about the tables and
stored procedures available on the ADO data store. You can then use this
information, for example, to let your users dynamically select a table or stored
procedure at runtime.
Understanding datasets
Chapter24
24
The fundamental unit for accessing data is the dataset family of objects. Your
application uses datasets for all database access. A dataset object represents a set of
records from a database organized into a logical table. These records may be the
records from a single database table, or they may represent the results of executing a
query or stored procedure.
All dataset objects that you use in your database applications descend from TDataSet,
and they inherit data fields, properties, events, and methods from this class. This
chapter describes the functionality of TDataSet that is inherited by the dataset objects
you use in your database applications. You need to understand this shared
functionality to use any dataset object.
TDataSet is a virtualized dataset, meaning that many of its properties and methods
are virtual or abstract. A virtual method is a function or procedure declaration where
the implementation of that method can be (and usually is) overridden in descendant
objects. An abstract method is a function or procedure declaration without an actual
implementation. The declaration is a prototype that describes the method (and its
parameters and return type, if any) that must be implemented in all descendant
dataset objects, but that might be implemented differently by each of them.
Because TDataSet contains abstract methods, you cannot use it directly in an
application without generating a runtime error. Instead, you either create instances
of the built-in TDataSet descendants and use them in your application, or you derive
your own dataset object from TDataSet or its descendants and write implementations
for all its abstract methods.
TDataSet defines much that is common to all dataset objects. For example, TDataSet
defines the basic structure of all datasets: an array of TField components that
correspond to actual columns in one or more database tables, lookup fields provided
by your application, or calculated fields provided by your application. For
information about TField components, see Chapter 25, “Working with field
components.”
This chapter describes how to use the common database functionality introduced by
TDataSet. Bear in mind, however, that although TDataSet introduces the methods for
this functionality, not all TDataSet dependants implement them. In particular,
unidirectional datasets implement only a limited subset.
In addition to the built-in datasets, you can create your own custom TDataSet
descendants — for example to supply data from a process other than a database
server, such as a spreadsheet. Writing custom datasets allows you the flexibility of
managing the data using any method you choose, while still letting you use the VCL
data controls to build your user interface. For more information about creating
custom components, see the Component Writer’s Guide, Chapter 1, “Overview of
component creation.”
Although each TDataSet descendant has its own unique properties and methods,
some of the properties and methods introduced by descendant classes are the same
as those introduced by other descendant classes that use another data access
mechanism. For example, there are similarities between the “table” components
(TTable, TADOTable, TSQLTable, and TIBTable). For information about the
commonalities introduced by TDataSet descendants, see “Types of datasets” on
page 24-24.
Just as the dataset receives BeforeOpen and AfterOpen events when you open it, it
receives a BeforeClose and AfterClose event when you close it. handlers that respond to
the Close method for a dataset. You can use these events, for example, to prompt the
user to post pending changes or cancel them before closing the dataset. The following
code illustrates such a handler:
procedure TForm1.CustTableVerifyBeforeClose(DataSet: TDataSet);
begin
if (CustTable.State in [dsEdit, dsInsert]) then begin
case MessageDlg('Post changes before closing?', mtConfirmation, mbYesNoCancel, 0) of
mrYes: CustTable.Post; { save the changes }
mrNo: CustTable.Cancel; { abandon the changes}
mrCancel: Abort; { abort closing the dataset }
end;
end;
end;
Note You may need to close a dataset when you want to change certain of its properties,
such as TableName on a TTable component. When you reopen the dataset, the new
property value takes effect.
Navigating datasets
Each active dataset has a cursor, or pointer, to the current row in the dataset. The
current row in a dataset is the one whose field values currently show in single-field,
data-aware controls on a form, such as TDBEdit, TDBLabel, and TDBMemo. If the
dataset supports editing, the current record contains the values that can be
manipulated by edit, insert, and delete methods.
You can change the current row by moving the cursor to point at a different row. The
following table lists methods you can use in application code to move to different
records:
Whenever you change the current record using one of these methods (or by other
methods that navigate based on a search criterion), the dataset receives two events:
BeforeScroll (before leaving the current record) and AfterScroll (after arriving at the
new record). You can use these events to update your user interface (for example, to
update a status bar that indicates information about the current record).
TDataSet also defines two boolean properties that provide useful information when
iterating through the records in a dataset.
Eof
When Eof is True, it indicates that the cursor is unequivocally at the last row in a
dataset. Eof is set to True when an application
• Opens an empty dataset.
• Calls a dataset’s Last method.
• Calls a dataset’s Next method, and the method fails (because the cursor is
currently at the last row in the dataset.
• Calls SetRange on an empty range or dataset.
Eof is set to False in all other cases; you should assume Eof is False unless one of the
conditions above is met and you test the property directly.
Eof is commonly tested in a loop condition to control iterative processing of all
records in a dataset. If you open a dataset containing records (or you call First) Eof is
False. To iterate through the dataset a record at a time, create a loop that steps
through each record by calling Next, and terminates when Eof is True. Eof remains
False until you call Next when the cursor is already on the last record.
The following code illustrates one way you might code a record-processing loop for a
dataset called CustTable:
CustTable.DisableControls;
try
CustTable.First; { Go to first record, which sets Eof False }
while not CustTable.Eof do { Cycle until Eof is True }
begin
{ Process each record here }
ƒ
CustTable.Next; { Eof False on success; Eof True when Next fails on last record }
end;
finally
CustTable.EnableControls;
end;
Tip This example also shows how to disable and enable data-aware visual controls tied to
a dataset. If you disable visual controls during dataset iteration, it speeds processing
because your application does not need to update the contents of the controls as the
current record changes. After iteration is complete, controls should be enabled again
to update them with values for the new current row. Note that enabling of the visual
controls takes place in the finally clause of a try...finally statement. This guarantees
that even if an exception terminates loop processing prematurely, controls are not left
disabled.
Bof
When Bof is True, it indicates that the cursor is unequivocally at the first row in a
dataset. Bof is set to True when an application
• Opens a dataset.
• Calls a dataset’s First method.
• Calls a dataset’s Prior method, and the method fails (because the cursor is
currently at the first row in the dataset.
• Calls SetRange on an empty range or dataset.
Bof is set to False in all other cases; you should assume Bof is False unless one of the
conditions above is met and you test the property directly.
Like Eof, Bof can be in a loop condition to control iterative processing of records in a
dataset. The following code illustrates one way you might code a record-processing
loop for a dataset called CustTable:
CustTable.DisableControls; { Speed up processing; prevent screen flicker }
try
while not CustTable.Bof do { Cycle until Bof is True }
begin
{ Process each record here }
ƒ
CustTable.Prior; { Bof False on success; Bof True when Prior fails on first record }
end;
finally
CustTable.EnableControls; { Display new current row in controls }
end;
A bookmarking example
The following code illustrates one use of bookmarking:
procedure DoSomething (const Tbl: TTable)
var
Bookmark: TBookmark;
begin
Bookmark := Tbl.GetBookmark; { Allocate memory and assign a value }
Tbl.DisableControls; { Turn off display of records in data controls }
try
Tbl.First; { Go to first record in table }
while not Tbl.Eof do {Iterate through each record in table }
begin
{ Do your processing here }
ƒ
Tbl.Next;
end;
finally
Tbl.GotoBookmark(Bookmark);
Tbl.EnableControls; { Turn on display of records in data controls, if necessary }
Tbl.FreeBookmark(Bookmark); {Deallocate memory for the bookmark }
end;
end;
Before iterating through records, controls are disabled. Should an error occur during
iteration through records, the finally clause ensures that controls are always enabled
and that the bookmark is always freed even if the loop terminates prematurely.
Searching datasets
If a dataset is not unidirectional, you can search against it using the Locate and Lookup
methods. These methods enable you to search on any type of columns in any dataset.
Note Some TDataSet descendants introduce an additional family of methods for searching
based on an index. For information about these additional methods, see “Using
Indexes to search for records” on page 24-28.
Using Locate
Locate moves the cursor to the first row matching a specified set of search criteria. In
its simplest form, you pass Locate the name of a column to search, a field value to
match, and an options flag specifying whether the search is case-insensitive or if it
can use partial-key matching. (Partial-key matching is when the criterion string need
only be a prefix of the field value.) For example, the following code moves the cursor
to the first row in the CustTable where the value in the Company column is
“Professional Divers, Ltd.”:
var
LocateSuccess: Boolean;
SearchOptions: TLocateOptions;
begin
SearchOptions := [loPartialKey];
LocateSuccess := CustTable.Locate('Company', 'Professional Divers, Ltd.', SearchOptions);
end;
If Locate finds a match, the first record containing the match becomes the current
record. Locate returns True if it finds a matching record, False if it does not. If a search
fails, the current record does not change.
The real power of Locate comes into play when you want to search on multiple
columns and specify multiple values to search for. Search values are Variants, which
means you can specify different data types in your search criteria. To specify
multiple columns in a search string, separate individual items in the string with
semicolons.
Because search values are Variants, if you pass multiple values, you must either pass
a Variant array as an argument (for example, the return values from the Lookup
method), or you must construct the Variant array in code using the VarArrayOf
function. The following code illustrates a search on multiple columns using multiple
search values and partial-key matching:
with CustTable do
Locate('Company;Contact;Phone', VarArrayOf(['Sight Diver','P']), loPartialKey);
Locate uses the fastest possible method to locate matching records. If the columns to
search are indexed and the index is compatible with the search options you specify,
Locate uses the index.
Using Lookup
Lookup searches for the first row that matches specified search criteria. If it finds a
matching row, it forces the recalculation of any calculated fields and lookup fields
associated with the dataset, then returns one or more fields from the matching row.
Lookup does not move the cursor to the matching row; it only returns values from it.
In its simplest form, you pass Lookup the name of field to search, the field value to
match, and the field or fields to return. For example, the following code looks for the
first record in the CustTable where the value of the Company field is “Professional
Divers, Ltd.”, and returns the company name, a contact person, and a phone number
for the company:
var
LookupResults: Variant;
begin
LookupResults := CustTable.Lookup('Company', 'Professional Divers, Ltd.',
'Company;Contact; Phone');
end;
Lookup returns values for the specified fields from the first matching record it finds.
Values are returned as Variants. If more than one return value is requested, Lookup
returns a Variant array. If there are no matching records, Lookup returns a Null
Variant. For more information about Variant arrays, see the online Help.
The real power of Lookup comes into play when you want to search on multiple
columns and specify multiple values to search for. To specify strings containing
multiple columns or result fields, separate individual fields in the string items with
semicolons.
Because search values are Variants, if you pass multiple values, you must either pass
a Variant array as an argument (for example, the return values from the Lookup
method), or you must construct the Variant array in code using the VarArrayOf
function. The following code illustrates a lookup search on multiple columns:
var
LookupResults: Variant;
begin
with CustTable do
LookupResults := Lookup('Company; City', VarArrayOf(['Sight Diver', 'Christiansted']),
'Company; Addr1; Addr2; State; Zip');
end;
Like Locate, Lookup uses the fastest possible method to locate matching records. If the
columns to search are indexed, Lookup uses the index.
Creating filters
There are two ways to create a filter for a dataset:
• Specify simple filter conditions in the Filter property. Filter is especially useful for
creating and applying filters at runtime.
• Write an OnFilterRecord event handler for simple or complex filter conditions.
With OnFilterRecord, you specify filter conditions at design time. Unlike the Filter
property, which is restricted to a single string containing filter logic, an
OnFilterRecord event can take advantage of branching and looping logic to create
complex, multi-level filter conditions.
The main advantage to creating filters using the Filter property is that your
application can create, change, and apply filters dynamically, (for example, in
response to user input). Its main disadvantages are that filter conditions must be
expressible in a single text string, cannot make use of branching and looping
constructs, and cannot test or compare its values against values not already in the
dataset.
The strengths of the OnFilterRecord event are that a filter can be complex and
variable, can be based on multiple lines of code that use branching and looping
constructs, and can test dataset values against values outside the dataset, such as the
text in an edit box. The main weakness of using OnFilterRecord is that you set the
filter at design time and it cannot be modified in response to user input. (You can,
however, create several filter handlers and switch among them in response to general
application conditions.)
The following sections describe how to create filters using the Filter property and the
OnFilterRecord event handler.
Table 24.4 Comparison and logical operators that can appear in a filter
Operator Meaning
< Less than
> Greater than
>= Greater than or equal to
<= Less than or equal to
= Equal to
Table 24.4 Comparison and logical operators that can appear in a filter (continued)
Operator Meaning
<> Not equal to
AND Tests two statements are both True
NOT Tests that the following statement is not True
OR Tests that at least one of two statements is True
+ Adds numbers, concatenates strings, adds numbers to date/time values (only
available for some drivers)
- Subtracts numbers, subtracts dates, or subtracts a number from a date (only available
for some drivers)
* Multiplies two numbers (only available for some drivers)
/ Divides two numbers (only available for some drivers)
* wildcard for partial comparisons (FilterOptions must include foPartialCompare)
By using combinations of these operators, you can create fairly sophisticated filters.
For example, the following statement checks to make sure that two test conditions
are met before accepting a record for display:
(Custno > 1400) AND (Custno < 1500);
Note When filtering is on, user edits to a record may mean that the record no longer meets
a filter’s test conditions. The next time the record is retrieved from the dataset, it may
therefore “disappear.” If that happens, the next record that passes the filter condition
becomes the current record.
For example, the following statements set up a filter that ignores case when
comparing values in the State field:
FilterOptions := [foCaseInsensitive];
Filter := 'State = ' + QuotedStr('CA');
For example, the following statement finds the first filtered record in a dataset:
DataSet1.FindFirst;
Provided that you set the Filter property or create an OnFilterRecord event handler for
your application, these methods position the cursor on the specified record
regardless of whether filtering is currently enabled. If you call these methods when
filtering is not enabled, then they
• Temporarily enable filtering.
• Position the cursor on a matching record if one is found.
• Disable filtering.
Note If filtering is disabled and you do not set the Filter property or create an
OnFilterRecord event handler, these methods do the same thing as First, Last, Next,
and Prior.
All navigational filter methods position the cursor on a matching record (if one is
found), make that record the current one, and return True. If a matching record is not
found, the cursor position is unchanged, and these methods return False. You can
check the status of the Found property to wrap these calls, and only take action when
Found is True. For example, if the cursor is already on the last matching record in the
dataset and you call FindNext, the method returns False, and the current record is
unchanged.
Modifying data
You can use the following dataset methods to insert, update, and delete data if the
read-only CanModify property is True. CanModify is True unless the dataset is
unidirectional, the database underlying the dataset does not permit read and write
privileges, or some other factor intervenes. (Intervening factors include the ReadOnly
property on some datasets or the RequestLive property on TQuery components.)
Table 24.7 Dataset methods for inserting, updating, and deleting data
Method Description
Edit Puts the dataset into dsEdit state if it is not already in dsEdit or dsInsert states.
Append Posts any pending data, moves current record to the end of the dataset, and puts the
dataset in dsInsert state.
Insert Posts any pending data, and puts the dataset in dsInsert state.
Post Attempts to post the new or altered record to the database. If successful, the dataset is
put in dsBrowse state; if unsuccessful, the dataset remains in its current state.
Cancel Cancels the current operation and puts the dataset in dsBrowse state.
Delete Deletes the current record and puts the dataset in dsBrowse state.
Editing records
A dataset must be in dsEdit mode before an application can modify records. In your
code you can use the Edit method to put a dataset into dsEdit mode if the read-only
CanModify property for the dataset is True.
When a dataset transitions to dsEdit mode, it first receives a BeforeEdit event. After the
transition to edit mode is successfully completed, the dataset receives an AfterEdit
event. Typically, these events are used for updating the user interface to indicate the
current state of the dataset. If the dataset can’t be put into edit mode for some reason,
an OnEditError event occurs, where you can inform the user of the problem or try to
correct the situation that prevented the dataset from entering edit mode.
On forms in your application, some data-aware controls can automatically put a
dataset into dsEdit state if
• The control’s ReadOnly property is False (the default),
• The AutoEdit property of the data source for the control is True, and
• CanModify is True for the dataset.
Note Even if a dataset is in dsEdit state, editing records may not succeed for SQL-based
databases if your application’s user does not have proper SQL access privileges.
Once a dataset is in dsEdit mode, a user can modify the field values for the current
record that appears in any data-aware controls on a form. Data-aware controls for
which editing is enabled automatically call Post when a user executes any action that
changes the current record (such as moving to a different record in a grid).
If you have a navigator component on your form, users can cancel edits by clicking
the navigator’s Cancel button. Canceling edits returns a dataset to dsBrowse state.
In code, you must write or cancel edits by calling the appropriate methods. You write
changes by calling Post. You cancel them by calling Cancel. In code, Edit and Post are
often used together. For example,
with CustTable do
begin
Edit;
FieldValues['CustNo'] := 1234;
Post;
end;
In the previous example, the first line of code places the dataset in dsEdit mode. The
next line of code assigns the number 1234 to the CustNo field of the current record.
Finally, the last line writes, or posts, the modified record. If you are not caching
updates, posting writes the change back to the database. If you are caching updates,
the change is written to a temporary buffer, where it stays until the dataset’s
ApplyUpdates method is called.
Inserting records
Insert opens a new, empty record before the current record, and makes the empty
record the current record so that field values for the record can be entered either by a
user or by your application code.
When an application calls Post (or ApplyUpdates when using cached updates), a
newly inserted record is written to a database in one of three ways:
• For indexed Paradox and dBASE tables, the record is inserted into the dataset in a
position based on its index.
• For unindexed Paradox and dBASE tables, the record is inserted into the dataset at
its current position.
Appending records
Append opens a new, empty record at the end of the dataset, and makes the empty
record the current one so that field values for the record can be entered either by a
user or by your application code.
When an application calls Post (or ApplyUpdates when using cached updates), a
newly appended record is written to a database in one of three ways:
• For indexed Paradox and dBASE tables, the record is inserted into the dataset in a
position based on its index.
• For unindexed Paradox and dBASE tables, the record is added to the end of the
dataset.
• For SQL databases, the physical location of the append is implementation-specific.
If the table is indexed, the index is updated with the new record information.
Deleting records
Use the Delete method to delete the current record in an active dataset. When the
Delete method is called,
• The dataset receives a BeforeDelete event.
• The dataset attempts to delete the current record.
• The dataset returns to the dsBrowse state.
• The dataset receives an AfterDelete event.
If want to prevent the deletion in the BeforeDelete event handler, you can call the
global Abort procedure:
procedure TForm1.TableBeforeDelete (Dataset: TDataset)
begin
if MessageDlg('Delete This Record?', mtConfirmation, mbYesNoCancel, 0) <> mrYes then
Abort;
end;
If Delete fails, it generates an OnDeleteError event. If the OnDeleteError event handler
can’t correct the problem, the dataset remains in dsEdit state. If Delete succeeds, the
dataset reverts to the dsBrowse state and the record that followed the deleted record
becomes the current record.
If you are caching updates, the deleted record is not removed from the underlying
database table until you call ApplyUpdates.
If you provide a navigator component on your forms, users can delete the current
record by clicking the navigator’s Delete button. In code, you must call Delete
explicitly to remove the current record.
Posting data
After you finish editing a record, you must call the Post method to write out your
changes. The Post method behaves differently, depending on the dataset’s state and
on whether you are caching updates.
• If you are not caching updates, and the dataset is in the dsEdit or dsInsert state, Post
writes the current record to the database and returns the dataset to the dsBrowse
state.
• If you are caching updates, and the dataset is in the dsEdit or dsInsert state, Post
writes the current record to an internal cache and returns the dataset to the
dsBrowse state. The edits are net written to the database until you call
ApplyUpdates.
• If the dataset is in the dsSetKey state, Post returns the dataset to the dsBrowse state.
Regardless of the initial state of the dataset, Post generates BeforePost and AfterPost
events, before and after writing the current changes. You can use these events to
update the user interface, or prevent the dataset from posting changes by calling the
Abort procedure. If the call to Post fails, the dataset receives an OnPostError event,
where you can inform the user of the problem or attempt to correct it.
Posting can be done explicitly, or implicitly as part of another procedure. When an
application moves off the current record, Post is called implicitly. Calls to the First,
Next, Prior, and Last methods perform a Post if the table is in dsEdit or dsInsert modes.
The Append and Insert methods also implicitly post any pending data.
Warning The Close method does not call Post implicitly. Use the BeforeClose event to post any
pending edits explicitly.
Canceling changes
An application can undo changes made to the current record at any time, if it has not
yet directly or indirectly called Post. For example, if a dataset is in dsEdit mode, and a
user has changed the data in one or more fields, the application can return the record
back to its original values by calling the Cancel method for the dataset. A call to Cancel
always returns a dataset to dsBrowse state.
If the dataset was in dsEdit or dsInsert mode when your application called Cancel, it
receives BeforeCancel and AfterCancel events before and after the current record is
restored to its original values.
On forms, you can allow users to cancel edit, insert, or append operations by
including the Cancel button on a navigator component associated with the dataset, or
you can provide code for your own Cancel button on the form.
These method take an array of values as an argument, where each value corresponds
to a column in the underlying dataset. The values can be literals, variables, or NULL.
If the number of values in an argument is less than the number of columns in a
dataset, then the remaining values are assumed to be NULL.
For unindexed datasets, AppendRecord adds a record to the end of the dataset and
InsertRecord inserts a record after the current cursor position. For indexed datasets,
both methods place the record in the correct position in the table, based on the index.
In both cases, the methods move the cursor to the record’s position.
SetFields assigns the values specified in the array of parameters to fields in the
dataset. To use SetFields, an application must first call Edit to put the dataset in dsEdit
mode. To apply the changes to the current record, it must perform a Post.
If you use SetFields to modify some, but not all fields in an existing record, you can
pass NULL values for fields you do not want to change. If you do not supply enough
values for all fields in a record, SetFields assigns NULL values to them. NULL values
overwrite any existing values already in those fields.
For example, suppose a database has a COUNTRY table with columns for Name,
Capital, Continent, Area, and Population. If a TTable component called CountryTable
were linked to the COUNTRY table, the following statement would insert a record
into the COUNTRY table:
CountryTable.InsertRecord(['Japan', 'Tokyo', 'Asia']);
This statement does not specify values for Area and Population, so NULL values are
inserted for them. The table is indexed on Name, so the statement would insert the
record based on the alphabetic collation of “Japan.”
Calculating fields
Using the Fields editor, you can define calculated fields for your datasets. When a
dataset contains calculated fields, you provide the code to calculate those field’s
values in an OnCalcFields event handler. For details on how to define calculated fields
using the Fields editor, see “Defining a calculated field” on page 25-7.
The AutoCalcFields property determines when OnCalcFields is called. If AutoCalcFields
is True, OnCalcFields is called when
• A dataset is opened.
• The dataset enters edit mode.
• A record is retrieved from the database.
• Focus moves from one visual component to another, or from one column to
another in a data-aware grid control and the current record has been modified.
If AutoCalcFields is False, then OnCalcFields is not called when individual fields within
a record are edited (the fourth condition above).
Caution OnCalcFields is called frequently, so the code you write for it should be kept short.
Also, if AutoCalcFields is True, OnCalcFields should not perform any actions that
modify the dataset (or a linked dataset if it is part of a master-detail relationship),
because this leads to recursion. For example, if OnCalcFields performs a Post, and
AutoCalcFields is True, then OnCalcFields is called again, causing another Post, and so
on.
When OnCalcFields executes, a dataset enters dsCalcFields mode. This state prevents
modifications or additions to the records except for the calculated fields the handler
is designed to modify. The reason for preventing other modifications is because
OnCalcFields uses the values in other fields to derive calculated field values. Changes
to those other fields might otherwise invalidate the values assigned to calculated
fields. After OnCalcFields is completed, the dataset returns to dsBrowse state.
Types of datasets
“Using TDataSet descendants” on page 24-2 classifies TDataSet descendants by the
method they use to access their data. Another useful way to classify TDataSet
descendants is to consider the type of server data they represent. Viewed this way,
there are three basic classes of datasets:
• Table type datasets: Table type datasets represent a single table from the database
server, including all of its rows and columns. Table type datasets include TTable,
TADOTable, TSQLTable, and TIBTable.
Table type datasets let you take advantage of indexes defined on the server.
Because there is a one-to-one correspondence between database table and dataset,
you can use server indexes that are defined for the database table. Indexes allow
your application to sort the records in the table, speed searches and lookups, and
can form the basis of a master/detail relationship. Some table type datasets also
take advantage of the one-to-one relationship between dataset and database table
to let you perform table-level operations such as creating and deleting database
tables.
• Query-type datasets: Query-type datasets represent a single SQL command, or
query. Queries can represent the result set from executing a command (typically a
SELECT statement), or they can execute a command that does not return any
records (for example, an UPDATE statement). Query-type datasets include
TQuery, TADOQuery, TSQLQuery, and TIBQuery.
To use a query-type dataset effectively, you must be familiar with SQL and your
server’s SQL implementation, including limitations and extensions to the SQL-92
standard. If you are new to SQL, you may want to purchase a third party book that
covers SQL in-depth. One of the best is Understanding the New SQL: A Complete
Guide, by Jim Melton and Alan R. Simpson, Morgan Kaufmann Publishers.
• Stored procedure-type datasets: Stored procedure-type datasets represent a
stored procedure on the database server. Stored procedure-type datasets include
TStoredProc, TADOStoredProc, TSQLStoredProc, and TIBStoredProc.
A stored procedure is a self-contained program written in the procedure and
trigger language specific to the database system used. They typically handle
frequently repeated database-related tasks, and are especially useful for
operations that act on large numbers of records or that use aggregate or
mathematical functions. Using stored procedures typically improves the
performance of a database application by:
• Taking advantage of the server’s usually greater processing power and speed.
• Reducing network traffic by moving processing to the server.
Stored procedures may or may not return data. Those that return data may return
it as a cursor (similar to the results of a SELECT query), as multiple cursors
(effectively returning multiple datasets), or they may return data in output
parameters. These differences depend in part on the server: Some servers do not
allow stored procedures to return data, or only allow output parameters. Some
servers do not support stored procedures at all. See your server documentation to
determine what is available.
Note You can usually use a query-type dataset to execute stored procedures because most
servers provide extensions to SQL for working with stored procedures. Each server,
however, uses its own syntax for this. If you choose to use a query-type dataset
instead of a stored procedure-type dataset, see your server documentation for the
necessary syntax.
In addition to the datasets that fall neatly into these three categories, TDataSet has
some descendants that fit into more than one category:
• TADODataSet and TSQLDataSet have a CommandType property that lets you
specify whether they represent a table, query, or stored procedure. Property and
method names are most similar to query-type datasets, although TADODataSet
lets you specify an index like a table type dataset.
• TClientDataSet represents the data from another dataset. As such, it can represent a
table, query, or stored procedure. TClientDataSet behaves most like a table type
dataset, because of its index support. However, it also has some of the features of
queries and stored procedures: the management of parameters and the ability to
execute without retrieving a result set.
• Some other client datasets (like TBDEClientDataSet) have a CommandType property
that lets you specify whether they represent a table, query, or stored procedure.
Property and method names are like TClientDataSet, including parameter support,
indexes, and the ability to execute without retrieving a result set.
• TIBDataSet can represent both queries and stored procedures. In fact, it can
represent multiple queries and stored procedures simultaneously, with separate
properties for each.
GotoKey and FindKey are boolean functions that, if successful, move the cursor to a
matching record and return True. If the search is unsuccessful, the cursor is not
moved, and these functions return False.
GotoNearest and FindNearest always reposition the cursor either on the first exact
match found or, if no match is found, on the first record that is greater than the
specified search criteria.
Specifying ranges
There are two mutually exclusive ways to specify a range:
• Specify the beginning and ending separately using SetRangeStart and SetRangeEnd.
• Specify both endpoints at once using SetRange.
column (CustNo). A form in the application has two edit components named StartVal
and EndVal, used to specify start and ending values for a range. The following code
can be used to create and apply a range:
with Customers do
begin
SetRangeStart;
FieldByName('CustNo').AsString := StartVal.Text;
SetRangeEnd;
if (Length(EndVal.Text) > 0) then
FieldByName('CustNo').AsString := EndVal.Text;
ApplyRange;
end;
This code checks that the text entered in EndVal is not null before assigning any
values to Fields. If the text entered for StartVal is null, then all records from the
beginning of the dataset are included, since all values are greater than null. However,
if the text entered for EndVal is null, then no records are included, since none are less
than null.
For a multi-column index, you can specify a starting value for all or some fields in the
index. If you do not supply a value for a field used in the index, a null value is
assumed when you apply the range. If you try to set a value for a field that is not in
the index, the dataset raises an exception.
Tip To start at the beginning of the dataset, omit the call to SetRangeStart.
To finish specifying the start of a range, call SetRangeEnd or apply or cancel the range.
For information about applying and canceling ranges, see “Applying or canceling a
range” on page 24-34.
As with specifying start of range values, if you try to set a value for a field that is not
in the index, the dataset raises an exception.
To finish specifying the end of a range, apply or cancel the range. For information
about applying and canceling ranges, see “Applying or canceling a range” on
page 24-34.
If you prefer, you can set the KeyExclusive property for a dataset to True to exclude
records equal to ending range. For example,
Contacts.KeyExclusive := True;
Contacts.SetRangeStart;
Contacts['LastName'] := 'Smith';
Contacts.SetRangeEnd;
Contacts['LastName'] := 'Tyler';
Contacts.ApplyRange;
This code includes all records in a range where LastName is greater than or equal to
“Smith” and less than “Tyler”.
Modifying a range
Two functions enable you to modify the existing boundary conditions for a range:
EditRangeStart, for changing the starting values for a range; and EditRangeEnd, for
changing the ending values for the range.
The process for editing and applying a range involves these general steps:
1 Putting the dataset into dsSetKey state and modifying the starting index value for
the range.
2 Modifying the ending index value for the range.
3 Applying the range to the dataset.
You can modify either the starting or ending values of the range, or you can modify
both boundary conditions. If you modify the boundary conditions for a range that is
currently applied to the dataset, the changes you make are not applied until you call
ApplyRange again.
Applying a range
When you specify a range, the boundary conditions you define are not put into effect
until you apply the range. To make a range take effect, call the ApplyRange method.
ApplyRange immediately restricts a user’s view of and access to data in the specified
subset of the dataset.
Canceling a range
The CancelRange method ends application of a range and restores access to the full
dataset. Even though canceling a range restores access to all records in the dataset,
the boundary conditions for that range are still available so that you can reapply the
range at a later time. Range boundaries are preserved until you provide new range
boundaries or modify the existing boundaries. For example, the following code is
valid:
ƒ
MyTable.CancelRange;
ƒ
{later on, use the same range again. No need to call SetRangeStart, etc.}
MyTable.ApplyRange;
ƒ
The dataset is linked to the master table based on its current index. Before you specify
the fields in the master dataset that are tracked by the detail dataset, first specify the
index in the detail dataset that starts with the corresponding fields. You can use
either the IndexName or the IndexFieldNames property.
Once you specify the index to use, use the MasterFields property to indicate the
column(s) in the master dataset that correspond to the index fields in the detail table.
To link datasets on multiple column names, separate field names with semicolons:
Parts.MasterFields := 'OrderNo;ItemNo';
To help create meaningful links between two datasets, you can use the Field Link
designer. To use the Field Link designer, double click on the MasterFields property in
the Object Inspector after you have assigned a MasterSource and an index.
The following steps create a simple form in which a user can scroll through customer
records and display all orders for the current customer. The master table is the
CustomersTable table, and the detail table is OrdersTable. The example uses the BDE-
based TTable component, but you can use the same methods to link any table type
datasets.
1 Place two TTable components and two TDataSource components in a data module.
2 Set the properties of the first TTable component as follows:
• DatabaseName: DBDEMOS
• TableName: CUSTOMER
• Name: CustomersTable
3 Set the properties of the second TTable component as follows:
• DatabaseName: DBDEMOS
• TableName: ORDERS
• Name: OrdersTable
4 Set the properties of the first TDataSource component as follows:
• Name: CustSource
• DataSet: CustomersTable
5 Set the properties of the second TDataSource component as follows:
• Name: OrdersSource
• DataSet: OrdersTable
6 Place two TDBGrid components on a form.
7 Choose File|Use Unit to specify that the form should use the data module.
8 Set the DataSource property of the first grid component to
“CustSource”, and set the DataSource property of the second grid to
“OrdersSource”.
The dataset component for the detail table is a dataset descendant of a type allowed
by the master table. TTable components only allow TNestedDataSet components as
nested datasets. TSQLTable components allow other TSQLTable components.
TClientDataset components allow other client datasets. Choose a dataset of the
appropriate type from the Component palette and add it to your form or data
module. Set this detail dataset’s DataSetField property to the persistent DataSet field
in the master dataset. Finally, place a data source component on the form or data
module and set its DataSet property to the detail dataset. Data-aware controls can use
this data source to access the data in the detail set.
Creating tables
TTable and TIBTable both let you create the underlying database table without using
SQL. Similarly, TClientDataSet lets you create a dataset when you are not working
with a dataset provider. Using TTable and TClientDataSet, you can create the table at
design time or runtime. TIBTable only lets you create tables at runtime.
Before you can create the table, you must be set properties to specify the structure of
the table you are creating. In particular, you must specify
• The database that will host the new table. For TTable, you specify the database
using the DatabaseName property. For TIBTable, you must use a TIBDatabase
component, which is assigned to the Database property. (Client datasets do not use
a database.)
• The type of database (TTable only). Set the TableType property to the desired type
of table. For Paradox, dBASE, or ASCII tables, set TableType to ttParadox, ttDBase,
or ttASCII, respectively. For all other table types, set TableType to ttDefault.
• The name of the table you want to create. Both TTable and TIBTable have a
TableName property for the name of the new table. Client datasets do not use a
table name, but you should specify the FileName property before you save the new
table. If you create a table that duplicates the name of an existing table, the existing
table and all its data are overwritten by the newly created table. The old table and
its data cannot be recovered. To avoid overwriting an existing table, you can check
the Exists property at runtime. Exists is only available on TTable and TIBTable.
• The fields for the new table. There are two ways to do this:
• You can add field definitions to the FieldDefs property. At design time, double-
click the FieldDefs property in the Object Inspector to bring up the collection
editor. Use the collection editor to add, remove, or change the properties of the
field definitions. At runtime, clear any existing field definitions and then use
the AddFieldDef method to add each new field definition. For each new field
definition, set the properties of the TFieldDef object to specify the desired
attributes of the field.
• You can use persistent field components instead. At design time, double-click
on the dataset to bring up the Fields editor. In the Fields editor, right-click and
choose the New Field command. Describe the basic properties of your field.
Once the field is created, you can alter its properties in the Object Inspector by
selecting the field in the Fields editor.
• Indexes for the new table (optional). At design time, double-click the IndexDefs
property in the Object Inspector to bring up the collection editor. Use the
collection editor to add, remove, or change the properties of index definitions. At
runtime, clear any existing index definitions, and then use the AddIndexDef
method to add each new index definition. For each new index definition, set the
properties of the TIndexDef object to specify the desired attributes of the index.
Note You can’t define indexes for the new table if you are using persistent field
components instead of field definition objects.
To create the table at design time, right-click the dataset and choose Create Table
(TTable) or Create Data Set (TClientDataSet). This command does not appear on the
context menu until you have specified all the necessary information.
To create the table at runtime, call the CreateTable method (TTable and TIBTable) or the
CreateDataSet method (TClientDataSet).
Note You can set up the definitions at design time and then call the CreateTable (or
CreateDataSet) method at runtime to create the table. However, to do so you must
indicate that the definitions specified at runtime should be saved with the dataset
component. (by default, field and index definitions are generated dynamically at
runtime). Specify that the definitions should be saved with the dataset by setting its
StoreDefs property to True.
Tip If you are using TTable, you can preload the field definitions and index definitions of
an existing table at design time. Set the DatabaseName and TableName properties to
specify the existing table. Right click the table component and choose Update Table
Definition. This automatically sets the values of the FieldDefs and IndexDefs
properties to describe the fields and indexes of the existing table. Next, reset the
DatabaseName and TableName to specify the table you want to create, canceling any
prompts to rename the existing table.
Note When creating Oracle8 tables, you can’t create object fields (ADT fields, array fields,
and dataset fields).
The following code creates a new table at runtime and associates it with the
DBDEMOS alias. Before it creates the new table, it verifies that the table name
provided does not match the name of an existing table:
var
TableFound: Boolean;
begin
with TTable.Create(nil) do // create a temporary TTable component
begin
try
{ set properties of the temporary TTable component }
Active := False;
DatabaseName := 'DBDEMOS';
TableName := Edit1.Text;
TableType := ttDefault;
{ define fields for the new table }
FieldDefs.Clear;
with FieldDefs.AddFieldDef do begin
Name := 'First';
DataType := ftString;
Size := 20;
Required := False;
end;
with FieldDefs.AddFieldDef do begin
Name := 'Second';
DataType := ftString;
Size := 30;
Required := False;
end;
{ define indexes for the new table }
IndexDefs.Clear;
with IndexDefs.AddIndexDef do begin
Name := '';
Fields := 'First';
Options := [ixPrimary];
end;
Deleting tables
TTable and TIBTable let you delete tables from the underlying database table without
using SQL. To delete a table at runtime, call the dataset’s DeleteTable method. For
example, the following statement removes the table underlying a dataset:
CustomersTable.DeleteTable;
Caution When you delete a table with DeleteTable, the table and all its data are gone forever.
If you are using TTable, you can also delete tables at design time: Right-click the table
component and select Delete Table from the context menu. The Delete Table menu
pick is only present if the table component represents an existing database table (the
DatabaseName and TableName properties specify an existing table).
Emptying tables
Many table type datasets supply a single method that lets you delete all rows of data
in the table.
• For TTable and TIBTable, you can delete all the records by calling the EmptyTable
method at runtime:
PhoneTable.EmptyTable;
• For TADOTable, you can use the DeleteRecords method.
PhoneTable.DeleteRecords;
• For TSQLTable, you can use the DeleteRecords method as well. Note, however, that
the TSQLTable version of DeleteRecords never takes any parameters.
PhoneTable.DeleteRecords;
• For client datasets, you can use the EmptyDataSet method.
PhoneTable.EmptyDataSet;
Note For tables on SQL servers, these methods only succeed if you have DELETE privilege
for the table.
Caution When you empty a dataset, the data you delete is gone forever.
Synchronizing tables
If you have two or more datasets that represent the same database table but do not
share a data source component, then each dataset has its own view on the data and
its own current record. As users access records through each datasets, the
components’ current records will differ.
If the datasets are all instances of TTable, or all instances of TIBTable, or all client
datasets, you can force the current record for each of these datasets to be the same by
calling the GotoCurrent method. GotoCurrent sets its own dataset’s current record to
the current record of a matching dataset. For example, the following code sets the
current record of CustomerTableOne to be the same as the current record of
CustomerTableTwo:
CustomerTableOne.GotoCurrent(CustomerTableTwo);
Tip If your application needs to synchronize datasets in this manner, put the datasets in a
data module and add the unit for the data module to the uses clause of each unit that
accesses the tables.
To synchronize datasets from separate forms, you must add one form’s unit to the
uses clause of the other, and you must qualify at least one of the dataset names with
its form name. For example:
CustomerTableOne.GotoCurrent(Form2.CustomerTableTwo);
4 If the query data is to be used with visual data controls, add a data source
component to the data module, and set its DataSet property to the query-type
dataset. The data source component forwards the results of the query (called a
result set) to data-aware components for display. Connect data-aware components
to the data source using their DataSource and DataField properties.
5 Activate the query component. For queries that return a result set, use the Active
property or the Open method. To execute queries that only perform an action on a
table and return no result set, use the ExecSQL method at runtime. If you plan to
execute the query more than once, you may want to call Prepare to initialize the
data access layer and bind parameter values into the query. For information about
preparing a query, see “Preparing queries” on page 24-48.
Note The parameter collection editor is the same collection editor that appears for other
collection properties. Because the editor is shared with other properties, its right-click
context menu contains the Add and Delete commands. However, they are never
enabled for query parameters. The only way to add or delete parameters is in the
SQL statement itself.
For each parameter, select it in the parameter collection editor. Then use the Object
Inspector to modify its properties.
When using the Params property (TParam objects), you will want to inspect or modify
the following:
• The DataType property lists the data type for the parameter’s value. For some
datasets, this value may be correctly initialized. If the dataset did not deduce the
type, DataType is ftUnknown, and you must change it to indicate the type of the
parameter value.
The DataType property lists the logical data type for the parameter. In general,
these data types conform to server data types. For specific logical type-to-server
data type mappings, see the documentation for the data access mechanism (BDE,
dbExpress, InterBase).
• The ParamType property lists the type of the selected parameter. For queries, this is
always ptInput, because queries can only contain input parameters. If the value of
ParamType is ptUnknown, change it to ptInput.
• The Value property specifies a value for the selected parameter. You can leave
Value blank if your application supplies parameter values at runtime.
When using the Parameters property (TParameter objects), you will want to inspect or
modify the following:
• The DataType property lists the data type for the parameter’s value. For some data
types, you must provide additional information:
• The NumericScale property indicates the number of decimal places for numeric
parameters.
• The Precision property indicates the total number of digits for numeric
parameters.
• The Size property indicates the number of characters in string parameters.
• The Direction property lists the type of the selected parameter. For queries, this is
always pdInput, because queries can only contain input parameters.
• The Attributes property controls the type of values the parameter will accept.
Attributes may be set to a combination of psSigned, psNullable, and psLong.
• The Value property specifies a value for the selected parameter. You can leave
Value blank if your application supplies parameter values at runtime.
To illustrate how this works, consider two tables: a customer table and an orders
table. For every customer, the orders table contains a set of orders that the customer
made. The Customer table includes an ID field that specifies a unique customer ID.
The orders table includes a CustID field that specifies the ID of the customer who
made an order.
The first step is to set up the Customer dataset:
1 Add a table type dataset to your application and bind it to the Customer table.
2 Add a TDataSource component named CustomerSource. Set its DataSet property to
the dataset added in step 1. This data source now represents the Customer dataset.
3 Add a query-type dataset and set its SQL property to
SELECT CustID, OrderNo, SaleDate
FROM Orders
WHERE CustID = :ID
Note that the name of the parameter is the same as the name of the field in the
master (Customer) table.
4 Set the detail dataset’s DataSource property to CustomerSource. Setting this
property makes the detail dataset a linked query.
At runtime the :ID parameter in the SQL statement for the detail dataset is not
assigned a value, so the dataset tries to match the parameter by name against a
column in the dataset identified by CustomersSource. CustomersSource gets its data
from the master dataset, which, in turn, derives its data from the Customer table.
Because the Customer table contains a column called “ID,” the value from the ID
field in the current record of the master dataset is assigned to the :ID parameter for
the detail dataset’s SQL statement. The datasets are linked in a master-detail
relationship. Each time the current record changes in the Customers dataset, the
detail dataset’s SELECT statement executes to retrieve all orders based on the current
customer id.
Preparing queries
Preparing a query is an optional step that precedes query execution. Preparing a
query submits the SQL statement and its parameters, if any, to the data access layer
and the database server for parsing, resource allocation, and optimization. In some
datasets, the dataset may perform additional setup operations when preparing the
query. These operations improve query performance, making your application
faster, especially when working with updatable queries.
An application can prepare a query by setting the Prepared property to True. If you do
not prepare a query before executing it, the dataset automatically prepares it for you
each time you call Open or ExecSQL. Even though the dataset prepares queries for
you, you can improve performance by explicitly preparing the dataset before you
open it the first time.
CustQuery.Prepared := True;
When you explicitly prepare the dataset, the resources allocated for executing the
statement are not freed until you set Prepared to False.
Set the Prepared property to False if you want to ensure that the dataset is re-prepared
before it executes (for example, if you add a parameter).
Note When you change the text of the SQL property for a query, the dataset automatically
closes and unprepares the query.
If you do not need to be able to navigate backward through a result set, TQuery and
TIBQuery let you improve query performance by requesting a unidirectional cursor
instead. To request a unidirectional cursor, set the UniDirectional property to True.
Set UniDirectional before preparing and executing a query. The following code
illustrates setting UniDirectional prior to preparing and executing a query:
if not (CustomerQuery.Prepared) then
begin
CustomerQuery.UniDirectional := True;
CustomerQuery.Prepared := True;
end;
CustomerQuery.Open; { returns a result set with a one-way cursor }
Note Do not confuse the UniDirectional property with a unidirectional dataset.
Unidirectional datasets (TSQLDataSet, TSQLTable, TSQLQuery, and TSQLStoredProc)
use dbExpress, which only returns unidirectional cursors. In addition to restricting
the ability to navigate backwards, unidirectional datasets do not buffer records, and
so have additional limitations (such as the inability to use filters).
4 If the stored procedure returns a cursor to be used with visual data controls, add a
data source component to the data module, and set its DataSet property to the
stored procedure-type dataset. Connect data-aware components to the data source
using their DataSource and DataField properties.
5 Provide input parameter values for the stored procedure, if necessary. If the server
does not provide information about all stored procedure parameters, you may
need to provide additional input parameter information, such as parameter names
and data types. For information about working with stored procedure parameters,
see “Working with stored procedure parameters” on page 24-51.
6 Execute the stored procedure. For stored procedures that return a cursor, use the
Active property or the Open method. To execute stored procedures that do not
return any results or that only return output parameters, use the ExecProc method
at runtime. If you plan to execute the stored procedure more than once, you may
want to call Prepare to initialize the data access layer and bind parameter values
into the stored procedure. For information about preparing a query, see
“Executing stored procedures that don’t return a result set” on page 24-55.
7 Process any results. These results can be returned as result and output parameters,
or they can be returned as a result set that populates the stored procedure-type
dataset. Some stored procedures return multiple cursors. For details on how to
access the additional cursors, see “Fetching multiple result sets” on page 24-56.
• The ParamType property indicates the type of the selected parameter. This can be
ptInput (for input parameters), ptOutput (for output parameters), ptInputOutput
(for input/output parameters) or ptResult (for result parameters).
• The Value property specifies a value for the selected parameter. You can never set
values for output and result parameters. These types of parameters have values
set by the execution of the stored procedure. For input and input/output
parameters, you can leave Value blank if your application supplies parameter
values at runtime.
If the dataset uses a Parameters property (TParameter objects), the following properties
must be correctly specified:
• The Name property indicates the name of the parameter as it is defined by the
stored procedure.
• The DataType property gives the data type for the parameter’s value. For some
data types, you must provide additional information:
• The NumericScale property indicates the number of decimal places for numeric
parameters.
• The Precision property indicates the total number of digits for numeric
parameters.
• The Size property indicates the number of characters in string parameters.
• The Direction property gives the type of the selected parameter. This can be
pdInput (for input parameters), pdOutput (for output parameters), pdInputOutput
(for input/output parameters) or pdReturnValue (for result parameters).
• The Attributes property controls the type of values the parameter will accept.
Attributes may be set to a combination of psSigned, psNullable, and psLong.
• The Value property specifies a value for the selected parameter. Do not set values
for output and result parameters. For input and input/output parameters, you can
leave Value blank if your application supplies parameter values at runtime.
As you scroll from record to record in a dataset, a field component lets you view and
change the value for that field in the current record.
Field components have many properties in common with one another (such as
DisplayWidth and Alignment), and they have properties specific to their data types
(such as Precision for TFloatField). Each of these properties affect how data appears to
an application’s users on a form. Some properties, such as Precision, can also affect
what data values the user can enter in a control when modifying or entering data.
All field components for a dataset are either dynamic (automatically generated for
you based on the underlying structure of database tables), or persistent (generated
based on specific field names and properties you set in the Fields editor). Dynamic
and persistent fields have different strengths and are appropriate for different types
of applications. The following sections describe dynamic and persistent fields in
more detail and offer advice on choosing between them.
4 Place data-aware controls in the application’s forms, add the data module to each
uses clause for each form’s unit, and associate each data-aware control with a data
source in the module. In addition, associate a field with each data-aware control
that requires one. Note that because you are using dynamic field components,
there is no guarantee that any field name you specify will exist when the dataset is
opened.
5 Open the datasets.
Aside from ease of use, dynamic fields can be limiting. Without writing code, you
cannot change the display and editing defaults for dynamic fields, you cannot safely
change the order in which dynamic fields are displayed, and you cannot prevent
access to any fields in the dataset. You cannot create additional fields for the dataset,
such as calculated fields or lookup fields, and you cannot override a dynamic field’s
default data type. To gain control and flexibility over fields in your database
applications, you need to invoke the Fields editor to create persistent field
components for your datasets.
All fields used by a single dataset are either persistent or dynamic. You cannot mix
field types in a single dataset. If you create persistent fields for a dataset, and then
want to revert to dynamic fields, you must remove all persistent fields from the
dataset. For more information about dynamic fields, see “Dynamic field
components” on page 25-2.
Note One of the primary uses of persistent fields is to gain control over the appearance and
display of data. You can also control the appearance of columns in data-aware grids.
To learn about controlling column appearance in grids, see “Creating a customized
grid” on page 20-17.
The New Field dialog box contains three group boxes: Field properties, Field type,
and Lookup definition.
• The Field properties group box lets you enter general field component
information. Enter the field name in the Name edit box. The name you enter here
corresponds to the field component’s FieldName property. The New Field dialog
uses this name to build a component name in the Component edit box. The name
that appears in the Component edit box corresponds to the field component’s
Name property and is only provided for informational purposes (Name is the
identifier by which you refer to the field component in your source code). The
dialog discards anything you enter directly in the Component edit box.
• The Type combo box in the Field properties group lets you specify the field
component’s data type. You must supply a data type for any new field component
you create. For example, to display floating-point currency values in a field, select
Currency from the drop-down list. Use the Size edit box to specify the maximum
number of characters that can be displayed or entered in a string-based field, or
the size of Bytes and VarBytes fields. For all other data types, Size is meaningless.
• The Field type radio group lets you specify the type of new field component to
create. The default type is Data. If you choose Lookup, the Dataset and Source
Fields edit boxes in the Lookup definition group box are enabled. You can also
create Calculated fields, and if you are working with a client dataset, you can
create InternalCalc fields or Aggregate fields. The following table describes these
types of fields you can create:
The Lookup definition group box is only used to create lookup fields. This is described
more fully in “Defining a lookup field” on page 25-9.
To create a replacement data field for a field in a table underlying a dataset, follow
these steps:
1 Remove the field from the list of persistent fields assigned for the dataset, and then
choose New Field from the context menu.
2 In the New Field dialog box, enter the name of an existing field in the database
table in the Name edit box. Do not enter a new field name. You are actually
specifying the name of the field from which your new field will derive its data.
3 Choose a new data type for the field from the Type combo box. The data type you
choose should be different from the data type of the field you are replacing. You
cannot replace a string field of one size with a string field of another size. Note that
while the data type should be different, it must be compatible with the actual data
type of the field in the underlying table.
4 Enter the size of the field in the Size edit box, if appropriate. Size is only relevant
for fields of type TStringField, TBytesField, and TVarBytesField.
5 Select Data in the Field type radio group if it is not already selected.
6 Choose OK. The New Field dialog box closes, the newly defined data field
replaces the existing field you specified in Step 1, and the component declaration
in the data module or form’s type declaration is updated.
To edit the properties or events associated with the field component, select the
component name in the Field editor list box, then edit its properties or events with
the Object Inspector. For more information about editing field component properties
and events, see “Setting persistent field properties and events” on page 25-11.
5 Choose OK. The newly defined calculated field is automatically added to the end
of the list of persistent fields in the Field editor list box, and the component
declaration is automatically added to the form’s or data module’s type
declaration.
6 Place code that calculates values for the field in the OnCalcFields event handler for
the dataset. For more information about writing code to calculate field values, see
“Programming a calculated field” on page 25-8.
Note To edit the properties or events associated with the field component, select the
component name in the Field editor list box, then edit its properties or events with
the Object Inspector. For more information about editing field component properties
and events, see “Setting persistent field properties and events” on page 25-11.
You can use the LookupCache property to hone the way lookup fields are determined.
LookupCache determines whether the values of a lookup field are cached in memory
when a dataset is first opened, or looked up dynamically every time the current
record in the dataset changes. Set LookupCache to True to cache the values of a lookup
field when the LookupDataSet is unlikely to change and the number of distinct lookup
values is small. Caching lookup values can speed performance, because the lookup
values for every set of LookupKeyFields values are preloaded when the DataSet is
opened. When the current record in the DataSet changes, the field object can locate its
Value in the cache, rather than accessing the LookupDataSet. This performance
improvement is especially dramatic if the LookupDataSet is on a network where
access is slow.
Tip nilTrueIf every record of DataSet has different values for KeyFields, the overhead of
locating values in the cache can be greater than any performance benefit provided by
the cache. The overhead of locating values in the cache increases with the number of
distinct values that can be taken by KeyFields.
If LookupDataSet is volatile, caching lookup values can lead to inaccurate results. Call
RefreshLookupList to update the values in the lookup cache. RefreshLookupList
regenerates the LookupList property, which contains the value of the LookupResultField
for every set of LookupKeyFields values.
When setting LookupCache at runtime, call RefreshLookupList to initialize the cache.
Not all properties are available for all field components. For example, a field
component of type TStringField does not have Currency, MaxValue, or DisplayFormat
properties, and a component of type TFloatField does not have a Size property.
While the purpose of most properties is straightforward, some properties, such as
Calculated, require additional programming steps to be useful. Others, such as
DisplayFormat, EditFormat, and EditMask, are interrelated; their settings must be
coordinated. For more information about using DisplayFormat, EditFormat, and
EditMask, see “Controlling and masking user input” on page 25-15.
Once you have created a new attribute set and added it to the Data Dictionary, you
can then associate it with other persistent field components. Even if you later remove
the association, the attribute set remains defined in the Data Dictionary.
Note You can also create attribute sets directly from the SQL Explorer. When you create an
attribute set using SQL Explorer, it is added to the Data Dictionary, but not applied to
any fields. SQL Explorer lets you specify two additional attributes: a field type (such
as TFloatField, TStringField, and so on) and a data-aware control (such as TDBEdit,
TDBCheckBox, and so on) that are automatically placed on a form when a field based
on the attribute set is dragged to the form. For more information, see the online help
for the SQL Explorer.
Only format properties appropriate to the data type of a field component are
available for a given component.
Default formatting conventions for date, time, currency, and numeric values are
based on the Regional Settings properties in the Control Panel. For example, using
the default settings for the United States, a TFloatField column with the Currency
property set to True sets the DisplayFormat property for the value 1234.56 to $1234.56,
while the EditFormat is 1234.56.
At design time or runtime, you can edit the DisplayFormat and EditFormat properties
of a field component to override the default display settings for that field. You can
also write OnGetText and OnSetText event handlers to do custom formatting for field
components at runtime.
Handling events
Like most components, field components have events associated with them. Methods
can be assigned as handlers for these events. By writing these handlers you can react
to the occurrence of events that affect data entered in fields through data-aware
controls and perform actions of your own design. The following table lists the events
associated with field components:
OnGetText and OnSetText events are primarily useful to programmers who want to
do custom formatting that goes beyond the built-in formatting functions. OnChange
is useful for performing application-specific tasks associated with data change, such
as enabling or disabling menus or visual controls. OnValidate is useful when you
want to control data-entry validation in your application before returning values to a
database server.
To write an event handler for a field component:
1 Select the component.
2 Select the Events page in the Object Inspector.
3 Double-click the Value field for the event handler to display its source code
window.
4 Create or edit the handler code.
AsFloat
AsCurrency AsDateTime
AsVariant AsString AsInteger AsBCD AsSQLTimeStamp AsBoolean
TStringField yes NA yes yes yes yes
TWideStringField yes yes yes yes yes yes
TIntegerField yes yes NA yes
TSmallIntField yes yes yes yes
TWordField yes yes yes yes
TLargeintField yes yes yes yes
TFloatField yes yes yes yes
TCurrencyField yes yes yes yes
TBCDField yes yes yes yes
TFMTBCDField yes yes yes yes
TDateTimeField yes yes yes yes
TDateField yes yes yes yes
TTimeField yes yes yes yes
TSQLTimeStampField yes yes yes yes
TBooleanField yes yes
TBytesField yes yes
TVarBytesField yes yes
TBlobField yes yes
TMemoField yes yes
TGraphicField yes yes
TVariantField NA yes yes yes yes yes
TAggregateField yes yes
Note that some columns in the table refer to more than one conversion property
(such as AsFloat, AsCurrency, and AsBCD). This is because all field data types that
support one of those properties always support the others as well.
Note also that the AsVariant property can translate among all data types. For any
datatypes not listed above, AsVariant is also available (and is, in fact, the only option).
When in doubt, use AsVariant.
In some cases, conversions are not always possible. For example, AsDateTime can be
used to convert a string to a date, time, or datetime format only if the string value is
in a recognizable datetime format. A failed conversion attempt raises an exception.
In some other cases, conversion is possible, but the results of the conversion are not
always intuitive. For example, what does it mean to convert a TDateTimeField value
into a float format? AsFloat converts the date portion of the field to the number of
days since 12/31/1899, and it converts the time portion of the field to a fraction of 24
hours. Table 25.7 lists permissible conversions that produce special results:
In other cases, conversions are not possible at all. In these cases, attempting a
conversion also raises an exception.
Conversion always occurs before an assignment is made. For example, the following
statement converts the value of CustomersCustNo to a string and assigns the string to
the text of an edit control:
Edit1.Text := CustomersCustNo.AsString;
Conversely, the next statement assigns the text of an edit control to the
CustomersCustNo field as an integer:
MyTableMyField.AsInteger := StrToInt(Edit1.Text);
Dataset and reference fields are fields that access other data sets. A dataset field
provides access to a nested (detail) dataset and a reference field stores a pointer
(reference) to another persistent object (ADT).
When you add fields with the Fields editor to a dataset that contains object fields,
persistent object fields of the correct type are automatically created for you. Adding
persistent object fields to a dataset automatically sets the dataset’s ObjectView
property to True, which instructs the dataset to store these fields hierarchically, rather
than flattening them out as if the constituent child fields were separate, independent
fields.
The following properties are common to all object fields and provide the
functionality to handle child fields and datasets.
Given these persistent fields, the following code uses a persistent field to assign an
array element value to an edit box named TelEdit.
TelEdit.Text := CustomerTelNos_Array0.AsString;
BDE-based architecture
When using the BDE, your application uses a variation of the general database
architecture described in “Database architecture” on page 19-6. In addition to the
user interface elements, datasource, and datasets common to all Delphi database
applications, A BDE-based application can include
• One or more database components to control transactions and to manage database
connections.
• One or more session components to isolate data access operations such as database
connections, and to manage groups of databases.
database
Session
Note If you use a session component, the SessionName property of a dataset must match the
SessionName property for the database component with which the dataset is
associated.
For more information about TDatabase and TSession, see “Connecting to databases
with TDatabase” on page 26-12 and “Managing database sessions” on page 26-16.
Caching BLOBs
BDE-enabled datasets all have a CacheBlobs property that controls whether BLOB
fields are cached locally by the BDE when an application reads BLOB records. By
default, CacheBlobs is True, meaning that the BDE caches a local copy of BLOB fields.
Caching BLOBs improves application performance by enabling the BDE to store local
copies of BLOBs instead of fetching them repeatedly from the database server as a
user scrolls through records.
In applications and environments where BLOBs are frequently updated or replaced,
and a fresh view of BLOB data is more important than application performance, you
can set CacheBlobs to False to ensure that your application always sees the latest
version of a BLOB field.
Using TTable
TTable encapsulates the full structure of and data in an underlying database table. It
implements all of the basic functionality introduced by TDataSet, as well as all of the
special features typical of table type datasets. Before looking at the unique features
introduced by TTable, you should familiarize yourself with the common database
features described in “Understanding datasets,” including the section on table type
datasets that starts on page 24-25.
Because TTable is a BDE-enabled dataset, it must be associated with a database and a
session. “Associating a dataset with database and session connections” on page 26-3
describes how you form these associations. Once the dataset is associated with a
database and session, you can bind it to a particular database table by setting the
TableName property and, if you are using a Paradox, dBASE, FoxPro, or comma-
delimited ASCII text table, the TableType property.
Note The table must be closed when you change its association to a database, session, or
database table, or when you set the TableType property. However, before you close
the table to change these properties, first post or discard any pending changes. If
cached updates are enabled, call the ApplyUpdates method to write the posted
changes to the database.
TTable components are unique in the support they offer for local database tables
(Paradox, dBASE, FoxPro, and comma-delimited ASCII text tables). The following
topics describe the special properties and methods that implement this support.
In addition, TTable components can take advantage of the BDE’s support for batch
operations (table level operations to append, update, delete, or copy entire groups of
records). This support is described in “Importing data from another table” on
page 26-8.
Table 26.1 Table types recognized by the BDE based on file extension
Extension Table type
No file extension Paradox
.DB Paradox
.DBF dBASE
.TXT ASCII text
If your local Paradox, dBASE, and ASCII text tables use the file extensions as
described in Table 26.1, then you can leave TableType set to ttDefault. Otherwise, your
application must set TableType to indicate the correct table type. Table 26.2 indicates
the values you can assign to TableType:
At design time, click the ellipsis button in the IndexFiles property value in the Object
Inspector to invoke the Index Files editor. To add one non-production index file or
.NDX file: click the Add button in the Index Files dialog and select the file from the
Open dialog. Repeat this process once for each non-production index file or .NDX
file. Click the OK button in the Index Files dialog after adding all desired indexes.
This same operation can be performed programmatically at runtime. To do this,
access the IndexFiles property using properties and methods of string lists. When
adding a new set of indexes, first call the Clear method of the table’s IndexFiles
property to remove any existing entries. Call the Add method to add each non-
production index file or .NDX file:
with Table2.IndexFiles do begin
Clear;
Add('Bystate.ndx');
Add('Byzip.ndx');
Add('Fullname.ndx');
Add('St_name.ndx');
end;
After adding any desired non-production or .NDX index files, the names of
individual indexes in the index file are available, and can be assigned to the
IndexName property. The index tags are also listed when using the GetIndexNames
method and when inspecting index definitions through the TIndexDef objects in the
IndexDefs property. Properly listed .NDX files are automatically updated as data is
added, changed, or deleted in the table (regardless of whether a given index is used
in the IndexName property).
In the example below, the IndexFiles for the AnimalsTable table component is set to the
non-production index file ANIMALS.MDX, and then its IndexName property is set to
the index tag called “NAME”:
AnimalsTable.IndexFiles.Add('ANIMALS.MDX');
AnimalsTable.IndexName := 'NAME';
Once you have specified the index file, using non-production or .NDX indexes works
the same as any other index. Specifying an index name sorts the data in the table and
makes it available for indexed-based searches, ranges, and (for non-production
indexes) master-detail linking. See “Using table type datasets” on page 24-25 for
details on these uses of indexes.
There are two special considerations when using dBASE III PLUS-style .NDX indexes
with TTable components. The first is that .NDX files cannot be used as the basis for
master-detail links. The second is that when activating a .NDX index with the
IndexName property, you must include the .NDX extension in the property value as
part of the index name:
with Table1 do begin
IndexName := 'ByState.NDX';
FindKey(['CA']);
end;
For example, the following code updates all records in the current table with records
from the Customer table that have the same values for fields in the current index:
Table1.BatchMove('CUSTOMER.DB', batUpdate);
BatchMove returns the number of records it imports successfully.
Caution Importing records using the batCopy mode overwrites existing records. To preserve
existing records use batAppend instead.
BatchMove performs only some of the batch operations supported by the BDE.
Additional functions are available using the TBatchMove component. If you need to
move a large amount of data between or among tables, use TBatchMove instead of
calling a table’s BatchMove method. For information about using TBatchMove, see
“Using TBatchMove” on page 26-49.
Using TQuery
TQuery represents a single Data Definition Language (DDL) or Data Manipulation
Language (DML) statement (For example, a SELECT, INSERT, DELETE, UPDATE,
CREATE INDEX, or ALTER TABLE command). The language used in commands is
server-specific, but usually compliant with the SQL-92 standard for the SQL
language. TQuery implements all of the basic functionality introduced by TDataSet,
as well as all of the special features typical of query-type datasets. Before looking at
the unique features introduced by TQuery, you should familiarize yourself with the
common database features described in “Understanding datasets,” including the
section on query-type datasets that starts on page 24-42.
Because TQuery is a BDE-enabled dataset, it must usually be associated with a
database and a session. (The one exception is when you use the TQuery for a
heterogeneous query.) “Associating a dataset with database
and session connections” on page 26-3 describes how you form these associations.
You specify the SQL statement for the query by setting the SQL property.
A TQuery component can access data in:
• Paradox or dBASE tables, using Local SQL, which is part of the BDE. Local SQL is
a subset of the SQL-92 specification. Most DML is supported and enough DDL
syntax to work with these types of tables. See the local SQL help,
LOCALSQL.HLP, for details on supported SQL syntax.
• Local InterBase Server databases, using the InterBase engine. For information on
InterBase’s SQL-92 standard SQL syntax support and extended syntax support,
see the InterBase Language Reference.
• Databases on remote database servers such as Oracle, Sybase, MS-SQL Server,
Informix, DB2, and InterBase. You must install the appropriate SQL Link driver
and client software (vendor-supplied) specific to the database server to access a
remote server. Any standard SQL syntax supported by these servers is allowed.
For information on SQL syntax, limitations, and extensions, see the documentation
for your particular server.
If an application requests and receives a live result set, the CanModify property of the
query component is set to True. Even if the query returns a live result set, you may
not be able to update the result set directly if it contains linked fields or you switch
indexes before attempting an update. If these conditions exist, you should treat the
result set as a read-only result set, and update it accordingly.
If an application requests a live result set, but the SELECT statement syntax does not
allow it, the BDE returns either
• A read-only result set for queries made against Paradox or dBASE.
• An error code for SQL queries made against a remote server.
Using TStoredProc
TStoredProc represents a stored procedure. It implements all of the basic functionality
introduced by TDataSet, as well as most of the special features typical of stored
procedure-type datasets. Before looking at the unique features introduced by
TStoredProc, you should familiarize yourself with the common database features
described in “Understanding datasets,” including the section on stored procedure-
type datasets that starts on page 24-50.
Because TStoredProc is a BDE-enabled dataset, it must be associated with a database
and a session. “Associating a dataset with database and session connections” on
page 26-3 describes how you form these associations. Once the dataset is associated
with a database and session, you can bind it to a particular stored procedure by
setting the StoredProcName property.
TStoredProc differs from other stored procedure-type datasets in the following ways:
• It gives you greater control over how to bind parameters.
• It provides support for Oracle overloaded stored procedures.
Binding parameters
When you prepare and execute a stored procedure, its input parameters are
automatically bound to parameters on the server.
TStoredProc lets you use the ParamBindMode property to specify how parameters
should be bound to the parameters on the server. By default ParamBindMode is set to
pbByName, meaning that parameters from the stored procedure component are
matched to those on the server by name. This is the easiest method of binding
parameters.
Some servers also support binding parameters by ordinal value, the order in which
the parameters appear in the stored procedure. In this case the order in which you
specify parameters in the parameter collection editor is significant. The first
parameter you specify is matched to the first input parameter on the server, the
second parameter is matched to the second input parameter on the server, and so on.
If your server supports parameter binding by ordinal value, you can set
ParamBindMode to pbByNumber.
Tip If you want to set ParamBindMode to pbByNumber, you need to specify the correct
parameter types in the correct order. You can view a server’s stored procedure source
code in the SQL Explorer to determine the correct order and type of parameters to
specify.
• Double-click the Params property in the Object Inspector to invoke the String List
editor.
• Double-click a database component in a data module or form to invoke the
Database Properties editor.
All of these methods edit the Params property for the database component. Params is
a string list containing the database connection parameters for the BDE alias
associated with a database component. Some typical connection parameters include
path statement, server name, schema caching size, language driver, and SQL query
mode.
When you first invoke the Database Properties editor, the parameters for the BDE
alias are not visible. To see the current settings, click Defaults. The current
parameters are displayed in the Parameter overrides memo box. You can edit
existing entries or add new ones. To clear existing parameters, click Clear. Changes
you make take effect only when you click OK.
At runtime, an application can set alias parameters only by editing the Params
property directly. For more information about parameters specific to using SQL
Links drivers with the BDE, see your online SQL Links help file.
Using ODBC
An application can use ODBC data sources (for example, Btrieve). An ODBC driver
connection requires
• A vendor-supplied ODBC driver.
• The Microsoft ODBC Driver Manager.
• The BDE Administration utility.
To set up a BDE alias for an ODBC driver connection, use the BDE Administration
utility. For more information, see the BDE Administration utility’s online help file.
To use the default session, you need write no code unless your application must
• Explicitly activate or deactivate a session, enabling or disabling the session’s
databases’ ability to open.
• Modify the properties of the session, such as specifying default properties for
implicitly generated database components.
• Execute a session’s methods, such as managing database connections (for example
opening and closing database connections in response to user actions).
• Respond to session events, such as when the application attempts to access a
password-protected Paradox or dBASE table.
• Set Paradox directory locations such as the NetFileDir property to access Paradox
tables on a network and the PrivateDir property to a local hard drive to speed
performance.
• Manage the BDE aliases that describe possible database connection configurations
for databases and datasets that use the session.
Whether you add database components to an application at design time or create
them dynamically at runtime, they are automatically associated with the default
session unless you specifically assign them to a different session. If you open a
dataset that is not associated with a database component, Delphi automatically
• Creates a database component for it at runtime.
• Associates the database component with the default session.
• Initializes some of the database component’s key properties based on the default
session’s properties. Among the most important of these properties is
KeepConnections, which determines when database connections are maintained or
dropped by an application.
The default session provides a widely applicable set of defaults that can be used as is
by most applications. You need only associate a database component with an
explicitly named session if the component performs a simultaneous query against a
database already opened by the default session. In this case, each concurrent query
must run under its own session. Multi-threaded database applications also require
multiple sessions, where each thread has its own session.
Applications can create additional session components as needed. BDE-based
database applications automatically include a session list component, named
Sessions, that you can use to manage all of your session components. For more
information about managing multiple sessions see, “Managing multiple sessions” on
page 26-29.
You can safely place session components in data modules. If you put a data module
that contains one or more session components into the Object Repository, however,
make sure to set the AutoSessionName property to True to avoid namespace conflicts
when users inherit from it.
Activating a session
Active is a Boolean property that determines if database and dataset components
associated with a session are open. You can use this property to read the current state
of a session’s database and dataset connections, or to change it. If Active is False (the
default), all databases and datasets associated with the session are closed. If True,
databases and datasets are open.
A session is activated when it is first created, and subsequently, whenever its Active
property is changed to True from False (for example, when a database or dataset is
associated with a session is opened and there are currently no other open databases or
datasets). Setting Active to True triggers a session’s OnStartup event, registers the
paradox directory locations with the BDE, and registers the ConfigMode property,
which determines what BDE aliases are available within the session. You can write
an OnStartup event handler to initialize the NetFileDir, PrivateDir, and ConfigMode
properties before they are registered with the BDE, or to perform other specific
session start-up activities. For information about the NetFileDir and PrivateDir
properties, see “Specifying Paradox directory locations” on page 26-24. For
information about ConfigMode, see “Working with BDE aliases” on page 26-25.
Once a session is active, you can open its database connections by calling the
OpenDatabase method.
For session components you place in a data module or form, setting Active to False
when there are open databases or datasets closes them. At runtime, closing databases
and datasets may trigger events associated with them.
Note You cannot set Active to False for the default session at design time. While you can
close the default session at runtime, it is not recommended.
You can also use a session’s Open and Close methods to activate or deactivate sessions
other than the default session at runtime. For example, the following single line of
code closes all open databases and datasets for a session:
Session1.Close;
This code sets Session1’s Active property to False. When a session’s Active property is
False, any subsequent attempt by the application to open a database or dataset resets
Active to True and calls the session’s OnStartup event handler if it exists. You can also
explicitly code session reactivation at runtime. The following code reactivates
Session1:
Session1.Open;
Note If a session is active you can also open and close individual database connections. For
more information, see “Closing database connections” on page 26-20.
Note Connection persistence for a database component you explicitly place in a data
module or form is controlled by that database component’s KeepConnection property.
If set differently, KeepConnection for a database component always overrides the
KeepConnections property of the session. For more information about controlling
individual database connections within a session, see “Managing database
connections” on page 26-19.
KeepConnections should be set to True for applications that frequently open and close
all datasets associated with a database on a remote server. This setting reduces
network traffic and speeds data access because it means that a connection need only
be opened and closed once during the lifetime of the session. Otherwise, every time
the application closes or reestablishes a connection, it incurs the overhead of
attaching and detaching the database.
Note Even when KeepConnections is True for a session, you can close and free inactive
database connections for all implicit database components by calling the
DropConnections method. For more information about DropConnections, see
“Dropping inactive database connections” on page 26-20.
If you provide a handler for the OnPassword event, do two things in the event
handler: call the AddPassword method and set the event handler’s Continue parameter
to True. The AddPassword method passes a string to the session to be used as a
password for the table. The Continue parameter indicates to Delphi that no further
password prompting need be done for this table open attempt. The default value for
Continue is False, and so requires explicitly setting it to True. If Continue is False after
the event handler has finished executing, an OnPassword event fires again—even if a
valid password has been passed using AddPassword. If Continue is True after
execution of the event handler and the string passed with AddPassword is not the
valid password, the table open attempt fails and an exception is raised.
OnPassword can be triggered by two circumstances. The first is an attempt to open a
password-protected table (dBASE or Paradox) when a valid password has not
already been supplied to the session. (If a valid password for that table has already
been supplied, the OnPassword event does not occur.)
The other circumstance is a call to the GetPassword method. GetPassword either generates
an OnPassword event, or, if the session does not have an OnPassword event handler, displays
a default password dialog. It returns True if the OnPassword event handler or default dialog
added a password to the session, and False if no entry at all was made.
In the following example, the Password method is designated as the OnPassword event
handler for the default session by assigning it to the global Session object’s
OnPassword property.
procedure TForm1.FormCreate(Sender: TObject);
begin
Session.OnPassword := Password;
end;
In the Password method, the InputBox function prompts the user for a password. The
AddPassword method then programmatically supplies the password entered in the
dialog to the session.
procedure TForm1.Password(Sender: TObject; var Continue: Boolean);
var
Passwrd: String;
begin
Passwrd := InputBox('Enter password', 'Password:', '');
Continue := (Passwrd > '');
Session.AddPassword(Passwrd);
end;
The OnPassword event (and thus the Password event handler) is triggered by an
attempt to open a password-protected table, as demonstrated below. Even though
the user is prompted for a password in the handler for the OnPassword event, the
table open attempt can still fail if they enter an invalid password or something else
goes wrong.
procedure TForm1.OpenTableBtnClick(Sender: TObject);
const
CRLF = #13 + #10;
begin
try
Table1.Open; { this line triggers the OnPassword event }
except
on E:Exception do begin { exception if cannot open table }
ShowMessage('Error!' + CRLF + { display error explaining what happened }
E.Message + CRLF +
'Terminating application...');
Application.Terminate; { end the application }
end;
end;
end;
To make a newly created alias available to all sessions and to other applications, use
the session’s SaveConfigFile method. SaveConfigFile writes aliases in memory to the
BDE configuration file where they can be read and used by other BDE-enabled
applications.
After you create an alias, you can make changes to its parameters by calling
ModifyAlias. ModifyAlias takes two parameters: the name of the alias to modify and a
string list containing the parameters to change and their values. For example, the
following statements use ModifyAlias to change the OPEN MODE parameter for the
CATS alias to READ/WRITE in the default session:
var
List: TStringList;
begin
List := TStringList.Create;
with List do begin
Clear;
Add('OPEN MODE=READ/WRITE');
end;
Session.ModifyAlias('CATS', List);
List.Free;
ƒ
To delete an alias previously created in a session, call the DeleteAlias method.
DeleteAlias takes one parameter, the name of the alias to delete. DeleteAlias makes an
alias unavailable to the session.
Note DeleteAlias does not remove an alias from the BDE configuration file if the alias was
written to the file by a previous call to SaveConfigFile. To remove the alias from the
configuration file after calling DeleteAlias, call SaveConfigFile again.
Session components provide five methods for retrieving information about a BDE
aliases, including parameter information and driver information. They are:
• GetAliasNames, to list the aliases to which a session has access.
• GetAliasParams, to list the parameters for a specified alias.
• GetAliasDriverName, to return the name of the BDE driver used by the alias.
• GetDriverNames, to return a list of all BDE drivers available to the session.
• GetDriverParams, to return driver parameters for a specified driver.
For more information about using a session’s informational methods, see “Retrieving
information about a session” below. For more information about BDE aliases and the
SQL Links drivers with which they work, see the BDE online help, BDE32.HLP.
Except for GetAliasDriverName, these methods return a set of values into a string list
declared and maintained by your application. (GetAliasDriverName returns a single
string, the name of the current BDE driver for a particular database component used
by the session.)
For example, the following code retrieves the names of all database components and
aliases known to the default session:
var
List: TStringList;
begin
List := TStringList.Create;
try
Session.GetDatabaseNames(List);
ƒ
finally
List.Free;
end;
end;
Naming a session
A session’s SessionName property is used to name the session so that you can
associate databases and datasets with it. For the default session, SessionName is
“Default,” For each additional session component you create, you must set its
SessionName property to a unique value.
Database and dataset components have SessionName properties that correspond to
the SessionName property of a session component. If you leave the SessionName
property blank for a database or dataset component it is automatically associated
with the default session. You can also set SessionName for a database or dataset
component to a name that corresponds to the SessionName of a session component
you create.
The following code uses the OpenSession method of the default TSessionList
component, Sessions, to open a new session component, sets its SessionName to
“InterBaseSession,” activate the session, and associate an existing database
component Database1 with that session:
var
IBSession: TSession;
ƒ
begin
IBSession := Sessions.OpenSession('InterBaseSession');
Database1.SessionName := 'InterBaseSession';
end;
Table 26.6 Properties, methods, and events for cached updates (continued)
On BDE-enabled datasets
(or TDatabase) On TBDEClientDataSet Purpose
OnUpdateError OnReconcileError An event for handling update errors on
a record-by-record basis.
OnUpdateRecord BeforeUpdateRecord An event for processing updates on a
record-by-record basis.
ApplyUpdates ApplyUpdates Applies records in the local cache to the
ApplyUpdates (database) database.
CancelUpdates CancelUpdates Removes all pending updates from the
local cache without applying them.
CommitUpdates Reconcile Clears the update cache following
successful application of updates.
FetchAll GetNextPacket Copies database records to the local
(and PacketRecords) cache for editing and updating.
RevertRecord RevertRecord Undoes updates to the current record if
updates are not yet applied.
For an overview of the cached update process, see “Overview of using cached
updates” on page 29-17.
Note Even if you are using a client dataset to cache updates, you may want to read the
section about update objects on page 26-40. You can use update objects in the
BeforeUpdateRecord event handler of TBDEClientDataSet or TDataSetProvider to apply
updates from stored procedures or multi-table queries.
Important To apply updates from a stored procedure or an SQL query that does not return a
live result set, you must use TUpdateSQL to specify how to perform updates. For
updates to joins (queries involving two or more tables), you must provide one
TUpdateSQL object for each table involved, and you must use the OnUpdateRecord
event handler to invoke these objects to perform the updates. See “Using update
objects to update a dataset” on page 26-40 for details.
The following code illustrates how you apply updates within a transaction for the
CustomerQuery dataset:
procedure TForm1.ApplyButtonClick(Sender: TObject)
begin
Database1.StartTransaction;
try
if not (Database1.IsSQLBased) and not (Database1.TransIsolation = tiDirtyRead) then
Database1.TransIsolation := tiDirtyRead;
CustomerQuery.ApplyUpdates; { try to write the updates to the database }
Database1.Commit; { on success, commit the changes }
except
Database1.Rollback; { on failure, undo any changes }
raise; { raise the exception again to prevent a call to CommitUpdates }
end;
CustomerQuery.CommitUpdates; { on success, clear the internal cache }
end;
If an exception is raised during the ApplyUpdates call, the database transaction is
rolled back. Rolling back the transaction ensures that the underlying database table is
not changed. The raise statement inside the try...except block reraises the exception,
thereby preventing the call to CommitUpdates. Because CommitUpdates is not called,
the internal cache of updates is not cleared so that you can handle error conditions
and possibly retry the update.
The UpdateAction parameter indicates whether you applied the update. Values for
UpdateAction are uaFail (the default), uaAbort, uaSkip, uaRetry, uaApplied. If your event
handler successfully applies the update, change this parameter to uaApplied before
exiting. If you decide not to update the current record, change the value to uaSkip to
preserve unapplied changes in the cache. If you do not change the value for
UpdateAction, the entire update operation for the dataset is aborted and an exception
is raised. You can suppress the error message (raising a silent exception) by changing
UpdateAction to uaAbort.
In addition to these parameters, you will typically want to make use of the OldValue
and NewValue properties for the field component associated with the current record.
OldValue gives the original field value that was fetched from the database. It can be
useful in locating the database record to update. NewValue is the edited value in the
update you are trying to apply.
Important An OnUpdateRecord event handler, like an OnUpdateError or OnCalcFields event
handler, should never call any methods that change the current record in a dataset.
The following example illustrates how to use these parameters and properties. It uses
a TTable component named UpdateTable to apply updates. In practice, it is easier to
use an update object, but using a table illustrates the possibilities more clearly.
procedure TForm1.EmpAuditUpdateRecord(DataSet: TDataSet;
UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
begin
if UpdateKind = ukInsert then
UpdateTable.AppendRecord([DataSet.Fields[0].NewValue, DataSet.Fields[1].NewValue])
else
if UpdateTable.Locate('KeyField', VarToStr(DataSet.Fields[1].OldValue), []) then
case UpdateKind of
ukModify:
begin
UpdateTable.Edit;
UpdateTable.Fields[1].AsString := VarToStr(DataSet.Fields[1].NewValue);
UpdateTable.Post;
end;
ukInsert:
begin
UpdateTable.Insert;
UpdateTable.Fields[1].AsString := VarToStr(DataSet.Fields[1].NewValue);
UpdateTable.Post;
end;
ukDelete: UpdateTable.Delete;
end;
UpdateAction := uaApplied;
end;
UpdateAction tells the BDE how to proceed with the update process when your event
handler exits. When your update error handler is first called, the value for this
parameter is always set to uaFail. Based on the error condition for the record that
caused the error and what you do to correct it, you typically set UpdateAction to a
different value before exiting the handler:
• If your error handler can correct the error condition that caused the handler to be
invoked, set UpdateAction to the appropriate action to take on exit. For error
conditions you correct, set UpdateAction to uaRetry to apply the update for the
record again.
• When set to uaSkip, the update for the row that caused the error is skipped, and the
update for the record remains in the cache after all other updates are completed.
• Both uaFail and uaAbort cause the entire update operation to end. uaFail raises an
exception and displays an error message. uaAbort raises a silent exception (does
not display an error message).
The following code shows an OnUpdateError event handler that checks to see if the
update error is related to a key violation, and if it is, it sets the UpdateAction
parameter to uaSkip:
{ Add 'Bde' to your uses clause for this example }
if (E is EDBEngineError) then
with EDBEngineError(E) do begin
if Errors[ErrorCount - 1].ErrorCode = DBIERR_KEYVIOL then
UpdateAction := uaSkip { key violation, just skip this record }
else
UpdateAction := uaAbort; { don't know what's wrong, abort the update }
end;
Note If an error occurs during the application of cached updates, an exception is raised and
an error message displayed. Unless the ApplyUpdates is called from within a
try...except construct, an error message to the user displayed from inside your
OnUpdateError event handler may cause your application to display the same error
message twice. To prevent error message duplication, set UpdateAction to uaAbort to
turn off the system-generated error message display.
you must provide one TUpdateSQL object for each table you want to update. When
using multiple update objects, you can’t simply associate the update object with the
dataset by setting the UpdateObject property. Instead, you must manually call the
update object from an OnUpdateRecord event handler (when using the BDE to cache
updates) or a BeforeUpdateRecord event handler (when using a client dataset).
The update object actually encapsulates three TQuery components. Each of these
query components perform a single update task. One query component provides an
SQL UPDATE statement for modifying existing records; a second query component
provides an INSERT statement to add new records to a table; and a third component
provides a DELETE statement to remove records from a table.
When you place an update component in a data module, you do not see the query
components it encapsulates. They are created by the update component at runtime
based on three update properties for which you supply SQL statements:
• ModifySQL specifies the UPDATE statement.
• InsertSQL specifies the INSERT statement.
• DeleteSQL specifies the DELETE statement.
At runtime, when the update component is used to apply updates, it:
1 Selects an SQL statement to execute based on whether the current record is
modified, inserted, or deleted.
2 Provides parameter values to the SQL statement.
3 Prepares and executes the SQL statement to perform the specified update.
To accept the statements and associate them with the update component’s SQL
properties, click OK.
The DeleteSQL property should contain only an SQL statement with the DELETE
command. The base table to be updated must be named in the FROM clause. So that
the SQL statement only deletes the record in the base table that corresponds to the
record deleted in the update cache, use a WHERE clause. In the WHERE clause, use a
parameter for one or more fields to uniquely identify the record in the base table that
corresponds to the cached update record. If the parameters are named the same as
the field and prefixed with “OLD_”, the parameters are automatically given the
values from the corresponding field from the cached update record. If the parameter
are named in any other manner, you must supply the parameter values.
DELETE FROM Inventory I
WHERE (I.ItemNo = :OLD_ItemNo)
Some table types might not be able to find the record in the base table when fields
used to identify the record contain NULL values. In these cases, the delete update
fails for those records. To accommodate this, add a condition for those fields that
might contain NULLs using the IS NULL predicate (in addition to a condition for a
non-NULL value). For example, when a FirstName field may contain a NULL value:
DELETE FROM Names
WHERE (LastName = :OLD_LastName) AND
((FirstName = :OLD_FirstName) OR (FirstName IS NULL))
The InsertSQL statement should contain only an SQL statement with the INSERT
command. The base table to be updated must be named in the INTO clause. In the
VALUES clause, supply a comma-separated list of parameters. If the parameters are
named the same as the field, the parameters are automatically given the value from
the cached update record. If the parameter are named in any other manner, you must
supply the parameter values. The list of parameters supplies the values for fields in
the newly inserted record. There must be as many value parameters as there are
fields listed in the statement.
INSERT INTO Inventory
(ItemNo, Amount)
VALUES (:ItemNo, 0)
The ModifySQL statement should contain only an SQL statement with the UPDATE
command. The base table to be updated must be named in the FROM clause. Include
one or more value assignments in the SET clause. If values in the SET clause
assignments are parameters named the same as fields, the parameters are
automatically given values from the fields of the same name in the updated record in
the cache. You can assign additional field values using other parameters, as long as
the parameters are not named the same as any fields and you manually supply the
values. As with the DeleteSQL statement, supply a WHERE clause to uniquely
identify the record in the base table to be updated using parameters named the same
as the fields and prefixed with “OLD_”. In the update statement below, the
parameter :ItemNo is automatically given a value and :Price is not.
UPDATE Inventory I
SET I.ItemNo = :ItemNo, Amount = :Price
WHERE (I.ItemNo = :OLD_ItemNo)
Considering the above update SQL, take an example case where the application end-
user modifies an existing record. The original value for the ItemNo field is 999. In a
grid connected to the cached dataset, the end-user changes the ItemNo field value to
123 and Amount to 20. When the ApplyUpdates method is invoked, this SQL
statement affects all records in the base table where the ItemNo field is 999, using the
old field value in the parameter :OLD_ItemNo. In those records, it changes the
ItemNo field value to 123 (using the parameter :ItemNo, the value coming from the
grid) and Amount to 20.
You may optionally perform data validation, data modification, or other operations
that depend on each record’s update.
Warning If you call an update object’s ExecSQL or Apply method in an OnUpdateRecord event
handler, be sure that you do not set the dataset’s UpdateObject property to that
update object. Otherwise, this will result in a second attempt to apply each record’s
update.
OnUpdateRecord event handlers indicate the type of update that needs to be applied
with an UpdateKind parameter of type TUpdateKind. You must pass this parameter to
the Apply method to indicate which update SQL statement to use. The following code
illustrates this using a BeforeUpdateRecord event handler:
procedure TForm1.BDEClientDataSet1BeforeUpdateRecord(Sender: TObject; SourceDS: TDataSet;
DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind; var Applied: Boolean);
begin
with UpdateSQL1 do
begin
DataSet := DeltaDS;
DatabaseName := (SourceDS as TDBDataSet).DatabaseName;
SessionName := (SourceDS as TDBDataSet).SessionName;
Apply(UpdateKind);
Applied := True;
end;
end;
begin
with UpdateSQL1 do
begin
DatabaseName := (SourceDS as TDBDataSet).DatabaseName;
SessionName := (SourceDS as TDBDataSet).SessionName;
ExecSQL(UpdateKind);
Applied := True;
end;
end;
If an exception is raised during the execution of the update program, execution
continues in the OnUpdateError event, if it is defined.
Using TBatchMove
TBatchMove encapsulates Borland Database Engine (BDE) features that let you to
duplicate a dataset, append records from one dataset to another, update records in
one dataset with records from another dataset, and delete records from one dataset
that match records in another dataset. TBatchMove is most often used to:
• Download data from a server to a local data source for analysis or other
operations.
• Move a desktop database into tables on a remote server as part of an upsizing
operation.
A batch move component can create tables on the destination that correspond to the
source tables, automatically mapping the column names and data types as
appropriate.
Appending records
To append data, the destination dataset must represent an existing table. During the
append operation, the BDE converts data to appropriate data types and sizes for the
destination dataset if necessary. If a conversion is not possible, an exception is
thrown and the data is not appended.
Updating records
To update data, the destination dataset must represent an existing table and must
have an index defined that enables records to be matched. If the primary index fields
are used for matching, records with index fields in the destination dataset that match
index fields records in the source dataset are overwritten with the source data.
During the update operation, the BDE converts data to appropriate data types and
sizes for the destination dataset if necessary.
Copying datasets
To copy a source dataset, the destination dataset should not represent an exist table.
If it does, the batch move operation overwrites the existing table with a copy of the
source dataset.
If the source and destination datasets are maintained by different types of database
engines, for example, Paradox and InterBase, the BDE creates a destination dataset
with a structure as close as possible to that of the source dataset and automatically
performs data type and size conversions as necessary.
Note TBatchMove does not copy metadata structures such as indexes, constraints, and
stored procedures. You must recreate these metadata objects on your database server
or through the SQL Explorer as appropriate.
Deleting records
To delete data in the destination dataset, it must represent an existing table and must
have an index defined that enables records to be matched. If the primary index fields
are used for matching, records with index fields in the destination dataset that match
index fields records in the source dataset are deleted in the destination table.
If source and destination column data types are not the same, a batch move operation
attempts a “best fit”. It trims character data types, if necessary, and attempts to
perform a limited amount of conversion, if possible. For example, mapping a
CHAR(10) column to a CHAR(5) column will result in trimming the last five
characters from the source column.
As an example of conversion, if a source column of character data type is mapped to
a destination of integer type, the batch move operation converts a character value of
‘5’ to the corresponding integer value. Values that cannot be converted generate
errors. For more information about errors, see “Handling batch move errors” on
page 26-52.
When moving data between different table types, a batch move component translates
data types as appropriate based on the dataset’s server types. See the BDE online
help file for the latest tables of mappings among server types.
Note To batch move data to an SQL server database, you must have that database server
and a version of Delphi with the appropriate SQL Link installed, or you can use
ODBC if you have the proper third party ODBC drivers installed.
The AbortOnProblem property specifies whether to abort the operation when a data
type conversion error occurs. If AbortOnProblem is True, the batch move operation is
canceled when an error occurs. If False, the operation continues. You can examine the
table you specify in the ProblemTableName to determine which records caused
problems.
The AbortOnKeyViol property indicates whether to abort the operation when a
Paradox key violation occurs.
The ProblemCount property indicates the number of records that could not be
handled in the destination table without a loss of data. If AbortOnProblem is True, this
number is one, since the operation is aborted when an error occurs.
The following properties enable a batch move component to create additional tables
that document the batch move operation:
• ChangedTableName, if specified, creates a local Paradox table containing all records
in the destination table that changed as a result of an update or delete operation.
• KeyViolTableName, if specified, creates a local Paradox table containing all records
from the source table that caused a key violation when working with a Paradox
table. If AbortOnKeyViol is True, this table will contain at most one entry since the
operation is aborted on the first problem encountered.
• ProblemTableName, if specified, creates a local Paradox table containing all records
that could not be posted in the destination table due to data type conversion
errors. For example, the table could contain records from the source table whose
data had to be trimmed to fit in the destination table. If AbortOnProblem is True,
there is at most one record in this table since the operation is aborted on the first
problem encountered.
Note If ProblemTableName is not specified, the data in the record is trimmed and placed in
the destination table.
In a client/server environment, the Data Dictionary can reside on a remote server for
additional sharing of information.
To learn how to create extended field attribute sets from the Fields editor at design
time, and how to associate them with fields throughout the datasets in your
application, see “Creating attribute sets for field components” on page 25-13. To
learn more about creating a data dictionary and extended field attributes with the
SQL and Database Explorers, see their respective online help files.
A programming interface to the Data Dictionary is available in the drintf unit
(located in the lib directory). This interface supplies the following methods:
The connection component represents an ADO connection object. Before you can use
the connection object to establish a connection, you must identify the data store to
which you want to connect. Typically, you provide information using the
ConnectionString property. ConnectionString is a semicolon delimited string that lists
one or more named connection parameters. These parameters identify the data store
by specifying either the name of a file that contains the connection information or the
name of an ADO provider and a reference identifying the data store. Use the
following, predefined parameter names to supply this information:
Fine-tuning a connection
One advantage of using TADOConnection for establishing the connection to a data
store instead of simply supplying a connection string for your ADO command and
dataset components, is that it provides a greater degree of control over the conditions
and attributes of the connection.
Controlling time-outs
You can control the amount of time that can elapse before attempted commands and
connections are considered failed and are aborted using the ConnectionTimeout and
CommandTimeout properties.
ConnectionTimeout specifies the amount of time, in seconds, before an attempt to
connect to the data store times out. If the connection does not successfully compile
prior to expiration of the time specified in ConnectionTimeout, the connection attempt
is canceled:
with ADOConnection1 do begin
ConnectionTimeout := 10 {seconds};
Open;
end;
CommandTimeout specifies the amount of time, in seconds, before an attempted
command times out. If a command initiated by a call to the Execute method does not
successfully complete prior to expiration of the time specified in CommandTimeout,
the command is canceled and ADO generates an exception:
with ADOConnection1 do begin
CommandTimeout := 10 {seconds};
Execute('DROP TABLE Employee1997', cmdText, []);
end;
The possible values for Mode correspond to the ConnectModeEnum values of the Mode
property on the underlying ADO connection object. See the Microsoft Data Access
SDK help for more information on these values.
Other events
ADO connection components introduce two additional events you can use to
respond to notifications from the underlying ADO connection object:
• The OnExecuteComplete event occurs after the connection component executes a
command on the data store (for example, after calling the Execute method).
OnExecuteComplete indicates whether the execution was successful.
• The OnInfoMessage event occurs when the underlying connection object provides
detailed information after an operation is completed. The OnInfoMessage event
handler receives the interface to an ADO Error object that contains the detailed
information and a status code indicating whether the operation was successful.
The ConnectionString property of ADO datasets works the same way as the
ConnectionString property of TADOConnection: it is a set of semicolon-delimited
connection parameters such as the following:
ADODataSet1.ConnectionString := 'Provider=YourProvider;Password=SecretWord;' +
'User ID=JaneDoe;SERVER=PURGATORY;UID=JaneDoe;PWD=SecretWord;' +
'Initial Catalog=Employee';
At design time you can use the Connection String Editor to help you build the
connection string. For more information about connection strings, see “Connecting to
a data store using TADOConnection” on page 27-3.
Using the batch updates features of ADO dataset components is a matter of:
• Opening the dataset in batch update mode
• Inspecting the update status of individual rows
• Filtering multiple rows based on update status
• Applying the batch updates to base tables
• Canceling batch updates
Before activating the dataset component, set the CursorType and LockType properties
as indicated above. Assign a SELECT statement to the component’s CommandText
property (for TADODataSet) or the SQL property (for TADOQuery). For
TADOStoredProc components, set the ProcedureName to the name of a stored
procedure that returns a result set. These properties can be set at design-time through
the Object Inspector or programmatically at runtime. The example below shows the
preparation of a TADODataSet component for batch update mode.
with ADODataSet1 do begin
CursorLocation := clUseClient;
CursorType := ctStatic;
LockType := ltBatchOptimistic;
CommandType := cmdText;
CommandText := 'SELECT * FROM Employee';
Open;
end;
After a dataset has been opened in batch update mode, all changes to the data are
cached rather than applied directly to the base tables.
In the example below, the first procedure saves the dataset retrieved by the
TADODataSet component ADODataSet1 to a file. The target file is an ADTG file
named SaveFile, saved to a local drive. The second procedure loads this saved file
into the TADODataSet component ADODataSet2.
procedure TForm1.SaveBtnClick(Sender: TObject);
begin
if (FileExists('c:\SaveFile')) then
begin
DeleteFile('c:\SaveFile');
StatusBar1.Panels[0].Text := 'Save file deleted!';
end;
ADODataSet1.SaveToFile('c:\SaveFile', pfADTG);
end;
procedure TForm1.LoadBtnClick(Sender: TObject);
begin
if (FileExists('c:\SaveFile')) then
ADODataSet2.LoadFromFile('c:\SaveFile')
else
StatusBar1.Panels[0].Text := 'Save file does not exist!';
end;
The datasets that save and load the data need not be on the same form as above, in
the same application, or even on the same computer. This allows for the briefcase-
style transfer of data from one computer to another.
Using TADODataSet
TADODataSet is a general-purpose dataset for working with data from an ADO data
store. Unlike the other ADO dataset components, TADODataSet is not a table-type,
query-type, or stored procedure-type dataset. Instead, it can function as any of these
types:
• Like a table-type dataset, TADODataSet lets you represent all of the rows and
columns of a single database table. To use it in this way, set the CommandType
property to cmdTable and the CommandText property to the name of the table.
TADODataSet supports table-type tasks such as
• Assigning indexes to sort records or form the basis of record-based searches. In
addition to the standard index properties and methods described in “Sorting
records with indexes” on page 24-26, TADODataSet lets you sort using
temporary indexes by setting the Sort property. Indexed-based searches
performed using the Seek method use the current index.
• Emptying the dataset. The DeleteRecords method provides greater control than
related methods in other table-type datasets, because it lets you specify what
records to delete.
The table-type tasks supported by TADODataSet are available even when you are
not using a CommandType of cmdTable.
• Like a query-type dataset, TADODataSet lets you specify a single SQL command
that is executed when you open the dataset. To use it in this way, set the
CommandType property to cmdText and the CommandText property to the SQL
command you want to execute. At design time, you can double-click on the
CommandText property in the Object Inspector to use the Command Text editor for
help in constructing the SQL command. TADODataSet supports query-type tasks
such as
• Using parameters in the query text. See “Using parameters in queries” on
page 24-45 for details on query parameters.
• Setting up master/detail relationships using parameters. See “Establishing
master/detail relationships using parameters” on page 24-47 for details on how
to do this.
• Preparing the query in advance to improve performance by setting the Prepared
property to True.
• Like a stored procedure-type dataset, TADODataSet lets you specify a stored
procedure that is executed when you open the dataset. To use it in this way, set the
CommandType property to cmdStoredProc and the CommandText property to the
name of the stored procedure. TADODataSet supports stored procedure-type tasks
such as
• Working with stored procedure parameters. See “Working with stored
procedure parameters” on page 24-51 for details on stored procedure
parameters.
• Fetching multiple result sets. See “Fetching multiple result sets” on page 24-56
for details on how to do this.
• Preparing the stored procedure in advance to improve performance by setting
the Prepared property to True.
In addition, TADODataSet lets you work with data stored in files by setting the
CommandType property to cmdFile and the CommandText property to the file name.
Before you set the CommandText and CommandType properties, you should link the
TADODataSet to a data store by setting the Connection or ConnectionString property.
This process is described in “Connecting an ADO dataset to a data store” on
page 27-10. As an alternative, you can use an RDS DataSpace object to connect the
TADODataSet to an ADO-based application server. To use an RDS DataSpace object,
set the RDSConnection property to a TRDSConnection object.
Canceling commands
If you are executing the command asynchronously, then after calling Execute you can
abort the execution by calling the Cancel method:
procedure TDataForm.ExecuteButtonClick(Sender: TObject);
begin
ADOCommand1.Execute;
end;
procedure TDataForm.CancelButtonClick(Sender: TObject);
begin
ADOCommand1.Cancel;
end;
The Cancel method only has an effect if there is a command pending and it was
executed asynchronously (eoAsynchExecute is in the ExecuteOptions parameter of the
Execute method). A command is said to be pending if the Execute method has been
called but the command has not yet been completed or timed out.
A command times out if it is not completed or canceled before the number of seconds
specified in the CommandTimeout property expire. By default, commands time out
after 30 seconds.
When working with stored procedures that return output parameters, you must use
the Parameters property instead. Even if you do not need to read output parameters,
you may prefer to use the Parameters property, which lets you supply parameters at
design time and lets you work with TADOCommand properties in the same way you
work with the parameters on datasets.
When you set the CommandText property, the Parameters property is automatically
updated to reflect the parameters in the query or those used by the stored procedure.
At design-time, you can use the Parameter Editor to access parameters, by clicking
the ellipsis button for the Parameters property in the Object Inspector. At runtime, use
properties and methods of TParameter to set (or get) the values of each parameter.
with ADOCommand1 do begin
CommandText := 'INSERT INTO Talley ' +
'(Counter) ' +
'VALUES (:NewValueParam)';
CommandType := cmdText;
Parameters.ParamByName('NewValueParam').Value := 57;
Execute
end;
• There is no support for filters, because filters work with multiple records, which
requires buffering. If you try to filter a unidirectional dataset, it raises an
exception. Instead, all limits on what data appears must be imposed using the SQL
command that defines the data for the dataset.
• There is no support for lookup fields, which require buffering to hold multiple
records containing lookup values. If you define a lookup field on a unidirectional
dataset, it does not work properly.
Despite these limitations, unidirectional datasets are a powerful way to access data.
They are the fastest data access mechanism, and very simple to use and deploy.
Setting up TSQLConnection
In order to describe a database connection in sufficient detail for TSQLConnection to
open a connection, you must identify both the driver to use and a set of connection
parameters the are passed to that driver.
The relationship between these two files and the database name is stored in a file
called dbxdrivers.ini, which is updated when you install a dbExpress driver.
Typically, you do not need to worry about these files because the SQL connection
component looks them up in dbxdrivers.ini when given the value of DriverName.
When you set the DriverName property, TSQLConnection automatically sets the
LibraryName and VendorLib properties to the names of the associated dlls. Once
LibraryName and VendorLib have been set, your application does not need to rely on
dbxdrivers.ini. (That is, you do not need to deploy dbxdrivers.ini with your
application unless you set the DriverName property at runtime.)
One advantage of using connection names arises when you develop your application
using one database (for example Local InterBase), but deploy it for use with another
(such as ORACLE). In that case, DriverName and Params will likely differ on the
system where you deploy your application from the values you use during
development. You can switch between the two connection descriptions easily by
using two versions of the dbxconnections.ini file. At design-time, your application
loads the DriverName and Params from the design-time version of dbxconnections.ini.
Then, when you deploy your application, it loads these values from a separate
version of dbxconnections.ini that uses the “real” database. However, for this to
work, you must instruct your connection component to reload the DriverName and
Params properties at runtime. There are two ways to do this:
• Set the LoadParamsOnConnect property to True. This causes TSQLConnection to
automatically set DriverName and Params to the values associated with
ConnectionName in dbxconnections.ini when the connection is opened.
• Call the LoadParamsFromIniFile method. This method sets DriverName and Params
to the values associated with ConnectionName in dbxconnections.ini (or in another
file that you specify). You might choose to use this method if you want to then
override certain parameter values before opening the connection.
• A pattern that must be matched for every name returned. This pattern is an SQL
pattern such as ‘Cust%’, which uses the wildcards ‘%’ (to match a string of
arbitrary characters of any length) and ‘_’ (to match a single arbitrary character).
To use a literal percent or underscore in a pattern, the character is doubled (%% or
__). If you do not want to use a pattern, this parameter can be nil.
Note If you are fetching schema information about tables (stTables), the resulting schema
information can describe ordinary tables, system tables, views, and/or synonyms,
depending on the value of the SQL connection’s TableScope property.
The following call requests a table listing all system tables (server tables that contain
metadata):
SQLDataSet1.SetSchemaInfo(stSysTable, '', '');
When you open the dataset after this call to SetSchemaInfo, the resulting dataset has a
record for each table, with columns giving the table name, type, schema name, and so
on. If the server does not use system tables to store metadata (for example MySQL),
when you open the dataset it contains no records.
The previous example used only the first parameter. Suppose, Instead, you want to
obtain a list of input parameters for a stored procedure named ‘MyProc’. Suppose,
further, that the person who wrote that stored procedure named all parameters using
a prefix to indicate whether they were input or output parameters (‘inName’,
‘outValue’ and so on). You could call SetSchemaInfo as follows:
SQLDataSet1.SetSchemaInfo(stProcedureParams, 'MyProc', 'in%');
The resulting dataset is a table of input parameters with columns to describe the
properties of each parameter.
Note If you were to use the previous event handler, you would also want to save any
partial list (fewer than 10 entries) when the application shuts down.
29
Using client datasets
Chapter29
Client datasets are specialized datasets that hold all their data in memory. The
support for manipulating the data they store in memory is provided by midaslib.dcu
or midas.dll. The format client datasets use for storing data is self-contained and
easily transported, which allows client datasets to
• Read from and write to dedicated files on disk, acting as a file-based dataset.
Properties and methods supporting this mechanism are described in “Using a
client dataset with file-based data” on page 29-33.
• Cache updates for data from a database server. Client dataset features that support
cached updates are described in “Using a client dataset to cache updates” on
page 29-16.
• Represent the data in the client portion of a multi-tiered application. To function in
this way, the client dataset must work with an external provider, as described in
“Using a client dataset with a provider” on page 29-24. For information about
multi-tiered database applications, see Chapter 31, “Creating multi-tiered
applications.”
• Represent the data from a source other than a dataset. Because a client dataset can
use the data from an external provider, specialized providers can adapt a variety
of information sources to work with client datasets. For example, you can use an
XML provider to enable a client dataset to represent the information in an XML
document.
Whether you use client datasets for file-based data, caching updates, data from an
external provider (such as working with an XML document or in a multi-tiered
application), or a combination of these approaches such as a “briefcase model”
application, you can take advantage of broad range of features client datasets
support for working with data.
With most datasets, filter strings are parsed into SQL commands that are then
implemented on the database server. Because of this, the SQL dialect of the server
limits what operations are used in filter strings. Client datasets implement their own
filter support, which includes more operations than that of other datasets. For
example, when using a client dataset, filter expressions can include string operators
that return substrings, operators that parse date/time values, and much more. Client
datasets also allow filters on BLOB fields or complex field types such as ADT fields
and array fields.
The various operators and functions that client datasets can use in filters, along with
a comparison to other datasets that support filters, is given below:
When applying ranges or filters, the client dataset still stores all of its records in
memory. The range or filter merely determines which records are available to
controls that navigate or display data from the client dataset.
Note When fetching data from a provider, you can also limit the data that the client dataset
stores by supplying parameters to the provider. For details, see “Limiting records
with parameters” on page 29-29.
Editing data
Client datasets represent their data as an in-memory data packet. This packet is the
value of the client dataset’s Data property. By default, however, edits are not stored
in the Data property. Instead the insertions, deletions, and modifications (made by
users or programmatically) are stored in an internal change log, represented by the
Delta property. Using a change log serves two purposes:
• The change log is required for applying updates to a database server or external
provider component.
• The change log provides sophisticated support for undoing changes.
The LogChanges property lets you disable logging. When LogChanges is True, changes
are recorded in the log. When LogChanges is False, changes are made directly to the
Data property. You can disable the change log in file-based applications if you do not
want the undo support.
Edits in the change log remain there until they are removed by the application.
Applications remove edits when
• Undoing changes
• Saving changes
Note Saving the client dataset to a file does not remove edits from the change log. When
you reload the dataset, the Data and Delta properties are the same as they were when
the data was saved.
Undoing changes
Even though a record’s original version remains unchanged in Data, each time a user
edits a record, leaves it, and returns to it, the user sees the last changed version of the
record. If a user or application edits a record a number of times, each changed
version of the record is stored in the change log as a separate entry.
Storing each change to a record makes it possible to support multiple levels of undo
operations should it be necessary to restore a record’s previous state:
• To remove the last change to a record, call UndoLastChange. UndoLastChange takes
a Boolean parameter, FollowChange, that indicates whether to reposition the cursor
on the restored record (True), or to leave the cursor on the current record (False). If
there are several changes to a record, each call to UndoLastChange removes another
layer of edits. UndoLastChange returns a Boolean value indicating success or
failure. If the removal occurs, UndoLastChange returns True. Use the ChangeCount
property to check whether there are more changes to undo. ChangeCount indicates
the number of changes stored in the change log.
• Instead of removing each layer of changes to a single record, you can remove them
all at once. To remove all changes to a record, select the record, and call
RevertRecord. RevertRecord removes any changes to the current record from the
change log.
• To restore a deleted record, first set the StatusFilter property to [usDeleted], which
makes the deleted records “visible.” Next, navigate to the record you want to
restore and call RevertRecord. Finally, restore the StatusFilter property to
[usModified, usInserted, usUnmodified] so that the edited version of the dataset (now
containing the restored record) is again visible.
• At any point during edits, you can save the current state of the change log using
the SavePoint property. Reading SavePoint returns a marker into the current
position in the change log. Later, if you want to undo all changes that occurred
since you read the save point, set SavePoint to the value you read previously. Your
application can obtain values for multiple save points. However, once you back up
the change log to a save point, the values of all save points that your application
read after that one are invalid.
• You can abandon all changes recorded in the change log by calling CancelUpdates.
CancelUpdates clears the change log, effectively discarding all edits to all records.
Be careful when you call CancelUpdates. After you call CancelUpdates, you cannot
recover any changes that were in the log.
Saving changes
Client datasets use different mechanisms for incorporating changes from the change
log, depending on whether the client datasets stores its data in a file or represents
data obtained through a provider. Whichever mechanism is used, the change log is
automatically emptied when all updates have been incorporated.
File-based applications can simply merge the changes into the local cache
represented by the Data property. They do not need to worry about resolving local
edits with changes made by other users. To merge the change log into the Data
property, call the MergeChangeLog method. “Merging changes into data” on
page 29-34 describes this process.
You can’t use MergeChangeLog if you are using the client dataset to cache updates or
to represent the data from an external provider component. The information in the
change log is required for resolving updated records with the data stored in the
database (or source dataset). Instead, you call ApplyUpdates, which attempts to write
the modifications to the database server or source dataset, and updates the Data
property only when the modifications have been successfully committed. See
“Applying updates” on page 29-20 for more information about this process.
• How the index sorts records. By default, indexes impose an ascending sort
order (based on the machine’s locale). This default sort order is case-sensitive.
You can set options to make the entire index case-insensitive or to sort in
descending order. Alternately, you can provide a list of fields to be sorted case-
insensitively and a list of fields to be sorted in descending order.
• The default level of grouping support for the index.
Indexes created with AddIndex do not persist when the client dataset is closed.
(That is, they are lost when you reopen the client dataset). You can't call AddIndex
when the dataset is closed. Indexes you add using AddIndex are not saved when
you save the client dataset to a file.
• The third way to create an index is at the time the client dataset is created. Before
creating the client dataset, specify the desired indexes using the IndexDefs
property. The indexes are then created along with the underlying dataset when
you call CreateDataSet. See “Creating and deleting tables” on page 24-38 for more
information about creating client datasets.
As with AddIndex, indexes you create with the dataset support grouping, can sort
in ascending order on some fields and descending order on others, and can be case
insensitive on some fields and case sensitive on others. Indexes created this way
always persist and are saved when you save the client dataset to a file.
Tip You can index and sort on internally calculated fields with client datasets.
Because of the sort order, adjacent values in the SalesRep column are duplicated.
Within the records for SalesRep 1, adjacent values in the Customer column are
duplicated. That is, the data is grouped by SalesRep, and within the SalesRep group
it is grouped by Customer. Each grouping has an associated level. In this case, the
SalesRep group has level 1 (because it is not nested in any other groups) and the
Customer group has level 2 (because it is nested in the group with level 1). Grouping
level corresponds to the order of fields in the index.
Client datasets let you determine where the current record lies within any given
grouping level. This allows your application to display records differently,
depending on whether they are the first record in the group, in the middle of a group,
or the last record in a group. For example, you might want to display a field value
only if it is on the first record of the group, eliminating the duplicate values. To do
this with the previous table results in the following:
To determine where the current record falls within any group, use the GetGroupState
method. GetGroupState takes an integer giving the level of the group and returns a
value indicating where the current record falls the group (first record, last record, or
neither).
When you create an index, you can specify the level of grouping it supports (up to
the number of fields in the index). GetGroupState can’t provide information about
groups beyond that level, even if the index sorts records on additional fields.
Specifying aggregates
To specify that you want to calculate summaries over the records in a client dataset,
use the Aggregates property. Aggregates is a collection of aggregate specifications
(TAggregate). You can add aggregate specifications to your client dataset using the
Collection Editor at design time, or using the Add method of Aggregates at runtime. If
you want to create field components for the aggregates, create persistent fields for the
aggregated values in the Fields Editor.
Note When you create aggregated fields, the appropriate aggregate objects are added to
the client dataset’s Aggregates property automatically. Do not add them explicitly
when creating aggregated persistent fields. For details on creating aggregated
persistent fields, see “Defining an aggregate field” on page 25-10.
For each aggregate, the Expression property indicates the summary calculation it
represents. Expression can contain a simple summary expression such as
Sum(Field1)
or a complex expression that combines information from several fields, such as
Sum(Qty * Price) - Sum(AmountPaid)
Aggregate expressions include one or more of the summary operators in Table 29.2
The summary operators act on field values or on expressions built from field values
using the same operators you use to create filters. (You can’t nest summary
operators, however.) You can create expressions by using operators on summarized
values with other summarized values, or on summarized values and constants.
However, you can’t combine summarized values with field values, because such
expressions are ambiguous (there is no indication of which record should supply the
field value.) These rules are illustrated in the following expressions:
The following code sets up a maintained aggregate that indicates the total amount for
each sales representative:
Agg.Expression := 'Sum(Amount)';
Agg.IndexName := 'SalesCust';
Agg.GroupingLevel := 1;
Agg.AggregateName := 'Total for Rep';
To add an aggregate that summarizes for each customer within a given sales
representative, create a maintained aggregate with level 2.
Maintained aggregates that summarize over a group of records are associated with a
specific index. The Aggregates property can include aggregates that use different
indexes. However, only the aggregates that summarize over the entire dataset and
those that use the current index are valid. Changing the current index changes which
aggregates are valid. To determine which aggregates are valid at any time, use the
ActiveAggs property.
If you are copying from a dataset other than a client dataset, you can create a dataset
provider component, link it to the source dataset, and then copy its data:
TempProvider := TDataSetProvider.Create(Form1);
TempProvider.DataSet := SourceDataSet;
ClientDataSet1.Data := TempProvider.Data;
TempProvider.Free;
Note When you assign directly to the Data property, the new data packet is not merged
into the existing data. Instead, all previous data is replaced.
If you want to merge changes from another dataset, rather than copying its data, you
must use a provider component. Create a dataset provider as in the previous
example, but attach it to the destination dataset and instead of copying the data
property, use the ApplyUpdates method:
TempProvider := TDataSetProvider.Create(Form1);
TempProvider.DataSet := ClientDataSet1;
TempProvider.ApplyUpdates(SourceDataSet.Delta, -1, ErrCount);
TempProvider.Free;
5 Apply the locally cached records to the database or cancel the updates. For each
record written to the database, a BeforeUpdateRecord event is triggered. If an error
occurs when writing an individual record to the database, an OnUpdateError event
enables the application to correct the error, if possible, and continue updating.
When updates are complete, all successfully applied updates are cleared from the
local cache. For more information about applying updates to the database, see
“Updating records” on page 29-20.
Instead of applying updates, an application can cancel the updates, emptying the
change log without writing the changes to the database. You can cancel the
updates by calling CancelUpdates method. All deleted records in the cache are
undeleted, modified records revert to original values, and newly inserted record
simply disappear.
In addition, you can cache updates using the generic client dataset (TClientDataSet)
with an external provider and source dataset. For information about using
TClientDataSet with an external provider, see “Using a client dataset with a provider”
on page 29-24.
Note The specialized client datasets associated with each data access mechanism actually
use a provider and source dataset as well. However, both the provider and the source
dataset are internal to the client dataset.
It is simplest to use one of the specialized client datasets to cache updates. However,
there are times when it is preferable to use TClientDataSet with an external provider:
• If you are using a data access mechanism that does not have a specialized client
dataset, you must use TClientDataSet with an external provider component. For
example, if the data comes from an XML document or custom dataset.
• If you are working with tables that are related in a master/detail relationship, you
should use TClientDataSet and connect it, using a provider, to the master table of
two source datasets linked in a master/detail relationship. The client dataset sees
the detail dataset as a nested dataset field. This approach is necessary so that
updates to master and detail tables can be applied in the correct order.
• If you want to code event handlers that respond to the communication between
the client dataset and the provider (for example, before and after the client dataset
fetches records from the provider), you must use TClientDataSet with an external
provider component. The specialized client datasets publish the most important
events for applying updates (OnReconcileError, BeforeUpdateRecord and
OnGetTableName), but do not publish the events surrounding communication
between the client dataset and its provider, because they are intended primarily
for multi-tiered applications.
• When using the BDE, you may want to use an external provider and source
dataset if you need to use an update object. Although it is possible to code an
update object from the BeforeUpdateRecord event handler of TBDEClientDataSet, it
can be simpler just to assign the UpdateObject property of the source dataset. For
information about using update objects, see “Using update objects to update a
dataset” on page 26-40.
The following example shows how to provide feedback about the update status of
records using the UpdateStatus method. It assumes that you have changed the
StatusFilter property to include usDeleted, allowing deleted records to remain visible
in the dataset. It further assumes that you have added a calculated field to the dataset
called “Status.”
procedure TForm1.ClientDataSet1CalcFields(DataSet: TDataSet);
begin
with ClientDataSet1 do begin
case UpdateStatus of
usUnmodified: FieldByName('Status').AsString := '';
usModified: FieldByName('Status').AsString := 'M';
usInserted: FieldByName('Status').AsString := 'I';
usDeleted: FieldByName('Status').AsString := 'D';
end;
end;
end;
Updating records
The contents of the change log are stored as a data packet in the client dataset’s Delta
property. To make the changes in Delta permanent, the client dataset must apply
them to the database (or source dataset or XML document).
When a client applies updates to the server, the following steps occur:
1 The client application calls the ApplyUpdates method of a client dataset object. This
method passes the contents of the client dataset’s Delta property to the (internal or
external) provider. Delta is a data packet that contains a client dataset’s updated,
inserted, and deleted records.
2 The provider applies the updates, caching any problem records that it can’t
resolve itself. See “Responding to client update requests” on page 30-8 for details
on how the provider applies updates.
3 The provider returns all unresolved records to the client dataset in a Result data
packet. The Result data packet contains all records that were not updated. It also
contains error information, such as error messages and error codes.
4 The client dataset attempts to reconcile update errors returned in the Result data
packet on a record-by-record basis.
Applying updates
Changes made to the client dataset’s local copy of data are not sent to the database
server (or XML document) until the client application calls the ApplyUpdates method.
ApplyUpdates takes the changes in the change log, and sends them as a data packet
(called Delta) to the provider. (Note that, when using most client datasets, the
provider is internal to the client dataset.)
When the provider is internal, however, as it is for any client dataset associated with
a data access mechanism, you can’t set its properties or provide event handlers. As a
result, the client dataset publishes one property and two events that let you influence
how the internal provider applies updates.
• UpdateMode controls what fields are used to locate records in the SQL statements
the provider generates for applying updates. UpdateMode is identical to the
provider’s UpdateMode property. For information on the provider’s UpdateMode
property, see “Influencing how updates are applied” on page 30-10.
• OnGetTableName lets you supply the provider with the name of the database table
to which it should apply updates. This lets the provider generate the SQL
statements for updates when it can’t identify the database table from the stored
procedure or query specified by CommandText. For example, if the query executes
a multi-table join that only requires updates to a single table, supplying an
OnGetTableName event handler allows the internal provider to correctly apply
updates.
An OnGetTableName event handler has three parameters: the internal provider
component, the internal dataset that fetched the data from the server, and a
parameter to return the table name to use in the generated SQL.
• BeforeUpdateRecord occurs for every record in the delta packet. This event lets you
make any last-minute changes before the record is inserted, deleted, or modified.
It also provides a way for you to execute your own SQL statements to apply the
update in cases where the provider can’t generate correct SQL (for example, for
multi-table joins where multiple tables must be updated.)
A BeforeUpdateRecord event handler has five parameters: the internal provider
component, the internal dataset that fetched the data from the server, a delta
packet that is positioned on the record that is about to be updated, an indication of
whether the update is an insertion, deletion, or modification, and a parameter that
returns whether the event handler performed the update.The use of these is
illustrated in the following event handler. For simplicity, the example assumes the
SQL statements are available as global variables that only need field values:
procedure TForm1.SimpleDataSet1BeforeUpdateRecord(Sender: TObject;
SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind;
var Applied Boolean);
var
SQL: string;
Connection: TSQLConnection;
begin
Connection := (SourceDS as TSimpleDataSet).Connection;
case UpdateKind of
ukModify:
begin
{ 1st dataset: update Fields[1], use Fields[0] in where clause }
SQL := Format(UpdateStmt1, [DeltaDS.Fields[1].NewValue, DeltaDS.Fields[0].OldValue]);
Connection.Execute(SQL, nil, nil);
{ 2nd dataset: update Fields[2], use Fields[3] in where clause }
SQL := Format(UpdateStmt2, [DeltaDS.Fields[2].NewValue, DeltaDS.Fields[3].OldValue]);
Connection.Execute(SQL, nil, nil);
end;
ukDelete:
begin
{ 1st dataset: use Fields[0] in where clause }
SQL := Format(DeleteStmt1, [DeltaDS.Fields[0].OldValue]);
Connection.Execute(SQL, nil, nil);
{ 2nd dataset: use Fields[3] in where clause }
SQL := Format(DeleteStmt2, [DeltaDS.Fields[3].OldValue]);
Connection.Execute(SQL, nil, nil);
end;
ukInsert:
begin
{ 1st dataset: values in Fields[0] and Fields[1] }
SQL := Format(InsertStmt1, [DeltaDS.Fields[0].NewValue, DeltaDS.Fields[1].NewValue]);
Connection.Execute(SQL, nil, nil);
{ 2nd dataset: values in Fields[2] and Fields[3] }
SQL := Format(InsertStmt2, [DeltaDS.Fields[2].NewValue, DeltaDS.Fields[3].NewValue]);
Connection.Execute(SQL, nil, nil);
end;
end;
Applied := True;
end;
• UpdateKind: The type of update that generated the error. UpdateKind can be
ukModify (the problem occurred updating an existing record that was modified),
ukInsert (the problem occurred inserting a new record), or ukDelete (the problem
occurred deleting an existing record).
• Action: A var parameter that indicates what action to take when the event handler
exits. In your event handler, you set this parameter to
• Skip this record, leaving it in the change log. (rrSkip or raSkip)
• Stop the entire reconcile operation. (rrAbort or raAbort)
• Merge the modification that failed into the corresponding record from the
server. (rrMerge or raMerge) This only works if the server record does not
include any changes to fields modified in the client dataset’s record.
• Replace the current update in the change log with the value of the record in the
event handler, which has presumably been corrected. (rrApply or raCorrect)
• Ignore the error completely. (rrIgnore) This possibility only exists in the
OnUpdateError event handler, and is intended for the case where the event
handler applies the update back to the database server. The updated record is
removed from the change log and merged into Data, as if the provider had
applied the update.
• Back out the changes for this record on the client dataset, reverting to the
originally provided values. (raCancel) This possibility only exists in the
OnReconcileError event handler.
• Update the current record value to match the record on the server. (raRefresh)
This possibility only exists in the OnReconcileError event handler.
The following code shows an OnReconcileError event handler that uses the reconcile
error dialog from the RecError unit which ships in the objrepos directory. (To use this
dialog, add RecError to your uses clause.)
procedure TForm1.ClientDataSetReconcileError(DataSet: TCustomClientDataSet; E:
EReconcileError; UpdateKind: TUpdateKind, var Action TReconcileAction);
begin
Action := HandleReconcileError(DataSet, UpdateKind, E);
end;
For any client dataset other than TClientDataSet, this provider is internal, and so not
directly accessible by the application. With TClientDataSet, the provider is an external
component that links the client dataset to an external source of data.
An external provider component can reside in the same application as the client
dataset, or it can be part of a separate application running on another system. For
more information about provider components, see Chapter 30, “Using provider
components.” For more information about applications where the provider is in a
separate application on another system, see Chapter 31, “Creating multi-tiered
applications.”
When using an (internal or external) provider, the client dataset always caches any
updates. For information on how this works, see “Using a client dataset to cache
updates” on page 29-16.
The following topics describe additional properties and methods of the client dataset
that enable it to work with a provider.
Specifying a provider
Unlike the client datasets that are associated with a data access mechanism,
TClientDataSet has no internal provider component to package data or apply
updates. If you want it to represent data from a source dataset or XML document,
therefore, you must associated the client dataset with an external provider
component.
The way you associate TClientDataSet with a provider depends on whether the
provider is in the same application as the client dataset or on a remote application
server running on another system.
• If the provider is in the same application as the client dataset, you can associate it
with a provider by choosing a provider from the drop-down list for the
ProviderName property in the Object Inspector. This works as long as the provider
has the same Owner as the client dataset. (The client dataset and the provider have
the same Owner if they are placed in the same form or data module.) To use a local
provider that has a different Owner, you must form the association at runtime
using the client dataset’s SetProvider method
If you think you may eventually scale up to a remote provider, or if you want to
make calls directly to the IAppServer interface, you can also set the RemoteServer
property to a TLocalConnection component. If you use TLocalConnection, the
TLocalConnection instance manages the list of all providers that are local to the
application, and handles the client dataset’s IAppServer calls. If you do not use
TLocalConnection, the application creates a hidden object that handles the
IAppServer calls from the client dataset.
Incremental fetching
By changing the PacketRecords property, you can specify that the client dataset fetches
data in smaller chunks. PacketRecords specifies either how many records to fetch at a
time, or the type of records to return. By default, PacketRecords is set to -1, which
means that all available records are fetched at once, either when the client dataset is
first opened, or when the application explicitly calls GetNextPacket. When
PacketRecords is -1, then after the client dataset first fetches data, it never needs to
fetch more data because it already has all available records.
To fetch records in small batches, set PacketRecords to the number of records to fetch.
For example, the following statement sets the size of each data packet to ten records:
ClientDataSet1.PacketRecords := 10;
This process of fetching records in batches is called “incremental fetching”. Client
datasets use incremental fetching when PacketRecords is greater than zero.
To fetch each batch of records, the client dataset calls GetNextPacket. Newly fetched
packets are appended to the end of the data already in the client dataset.
GetNextPacket returns the number of records it fetches. If the return value is the same
as PacketRecords, the end of available records was not encountered. If the return value
is greater than 0 but less than PacketRecords, the last record was reached during the
fetch operation. If GetNextPacket returns 0, then there are no more records to fetch.
Warning Incremental fetching does not work if you are fetching data from a remote provider
on a stateless application server. See “Supporting state information in remote
data modules” on page 31-19 for information on how to use incremental fetching
with stateless remote data modules.
Note You can also use PacketRecords to fetch metadata information about the source
dataset. To retrieve metadata information, set PacketRecords to 0.
Fetch-on-demand
Automatic fetching of records is controlled by the FetchOnDemand property. When
FetchOnDemand is True (the default), the client dataset automatically fetches records
as needed. To prevent automatic fetching of records, set FetchOnDemand to False.
When FetchOnDemand is False, the application must explicitly call GetNextPacket to
fetch records.
For example, Applications that need to represent extremely large read-only datasets
can turn off FetchOnDemand to ensure that the client datasets do not try to load more
data than can fit into memory. Between fetches, the client dataset frees its cache using
the EmptyDataSet method. This approach, however, does not work well when the
client must post updates to the server.
The provider controls whether the records in data packets include BLOB data and
nested detail datasets. If the provider excludes this information from records, the
FetchOnDemand property causes the client dataset to automatically fetch BLOB data
and detail datasets on an as-needed basis. If FetchOnDemand is False, and the provider
does not include BLOB data and detail datasets with records, you must explicitly call
the FetchBlobs or FetchDetails method to retrieve this information.
If the client dataset is not active, you can send the parameters to the application
server and retrieve a data packet that reflects those parameter values simply by
setting the Active property to True.
Refreshing records
Client datasets work with an in-memory snapshot of the data from the source
dataset. If the source dataset represents server data, then as time elapses other users
may modify that data. The data in the client dataset becomes a less accurate picture
of the underlying data.
Like any other dataset, client datasets have a Refresh method that updates its records
to match the current values on the server. However, calling Refresh only works if
there are no edits in the change log. Calling Refresh when there are unapplied edits
results in an exception.
Client datasets can also update the data while leaving the change log intact. To do
this, call the RefreshRecord method. Unlike the Refresh method, RefreshRecord updates
only the current record in the client dataset. RefreshRecord changes the record value
originally obtained from the provider but leaves any changes in the change log.
Warning It is not always appropriate to call RefreshRecord. If the user’s edits conflict with
changes made to the underlying dataset by other users, calling RefreshRecord masks
this conflict. When the client dataset applies its updates, no reconcile error occurs
and the application can’t resolve the conflict.
In order to avoid masking update errors, you may want to check that there are no
pending updates before calling RefreshRecord. For example, the following AfterScroll
refreshes the current record every time the user moves to a new record (ensuring the
most up-to-date value), but only when it is safe to do so.:
procedure TForm1.ClientDataSet1AfterScroll(DataSet: TDataSet);
begin
if ClientDataSet1.UpdateStatus = usUnModified then
ClientDataSet1.RefreshRecord;
end;
Once the dataset is created, you can save it to a file. From then on, you do not need to
recreate the table, only load it from the file you saved. When beginning a file-based
database application, you may want to first create and save empty files for your
datasets before writing the application itself. This way, you start with the metadata
for your client dataset already defined, making it easier to set up the user interface.
Note It is also possible to merge changes into the data of a separate client dataset if that
dataset originally provided the data in the Data property. To do this, you must use a
dataset provider. For an example of how to do this, see “Assigning data directly” on
page 29-14.
If you do not want to use the extended undo capabilities of the change log, you can
set the client dataset’s LogChanges property to False. When LogChanges is False, edits
are automatically merged when you post records and there is no need to call
MergeChangeLog.
3 To indicate what data you want to fetch from the server, expand the DataSet
property and set the appropriate values. There are three ways to fetch data from
the server:
• Set CommandType to ctQuery and set CommandText to an SQL statement you
want to execute on the server. This statement is typically a SELECT statement.
Supply the values for any parameters using the Params property.
• Set CommandType to ctStoredProc and set CommandText to the name of the stored
procedure you want to execute. Supply the values for any input parameters
using the Params property.
• Set CommandType to ctTable and set CommandText to the name of the database
tables whose records you want to use.
4 If the data is to be used with visual data controls, add a data source component to
the form or data module, and set its DataSet property to the TSimpleDataSet object.
The data source component forwards the data in the client dataset’s in-memory
cache to data-aware components for display. Connect data-aware components to
the data source using their DataSource and DataField properties.
5 Activate the dataset by setting the Active property to true (or, at runtime, calling
the Open method).
6 If you executed a stored procedure, use the Params property to retrieve any output
parameters.
7 When the user has edited the data in the simple dataset, you can apply those edits
back to the database server by calling the ApplyUpdates method. Resolve any
update errors in an OnReconcileError event handler. For more information on
applying updates, see “Updating records” on page 29-20.
If the client dataset will be editing the data and applying updates, you must include
enough fields so that there are no duplicate records in the data packet. Otherwise,
when the updates are applied, it is impossible to determine which record to update.
If you do not want the client dataset to be able to see or use extra fields provided only
to ensure uniqueness, set the ProviderFlags property for those fields to include
pfHidden.
Note Including enough fields to avoid duplicate records is also a consideration when the
provider’s source dataset represents a query. You must specify the query so that it
includes enough fields to ensure all records are unique, even if your application does
not use all the fields.
As with all method calls made through the IAppServer interface, the provider can
communicate persistent state information with a client dataset before and after the
call to GetRecords. This communication takes place using the BeforeGetRecords and
AfterGetRecords event handlers. For a discussion of persistent state information in
application servers, see “Supporting state information in remote data modules” on
page 31-19.
Note Applications must supply extra support when the updates are directed at a dataset
that does not represent a single table. For details on how to do this, see “Applying
updates to datasets that do not represent a single table” on page 30-12.
For example, the following OnUpdateData event handler inserts the current date into
every new record that is inserted into the database:
procedure TMyDataModule1.Provider1UpdateData(Sender: TObject; DataSet:
TCustomClientDataSet);
begin
with DataSet do
begin
First;
while not Eof do
begin
if UpdateStatus = usInserted then
begin
Edit;
FieldByName('DateCreated').AsDateTime := Date;
Post;
end;
Next;
end;
end;
You might, however, want even more control. For example, with the previous
statement, you might want to prevent the EMPNO field from being modified by
leaving it out of the UPDATE clause and leave the TITLE and DEPT fields out of the
WHERE clause to avoid update conflicts when other applications have modified the
data. To specify the clauses where a specific field appears, use the ProviderFlags
property. ProviderFlags is a set that can include any of the values in Table 30.5
Thus, the following OnUpdateData event handler allows the TITLE field to be
updated and uses the EMPNO and DEPT fields to locate the desired record. If an
error occurs, and a second attempt is made to locate the record based only on the key,
the generated SQL looks for the EMPNO field only:
procedure TMyDataModule1.Provider1UpdateData(Sender: TObject; DataSet:
TCustomClientDataSet);
begin
with DataSet do
begin
FieldByName('TITLE').ProviderFlags := [pfInUpdate];
FieldByName('EMPNO').ProviderFlags := [pfInWhere, pfInKey];
FieldByName('DEPT').ProviderFlags := [pfInWhere];
end;
end;
Note You can use the UpdateFlags property to influence how updates are applied even if
you are updating to a dataset and not using dynamically generated SQL. These flags
still determine which fields are used to locate records and which fields get updated.
The components needed for a multi-tiered application are described in Table 31.1:
3 The application server retrieves the data (first establishing a database connection,
if necessary), packages it for the client, and returns a data packet to the client.
Additional information, (for example, field display characteristics) can be
included in the metadata of the data packet. This process of packaging data into
data packets is called “providing.”
4 The client decodes the data packet and displays the data to the user.
5 As the user interacts with the client application, the data is updated (records are
added, deleted, or modified). These modifications are stored in a change log by the
client.
6 Eventually the client applies its updates to the application server, usually in
response to a user action. To apply updates, the client packages its change log and
sends it as a data packet to the server.
7 The application server decodes the package and posts updates (in the context of a
transaction if appropriate). If a record can’t be posted (for example, because
another application changed the record after the client requested it and before the
client applied its updates), the application server either attempts to reconcile the
client’s changes with the current data, or saves the records that could not be
posted. This process of posting records and caching problem records is called
“resolving.”
8 When the application server finishes the resolving process, it returns any
unposted records to the client for further resolution.
9 The client reconciles unresolved records. There are many ways a client can
reconcile unresolved records. Typically the client attempts to correct the situation
that prevented records from being posted or discards the changes. If the error
situation can be rectified, the client applies updates again.
10 The client refreshes its data from the server.
The client dataset communicates with the provider through the IAppServer interface.
It gets this interface from a connection component. The connection component
establishes the connection to the application server. Different connection components
are available for using different communications protocols. These connection
components are summarized in the following table:
Note The DataSnap page of the Component palette also includes a connection component
that does not connect to an application server at all, but instead supplies an
IAppServer interface for client datasets to use when communicating with providers in
the same application. This component, TLocalConnection, is not required, but makes it
easier to scale up to a multi-tiered application later.
For more information about using connection components, see “Connecting to the
application server” on page 31-23.
To set up object pooling when using a Web connection (HTTP), your remote data
module must override the UpdateRegistry method. In the overridden method, call
RegisterPooled when the remote data module registers and UnregisterPooled when the
remote data module unregisters. When using either method of object pooling, your
remote data module must be stateless. This is because a single instance potentially
handles requests from several clients. If it relied on persistent state information,
clients could interfere with each other. See “Supporting state information in remote
data modules” on page 31-19 for more information on how to ensure that your
remote data module is stateless.
Before you can use a socket connection, the application server must register its
availability to clients using a socket connection. By default, all new remote data
modules automatically register themselves by adding a call to EnableSocketTransport
in the UpdateRegistry method. You can remove this call to prevent socket connections
to your application server.
Note Because older servers did not add this registration, you can disable the check for
whether an application server is registered by unchecking the Connections|
Registered Objects Only menu item on ScktSrvr.exe.
When using sockets, there is no protection on the server against client systems failing
before they release a reference to interfaces on the application server. While this
results in less message traffic than when using DCOM (which sends periodic keep-
alive messages), this can result in an application server that can’t release its resources
because it is unaware that the client has gone away.
Unlike most other connection components, you can’t use callbacks when the
connection is formed via HTTP.
5 Write application server code to implement events, shared business rules, shared
data validation, and shared security. When writing this code, you may want to
• Extend the application server’s interface to provide additional ways for the
client application to call the server. Extending the application server’s interface
is described in “Extending the application server’s interface” on page 31-16.
• Provide transaction support beyond the transactions automatically created
when applying updates. Transaction support in multi-tiered database
applications is described in “Managing transactions in multi-tiered
applications” on page 31-17.
• Create master/detail relationships between the datasets in your application
server. Master/detail relationships are described in “Supporting master/detail
relationships” on page 31-18.
• Ensure your application server is stateless. Handling state information is
described in “Supporting state information in remote data modules” on
page 31-19.
• Divide your application server into multiple remote data modules. Using
multiple remote data modules is described in “Using multiple remote data
modules” on page 31-21.
6 Save, compile, and register or install the application server. Registering an
application server is described in “Registering the application server” on
page 31-22.
7 If your server application does not use DCOM or SOAP, you must install the
runtime software that receives client messages, instantiates the remote data
module, and marshals interface calls.
• For TCP/IP sockets this is a socket dispatcher application, Scktsrvr.exe.
• For HTTP connections this is httpsrvr.dll, an ISAPI/NSAPI DLL that must be
installed with your Web server.
Configuring TRemoteDataModule
To add a TRemoteDataModule component to your application, choose File|New|
Other and select Remote Data Module from the Multitier page of the new items
dialog. You will see the Remote Data Module wizard.
You must supply a class name for your remote data module. This is the base name of
a descendant of TRemoteDataModule that your application creates. It is also the base
name of the interface for that class. For example, if you specify the class name
MyDataServer, the wizard creates a new unit declaring TMyDataServer, a descendant
of TRemoteDataModule, which implements IMyDataServer, a descendant of
IAppServer.
Note You can add your own properties and methods to the new interface. For more
information, see “Extending the application server’s interface” on page 31-16.
You must specify the threading model in the Remote Data Module wizard. You can
choose Single-threaded, Apartment-threaded, Free-threaded, or Both.
• If you choose Single-threaded, COM ensures that only one client request is
serviced at a time. You do not need to worry about client requests interfering with
each other.
• If you choose Apartment-threaded, COM ensures that any instance of your remote
data module services one request at a time. When writing code in an Apartment-
threaded library, you must guard against thread conflicts if you use global
variables or objects not contained in the remote data module. This is the
recommended model if you are using BDE-enabled datasets. (Note that you will
need a session component with its AutoSessionName property set to True to handle
threading issues on BDE-enabled datasets).
• If you choose Free-threaded, your application can receive simultaneous client
requests on several threads. You are responsible for ensuring your application is
thread-safe. Because multiple clients can access your remote data module
simultaneously, you must guard your instance data (properties, contained objects,
and so on) as well as global variables. This is the recommended model if you are
using ADO datasets.
• If you choose Both, your library works the same as when you choose Free-
threaded, with one exception: all callbacks (calls to client interfaces) are serialized
for you.
• If you choose Neutral, the remote data module can receive simultaneous calls on
separate threads, as in the Free-threaded model, but COM guarantees that no two
threads access the same method at the same time.
If you are creating an EXE, you must also specify what type of instancing to use. You
can choose Single instance or Multiple instance (Internal instancing applies only if
the client code is part of the same process space.)
• If you choose Single instance, each client connection launches its own instance of
the executable. That process instantiates a single instance of the remote data
module, which is dedicated to the client connection.
• If you choose Multiple instance, a single instance of the application (process)
instantiates all remote data modules created for clients. Each remote data module
is dedicated to a single client connection, but they all share the same process space.
Configuring TMTSDataModule
To add a TMTSDataModule component to your application, choose File|New|Other
and select Transactional Data Module from the Multitier page of the new items
dialog. You will see the Transactional Data Module wizard.
You must supply a class name for your remote data module. This is the base name of
a descendant of TMTSDataModule that your application creates. It is also the base
name of the interface for that class. For example, if you specify the class name
MyDataServer, the wizard creates a new unit declaring TMyDataServer, a descendant
of TMTSDataModule, which implements IMyDataServer, a descendant of IAppServer.
Note You can add your own properties and methods to your new interface. For more
information, see “Extending the application server’s interface” on page 31-16.
You must specify the threading model in the Transactional Data Module wizard.
Choose Single, Apartment, or Both.
• If you choose Single, client requests are serialized so that your application services
only one at a time. You do not need to worry about client requests interfering with
each other.
• If you choose Apartment, the system ensures that any instance of your remote
data module services one request at a time, and calls always use the same thread.
You must guard against thread conflicts if you use global variables or objects not
contained in the remote data module. Instead of using global variables, you can
use the shared property manager. For more information on the shared property
manager, see “Shared property manager” on page 46-6.
• If you choose Both, MTS calls into the remote data module’s interface in the same
way as when you choose Apartment. However, any callbacks you make to client
applications are serialized, so that you don’t need to worry about them interfering
with each other.
Note The Apartment model under MTS or COM+ is different from the corresponding
model under DCOM.
You must also specify the transaction attributes of your remote data module. You can
choose from the following options:
• Requires a transaction. When you select this option, every time a client uses your
remote data module’s interface, that call is executed in the context of a transaction.
If the caller supplies a transaction, a new transaction need not be created.
• Requires a new transaction. When you select this option, every time a client uses
your remote data module’s interface, a new transaction is automatically created
for that call.
• Supports transactions. When you select this option, your remote data module can
be used in the context of a transaction, but the caller must supply the transaction
when it invokes the interface.
• Does not support transactions. When you select this option, your remote data
module can’t be used in the context of transactions.
Configuring TSoapDataModule
To add a TSoapDataModule component to your application, choose File|New|Other
and select SOAP Server Data Module from the WebServices page of the new items
dialog. The SOAP data module wizard appears.
You must supply a class name for your SOAP data module. This is the base name of a
TSoapDataModule descendant that your application creates. It is also the base name of
the interface for that class. For example, if you specify the class name MyDataServer,
the wizard creates a new unit declaring TMyDataServer, a descendant of
TSoapDataModule, which implements IMyDataServer, a descendant of
IAppServerSOAP.
Note To use TSoapDataModule, the new data module should be added to a Web Service
application. The IAppServerSOAP interface is an invokable interface, which is
registered in the initialization section of the new unit. This allows the invoker
component in the main Web module to forward all incoming calls to your data
module.
You may want to edit the definitions of the generated interface and TSoapDataModule
descendant, adding your own properties and methods. These properties and
methods are not called automatically, but client applications that request your new
interface by name or GUID can use any of the properties and methods that you add.
Note You must explicitly save the TLB file by choosing Refresh in the type library editor
and then saving the changes from the IDE.
Once you have added to your remote data module’s interface, locate the properties
and methods that were added to your remote data module’s implementation. Add
code to finish this implementation by filling in the bodies of the new methods.
If you are not writing a SOAP data module, client applications call your interface
extensions using the AppServer property of their connection component. With SOAP
data modules, they call the connection component’s GetSOAPServer method. For
more information on how to call your interface extensions, see “Calling server
interfaces” on page 31-28.
If you have a transactional data module, you can broaden your transaction support
by using COM+ (or MTS) transactions. These transactions can include any of the
business logic on your application server, not just the database access. In addition,
because they support two-phase commits, they can span multiple databases.
Only the BDE- and ADO-based data access components support two-phase commit.
Do not use InterbaseExpress or dbExpress components if you want to have
transactions that span multiple databases.
Warning When using the BDE, two-phase commit is fully implemented only on Oracle7 and
MS-SQL databases. If your transaction involves multiple databases, and some of
them are remote servers other than Oracle7 or MS-SQL, your transaction runs a small
risk of only partially succeeding. Within any one database, however, you will always
have transaction support.
By default, all IAppServer calls on a transactional data module are transactional. You
need only set the transaction attribute of your data module to indicate that it must
participate in transactions. In addition, you can extend the application server’s
interface to include method calls that encapsulate transactions that you define.
If your transaction attribute indicates that the remote data module requires a
transaction, then every time a client calls a method on its interface, it is automatically
wrapped in a transaction. All client calls to your application server are then enlisted
in that transaction until you indicate that the transaction is complete. These calls
either succeed as a whole or are rolled back.
Note Do not combine COM+ or MTS transactions with explicit transactions created by a
database connection component or using explicit SQL commands. When your
transactional data module is enlisted in a transaction, it automatically enlists all of
your database calls in the transaction as well.
For more information about using COM+ (or MTS) transactions, see “MTS and
COM+ transaction support” on page 46-9.
In multi-tiered applications, you can avoid these problems by using nested tables to
represent the master/detail relationship. To do this when providing from datasets,
set up a master/detail relationship between the datasets on the application server.
Then set the DataSet property of your provider component to the master table. To use
nested tables to represent master/detail relationships when providing from XML
documents, use a transformation file that defines the nested detail sets.
When clients call the GetRecords method of the provider, it automatically includes the
detail dataset as a DataSet field in the records of the data packet. When clients call the
ApplyUpdates method of the provider, it automatically handles applying updates in
the proper order.
Usually the application server is on a different machine from the client application,
but even if the server resides on the same machine as the client application (for
example, during the building and testing of the entire multi-tier application), you can
still use the connection component to identify the application server by name, specify
a server machine, and use the application server interface.
If you have multiple servers that your client application can choose from, you can use
the ObjectBroker property instead of specifying a value for Address or Host. For more
information, see “Brokering connections” on page 31-27.
By default, the value of Port is 211, which is the default port number of the socket
dispatcher program that forwards incoming messages to your application server. If
the socket dispatcher has been configured to use a different port, set the Port
property to match that value.
Note You can configure the port of the socket dispatcher while it is running by right-
clicking the Borland Socket Server tray icon and choosing Properties.
Although socket connections do not provide for using security protocols, you can
customize the socket connection to add your own encryption. To do this
1 Create a COM object that supports the IDataIntercept interface. This is an interface
for encrypting and decrypting data.
2 Use TPacketInterceptFactory as the class factory for this object. If you are using a
wizard to create the COM object in step 1, replace the line in the initialization
section that says TComponentFactory.Create(...) with
TPacketInterceptFactory.Create(...).
3 Register your new COM server on the client machine.
4 Set the InterceptName or InterceptGUID property of the socket connection
component to specify this COM object. If you used TPacketInterceptFactory in step
2, your COM server appears in the drop-down list of the Object Inspector for the
InterceptName property.
5 Finally, right click the Borland Socket Server tray icon, choose Properties, and on
the properties tab set the Intercept Name or Intercept GUID to the ProgId or GUID
for the interceptor.
This mechanism can also be used for data compression and decompression.
If the Web server requires authentication, or if you are using a proxy server that
requires authentication, you must set the values of the UserName and Password
properties so that the connection component can log on.
If you have multiple servers that your client application can choose from, you can use
the ObjectBroker property instead of specifying a value for URL. For more
information, see “Brokering connections” on page 31-27.
Note When using TSoapConnection, wininet.dll must be installed on the client machine. If
you have IE3 or higher installed, wininet.dll can be found in the Windows system
directory.
Brokering connections
If you have multiple COM-based servers that your client application can choose
from, you can use an Object Broker to locate an available server system. The object
broker maintains a list of servers from which the connection component can choose.
When the connection component needs to connect to an application server, it asks the
Object Broker for a computer name (or IP address, host name, or URL). The broker
supplies a name, and the connection component forms a connection. If the supplied
name does not work (for example, if the server is down), the broker supplies another
name, and so on, until a connection is formed.
Once the connection component has formed a connection with a name supplied by
the broker, it saves that name as the value of the appropriate property
(ComputerName, Address, Host, RemoteHost, or URL). If the connection component
closes the connection later, and then needs to reopen the connection, it tries using this
property value, and only requests a new name from the broker if the connection fails.
Use an Object Broker by specifying the ObjectBroker property of your connection
component. When the ObjectBroker property is set, the connection component does
not save the value of ComputerName, Address, Host, RemoteHost, or URL to disk.
Note You can not use the ObjectBroker property with SOAP connections.
However, this technique provides late (dynamic) binding of the interface call. That is,
the SpecialMethod procedure call is not bound until runtime when the call is executed.
Late binding is very flexible, but by using it you lose many benefits such as code
insight and type checking. In addition, late binding is slower than early binding,
because the compiler generates additional calls to the server to set up interface calls
before they are invoked.
module, add an additional component that shares the connection to the main remote
data module.
1 For the connection to the main remote data module, add and set up a connection
component as described in “Connecting to the application server” on page 31-23.
The only limitation is that you can’t use a SOAP connection.
2 For each child remote data module, use a TSharedConnection component.
• Set its ParentConnection property to the connection component you added in
step 1. The TSharedConnection component shares the connection that this main
connection establishes.
• Set its ChildName property to the name of the property on the main remote data
module’s interface that exposes the interface of the desired child remote data
module.
When you assign the TSharedConnection component placed in step 2 as the value of a
client dataset’s RemoteServer property, it works as if you were using an entirely
independent connection to the child remote data module. However, the
TSharedConnection component uses the connection established by the component you
placed in step 1.
Web-based
Client
Application Application
Server
Remote Database
There are two approaches that you can take to build the Web application:
• You can combine the multi-tiered database architecture with an ActiveX form to
distribute the client application as an ActiveX control. This allows any browser
that supports ActiveX to run your client application as an in-process server.
• You can use XML data packets to build an InternetExpress application. This allows
browsers that supports javascript to interact with your client application through
html pages.
These two approaches are very different. Which one you choose depends on the
following considerations:
• Each approach relies on a different technology (ActiveX vs. javascript and XML).
Consider what systems your end users will use. The first approach requires a
browser to support ActiveX (which limits clients to a Windows platform). The
second approach requires a browser to support javascript and the DHTML
capabilities introduced by Netscape 4 and Internet Explorer 4.
• ActiveX controls must be downloaded to the browser to act as an in-process
server. As a result, the clients using an ActiveX approach require much more
memory than the clients of an HTML-based application.
• The InternetExpress approach can be integrated with other HTML pages. An
ActiveX client must run in a separate window.
• The InternetExpress approach uses standard HTTP, thereby avoiding any firewall
issues that confront an ActiveX application.
• The ActiveX approach provides greater flexibility in how you program your
application. You are not limited by the capabilities of the javascript libraries. The
client datasets used in the ActiveX approach surface more features (such as filters,
ranges, aggregation, optional parameters, delayed fetching of BLOBs or nested
details, and so on) than the XML brokers used in the InternetExpress approach.
Caution Your Web client application may look and act differently when viewed from
different browsers. Test your application with the browsers you expect your end-
users to use.
7 Right-click a Web page and choose Action Editor to display the Action editor. Add
action items for every message you want to handle from browsers. Associate the
page producers you added in step 6 with these actions by setting their Producer
property or writing code in an OnAction event handler. For more information on
adding action items using the Action editor, see “Adding actions to the
dispatcher” on page 34-5.
8 Double-click each Web page to display the Web Page editor. (You can also display
this editor by clicking the ellipsis button in the Object Inspector next to the
WebPageItems property.) In this editor you can add Web Items to design the pages
that users see in their browsers. For more information about designing Web pages
for your InternetExpress application, see “Creating Web pages with an
InternetExpress page producer” on page 31-39.
9 Build your Web application. Once you install this application with your Web
server, browsers can call it by specifying the name of the application as the script
name portion of the URL and the name of the Web Page component as the
pathinfo portion.
Once you have installed these libraries, you must set the IncludePathURL property of
all InternetExpress page producers to indicate where they can be found.
It is possible to write your own HTML pages using the javascript classes provided in
these libraries instead of using Web items to generate your Web pages. However, you
must ensure that your code does not do anything illegal, as these classes include
minimal error checking (so as to minimize the size of the generated Web pages).
You must set the following properties so that the XML producer can use the
IAppServer interface:
• Set the RemoteServer property to the connection component that establishes the
connection to the application server and gets its IAppServer interface. At design
time, you can select this value from a drop-down list in the object inspector.
• Set the ProviderName property to the name of the provider component on the
application server that represents the dataset for which you want XML data
packets. This provider both supplies XML data packets and applies updates from
XML delta packets. At design time, if the RemoteServer property is set and the
connection component has an active connection, the Object Inspector displays a
list of available providers. (If you are using a DCOM connection the application
server must also be registered on the client machine).
Two properties let you indicate what you want to include in data packets:
• You can limit the number of records that are added to the data packet by setting
the MaxRecords property. This is especially important for large datasets because
InternetExpress applications send the entire data packet to client Web browsers. If
the data packet is too large, the download time can become prohibitively long.
• If the provider on the application server represents a query or stored procedure,
you may want to provide parameter values before obtaining an XML data packet.
You can supply these parameter values using the Params property.
The components that generate HTML and javascript for the InternetExpress
application automatically use the XML broker’s XML data packet once you set their
XMLBroker property. To obtain the XML data packet directly in code, use the
RequestRecords method.
Note When the XML broker supplies a data packet to another component (or when you
call RequestRecords), it receives an OnRequestRecords event. You can use this event to
supply your own XML string instead of the data packet from the application server.
For example, you could fetch the XML data packet from the application server using
GetXMLRecords and then edit it before supplying it to the emerging Web page.
So that the dispatcher can recognize messages for the XML broker, you must describe
them using the WebDispatch property. Set the PathInfo property to the path portion of
the URL to which messages for the XML broker are sent. Set MethodType to the value
of the method header of update messages addressed to that URL (typically mtPost). If
you want to respond to all messages with the specified path, set MethodType to
mtAny. If you don’t want the XML broker to respond directly to update messages (for
example, if you want to handle them explicitly using an action item), set the Enabled
property to False. For more information on how the Web dispatcher determines
which component handles messages from the Web browser, see “Dispatching
request messages” on page 34-5.
When the dispatcher passes an update message on to the XML broker, it passes the
updates on to the application server and, if there are update errors, receives an XML
delta packet describing all update errors. Finally, it sends a response message back to
the browser, which either redirects the browser to the same page that generated the
XML delta packet or sends it some new content.
A number of events allow you to insert custom processing at all steps of this update
process:
1 When the dispatcher first passes the update message to the XML broker, it receives
a BeforeDispatch event, where you can preprocess the request or even handle it
entirely. This event allows the XML broker to handle messages other than update
messages.
2 If the BeforeDispatch event handler does not handle the message, the XML broker
receives an OnRequestUpdate event, where you can apply the updates yourself
rather than using the default processing.
3 If the OnRequestUpdate event handler does not handle the request, the XML broker
applies the updates and receives a delta packet containing any update errors.
4 If there are no update errors, the XML broker receives an OnGetResponse event,
where you can create a response message that indicates the updates were
successfully applied or sends refreshed data to the browser. If the OnGetResponse
event handler does not complete the response (does not set the Handled parameter
to True), the XML broker sends a response that redirects the browser back to the
document that generated the delta packet.
5 If there are update errors, the XML broker receives an OnGetErrorResponse event
instead. You can use this event to try to resolve update errors or to generate a Web
page that describes them to the end user. If the OnGetErrorResponse event handler
does not complete the response (does not set the Handled parameter to True), the
XML broker calls on a special content producer called the ReconcileProducer to
generate the content of the response message.
6 Finally, the XML broker receives an AfterDispatch event, where you can perform
any final actions before sending a response back to the Web browser.
The InternetExpress page producer can contain one of two types of item, each of
which generates an HTML form:
• TDataForm, which generates an HTML form for displaying data and the controls
that manipulate that data or submit updates.
Items you add to TDataForm display data in a multi-record grid (TDataGrid) or in a
set of controls each of which represents a single field from a single record
(TFieldGroup). In addition, you can add a set of buttons to navigate through data or
post updates (TDataNavigator), or a button to apply updates back to the Web client
(TApplyUpdatesButton). Each of these items contains subitems to represent
individual fields or buttons. Finally, as with most Web items, you can add a layout
grid (TLayoutGroup), that lets you customize the layout of any items it contains.
• TQueryForm, which generates an HTML form for displaying or reading
application-defined values. For example, you can use this form for displaying and
submitting parameter values.
Items you add to TQueryForm display application-defined
values(TQueryFieldGroup) or a set of buttons to submit or reset those values
(TQueryButtons). Each of these items contains subitems to represent individual
values or buttons. You can also add a layout grid to a query form, just as you can
to a data form.
The bottom of the Web page editor displays the generated HTML code and lets you
see what it looks like in a browser (Internet Explorer).
attributes that apply to a tag and everything in its scope. Web items offer a flexible
selection of ways to use them:
• The simplest way to use styles is to define a style attribute directly on the Web
item. To do this, use the Style property. The value of Style is simply the attribute
definition portion of a standard HTML style definition, such as
color: red.
• You can also define a style sheet that defines a set of style definitions. Each
definition includes a style selector (the name of a tag to which the style always
applies or a user-defined style name) and the attribute definition in curly braces:
H2 B {color: red}
.MyStyle {font-family: arial; font-weight: bold; font-size: 18px }
The entire set of definitions is maintained by the InternetExpress page producer as
its Styles property. Each Web item can then reference the styles with user-defined
names by setting its StyleRule property.
• If you are sharing a style sheet with other applications, you can supply the style
definitions as the value of the InternetExpress page producer’s StylesFile property
instead of the Styles property. Individual Web items still reference styles using the
StyleRule property.
Another common property of Web items is the Custom property. Custom includes a
set of options that you add to the generated HTML tag. HTML defines a different set
of options for each type of tag. The VCL reference for the Custom property of most
Web items gives an example of possible options. For more information on possible
options, use an HTML reference.
<#STYLES> generates the statements that defines a style sheet from definitions listed in
the Styles or StylesFile property of the InternetExpress page producer.
<#WARNINGS> generates nothing at runtime. At design time, it adds warning messages
for problems detected while generating the HTML document. You can see these
messages in the Web page editor.
<#FORMS> generates the HTML produced by the components that you add in the Web
page editor. The HTML from each component is generated in the order it appears in
WebPageItems.
<#SCRIPT> generates a block of javascript declarations that are used in the HTML
generated by the components added in the Web page editor.
You can replace the default template by changing the value of HTMLDoc or setting
the HTMLFile property. The customized HTML template can include any of the
HTML-transparent tags that make up the default template. The InternetExpress page
producer automatically translates these tags when you call the Content method. In
addition, The InternetExpress page producer automatically translates three
additional tags:
<#BODYELEMENTS> is replaced by the same HTML as results from the 5 tags in the default
template. It is useful when generating a template in an HTML editor when you want
to use the default layout but add additional elements using the editor.
<#COMPONENT Name=WebComponentName> is replaced by the HTML that the component
named WebComponentName generates. This component can be one of the components
added in the Web page editor, or it can be any component that supports the
IWebContent interface and has the same Owner as the InternetExpress page producer.
<#DATAPACKET XMLBroker=BrokerName> is replaced with the XML data packet obtained
from the XML broker specified by BrokerName. When, in the Web page editor, you see
the HTML that the InternetExpress page producer generates, you see this tag instead
of the actual XML data packet.
In addition, the customized template can include any other HTML-transparent tags
that you define. When the InternetExpress page producer encounters a tag that is not
one of the seven types it translates automatically, it generates an OnHTMLTag event,
where you can write code to perform your own translations. For more information
about HTML templates in general, see “HTML templates” on page 34-14.
Tip The components that appear in the Web page editor generate static code. That is,
unless the application server changes the metadata that appears in data packets, the
HTML is always the same, no matter when it is generated. You can avoid the
overhead of generating this code dynamically at runtime in response to every request
message by copying the generated HTML in the Web page editor and using it as a
template. Because the Web page editor displays a <#DATAPACKET> tag instead of
the actual XML, using this as a template still allows your application to fetch data
packets from the application server dynamically.
Defining transformations
Before you can convert between data packets and XML documents, you must define
the relationship between the metadata in a data packet and the nodes of the
corresponding XML document. A description of this relationship is stored in a
special XML document called a transformation.
Each transformation file contains two things: the mapping between the nodes in an
XML schema and the fields in a data packet, and a skeletal XML document that
represents the structure for the results of the transformation. A transformation is a
one-way mapping: from an XML schema or document to a data packet or from the
metadata in a data packet to an XML schema. Often, you create transformation files
in pairs: one that maps from XML to data packet, and one that maps from data packet
to XML.
In order to create the transformation files for a mapping, use the XMLMapper utility
that ships in the bin directory.
One natural mapping between this document and a dataset would map each e-mail
message to a single record. The record would have fields for the sender’s name and
address. Because an e-mail message can have multiple recipients, the recipient (<to>
would map to a nested dataset. Similarly, the cc list maps to a nested dataset. The
subject line would map to a string field while the message itself (<content>) would
probably be a memo field. The names of attachment files would map to a nested
dataset because one message can have several attachments. Thus, the e-mail above
would map to a dataset something like the following:
Name Address
Joe Engineer jengineer@MyCo.Com
Name Address
Robin Smith rsmith@MyCo.Com
Leonard Devon ldevon@MyCo.Com
Attachfile
XMLSpec.txt
Schedule.txt
Defining such a mapping involves identifying those nodes of the XML document that
can be repeated and mapping them to nested datasets. Tagged elements that have
values and appear only once (such as <content>...</content>) map to fields whose
datatype reflects the type of data that can appear as the value. Attributes of a tag
(such as the AttachFile attribute of the attachment tag) also map to fields.
Note that not all tags in the XML document appear in the corresponding dataset. For
example, the <head>...<head/> element has no corresponding element in the
resulting dataset. Typically, only elements that have values, elements that can be
repeated, or the attributes of a tag map to the fields (including nested dataset fields)
of a dataset. The exception to this rule is when a parent node in the XML document
maps to a field whose value is built up from the values of the child nodes. For
example, an XML document might contain a set of tags such as
<FullName>
<Title> Mr. </Title>
<FirstName> John </FirstName>
<LastName> Smith </LastName>
</FullName>
Using XMLMapper
The XML mapper utility, xmlmapper.exe, lets you define mappings in three ways:
• From an existing XML schema (or document) to a client dataset that you define.
This is useful when you want to create a database application to work with data
for which you already have an XML schema.
• From an existing data packet to a new XML schema you define. This is useful
when you want to expose existing database information in XML, for example to
create a new business-to-business communication system.
• Between an existing XML schema and an existing data packet. This is useful when
you have an XML schema and a database that both describe the same information
and you want to make them work together.
Once you define the mapping, you can generate the transformation files that are used
to convert XML documents to data packets and to convert data packets to XML
documents. Note that only the transformation file is directional: a single mapping
can be used to generate both the transformation from XML to data packet and from
data packet to XML.
Note XML mapper relies on two .DLLs (midas.dll and msxml.dll) to work correctly. Be
sure that you have both of these .DLLs installed before you try to use
xmlmapper.exe. In addition, msxml.dll must be registered as a COM server. You can
register it using Regsvr32.exe.
Defining mappings
The mapping between an XML document and a data packet need not include all of
the fields in the data packet or all of the tagged elements in the XML document.
Therefore, you must first specify those elements that are mapped. To specify these
elements, first select the Mapping page in the central pane of the dialog.
To specify the elements of an XML document or schema that are mapped to fields in
a data packet, select the Sample or Structure tab of the XML document pane and
double-click on the nodes for elements that map to data packet fields.
To specify the fields of the data packet that are mapped to tagged elements or
attributes in the XML document, double-click on the nodes for those fields in the
Datapacket pane.
If you have only loaded one side of the mapping (the XML document or the data
packet), you can generate the other side after you have selected the nodes that are
mapped.
• If you are generating a data packet from an XML document, you first define
attributes for the selected nodes that determine the types of fields to which they
correspond in the data packet. In the center pane, select the Node Repository page.
Select each node that participates in the mapping and indicate the attributes of the
corresponding field. If the mapping is not straightforward (for example, a node
with subnodes that corresponds to a field whose value is built from those
subnodes), check the User Defined Translation check box. You will need to write
an event handler later to perform the transformation on user defined nodes.
Once you have specified the way nodes are to be mapped, choose Create|
Datapacket from XML. The corresponding data packet is automatically generated
and displayed in the Datapacket pane.
• If you are generating an XML document from a data packet, choose Create|XML
from Datapacket. A dialog appears where you can specify the names of the tags
and attributes in the XML document that correspond to fields, records, and
datasets in the data packet. For field values, the way you name them indicates
whether they map to a tagged element with a value or to an attribute. Names that
begin with an @ symbol map to attributes of the tag that corresponds to the record,
while names that do not begin with an @ symbol map to tagged elements that
have values and that are nested within the element for the record.
• If you have loaded both an XML document and a data packet (client dataset file),
be sure you select corresponding nodes in the same order. The corresponding
nodes should appear next to each other in the table at the top of the Mapping page.
Once you have loaded or generated both the XML document and the data packet and
selected the nodes that appear in the mapping, the table at the top of the Mapping
page should reflect the mapping you have defined.
TXMLTransform checks these properties in the order listed above. That is, it first
checks for a file name in the SourceXmlFile property. Only if SourceXmlFile is an
empty string does it check the SourceXml property. Only if SourceXml is an empty
string does it then check the SourceXmlDocument property.
For example, the following OnTranslate event handler converts a node in the XML
document with the following form
<FullName>
<Title> </Title>
<FirstName> </FirstName>
<LastName> </LastName>
</FullName>
into a single field value:
procedure TForm1.XMLTransform1Translate(Sender: TObject; Id: String; SrcNode: IDOMNode;
var Value: String; DestNode: IDOMNode);
var
CurNode: IDOMNode;
begin
if Id = 'FullName' then
begin
Value = '';
if SrcNode.hasChildNodes then
begin
CurNode := SrcNode.firstChild;
Value := Value + CurNode.nodeValue;
while CurNode <> SrcNode.lastChild do
begin
CurNode := CurNode.nextSibling;
Value := Value + ' ';
Value := Value + CurNode.nodeValue;
end;
end;
end;
end;
When using TXMLTransformProvider, you must specify the transformations that these
two TXMLTransform components use to translate between data packets and the
source XML document. You do this by setting the TXMLTransform component’s
TransformationFile or TransformationDocument property, just as when using a stand-
alone TXMLTransform component.
In addition, if the transformation includes any user-defined nodes, you must supply
an OnTranslate event handler to the internal TXMLTransform components.
You do not need to specify the source document on the TXMLTransform components
that are the values of TransformRead and TransformWrite. For TransformRead, the
source is the file specified by the provider’s XMLDataFile property (although, if you
set XMLDataFile to an empty string, you can supply the source document using
TransformRead.XmlSource or TransformRead.XmlSourceDocument). For TransformWrite,
the source is generated internally by the provider when it applies updates.
Before you can create an XML document that represents the data from a provider,
you must specify the transformation file that TransformGetData uses to translate the
data packet into the appropriate XML format. You do this by setting the
TXMLTransform component’s TransformationFile or TransformationDocument property,
just as when using a stand-alone TXMLTransform component. If that transformation
includes any user-defined nodes, you will want to supply TransformGetData with an
OnTranslate event handler as well.
There is no need to specify the source document for TransformGetData,
TXMLTransformClient fetches that from the provider. However, if the provider
expects any input parameters, you may want to set them before fetching the data.
Use the SetParams method to supply these input parameters before you fetch data
from the provider. SetParams takes two arguments: a string of XML from which to
extract parameter values, and the name of a transformation file to translate that XML
into a data packet. SetParams uses the transformation file to convert the string of XML
into a data packet, and then extracts the parameter values from that data packet.
Note You can override either of these arguments if you want to specify the parameter
document or transformation in another way. Simply set one of the properties on
TransformSetParams property to indicate the document that contains the parameters
or the transformation to use when converting them, and then set the argument you
want to override to an empty string when you call SetParams. For details on the
properties you can use, see “Converting XML documents into data packets” on
page 32-6.
Once you have configured TransformGetData and supplied any input parameters, you
can call the GetDataAsXml method to fetch the XML. GetDataAsXml sends the current
parameter values to the provider, fetches a data packet, converts it into an XML
document, and returns that document as a string. You can save this string to a file:
var
XMLDoc: TFileStream;
XML: string;
begin
XMLTransformClient1.ProviderName := 'Provider1';
XMLTransformClient1.TransformGetData.TransformationFile := 'CustTableToCustXML.xtr';
XMLTransformClient1.TransFormSetParams.SourceXmlFile := 'InputParams.xml';
XMLTransformClient1.SetParams('', 'InputParamsToDP.xtr');
XML := XMLTransformClient1.GetDataAsXml;
XMLDoc := TFileStream.Create('Customers.xml', fmCreate or fmOpenWrite);
try
XMLDoc.Write(XML, Length(XML));
finally
XMLDoc.Free;
end;
end;
III
Writing Internet applications
Part III
The chapters in “Writing Internet applications” present concepts and skills necessary
for building applications that are distributed over the Internet. The components
described in this section are not available in all editions of Delphi.
The first step in building a Web server application is choosing which architecture you
want to use, Web Broker or WebSnap. Both approaches provide many of the same
features, including
• Support for CGI and Apache DSO Web server application types. These are
described in “Types of Web server applications” on page 33-6.
• Multithreading support so that incoming client requests are handled on separate
threads.
• Caching of Web modules for quicker responses.
• Cross-platform development. You can easily port your Web server application
between the Windows and Linux operating systems. Your source code will
compile on either platform.
Both the Web Broker and WebSnap components handle all of the mechanics of page
transfer. WebSnap uses Web Broker as its foundation, so it incorporates all of the
functionality of Web Broker’s architecture. WebSnap offers a much more powerful
set of tools for generating pages, however. Also, WebSnap applications allow you to
use server-side scripting to help generate pages at runtime. Web Broker does not
have this scripting capability. The tools offered in Web Broker are not nearly as
complete as those in WebSnap, and are much less intuitive. If you are developing a
new Web server application, WebSnap is probably a better choice of architecture than
Web Broker.
The major differences between these two approaches are outlined in the following
table:
For more information on Web Broker, see Chapter 34, “Using Web Broker.” For more
information on WebSnap, see Chapter 35, “Creating Web Server applications using
WebSnap.”
http://www.Tsite.com/art/gallery.dll/mammals?animal=dog&color=black
The first portion (not technically part of the URL) identifies the protocol (http). This
portion can specify other protocols such as https (secure http), ftp, and so on.
The Host portion identifies the machine that runs the Web server and Web server
application. Although it is not shown in the preceding picture, this portion can
override the port that receives messages. Usually, there is no need to specify a port,
because the port number is implied by the protocol.
The ScriptName portion specifies the name of the Web server application. This is the
application to which the Web server passes messages.
Following the script name is the pathinfo. This identifies the destination of the
message within the Web server application. Path info values may refer to directories
on the host machine, the names of components that respond to specific messages, or
any other mechanism the Web server application uses to divide the processing of
incoming messages.
The Query portion contains a set a named values. These values and their names are
defined by the Web server application.
• If the program is a dynamic-link library (DLL), the server loads the DLL (if
necessary) and passes the information contained in the request to the DLL as a
structure. The server waits while the program executes. When the DLL exits, it
passes the content directly back to the server.
In all cases, the program acts on the request of and performs actions specified by the
programmer: accessing databases, doing simple table lookups or calculations,
constructing or selecting HTML documents, and so on.
CGI stand-alone
A CGI stand-alone Web server application is a console application that receives client
request information on standard input and passes the results back to the server on
standard output. This data is evaluated by the CGI application, which creates
appropriate request and response objects. Each request message is handled by a
separate instance of the application.
Apache
An Apache Web server application is a DLL that is loaded by the Web server. Client
request information is passed to the DLL as a structure and evaluated by the Apache
Web server application, which creates appropriate request and response objects.
Each request message is automatically handled in a separate execution thread. You
can build your Web server applications using Apache 1 or 2 as your target type.
When you deploy your Apache Web server application, you will need to specify
some application-specific information in the Apache configuration files. For example,
in Apache 1 projects the default module name is the project name with _module
appended to the end. For example, a project named Project1 would have
Project1_module as its module name. Similarly, the default content type is the project
name with -content appended, and the default handler type is the project name with-
handler appended.
These definitions can be changed in the project (.dpr) file when necessary. For
example, when you create your project a default module name is stored in the project
file. Here is a common example:
exports
apache_module name ’Project1_module’;
Note When you rename the project during the save process, that name isn’t changed
automatically. Whenever you rename your project, you must change the module
name in your project file to match your project name. The content and handler
definitions should change automatically once the module name is changed.
For information on using module, content, and handler definitions in your Apache
configuration files, see the documentation on the Apache Web site httpd.apache.org.
6 Once you have selected your application in the drop-down list, press the Go
button. This launches your application in the Web Application Debugger, which
provides you with details on request and response messages that pass between
your application and the Web Application Debugger.
34
Using Web Broker
Chapter34
Web Broker components (located on the Internet tab of the component palette)
enable you to create event handlers that are associated with a specific Uniform
Resource Identifier (URI). When processing is complete, you can programmatically
construct HTML or XML documents and transfer them to the client. You can use Web
Broker components for cross-platform application development.
Frequently, the content of Web pages is drawn from databases. You can use Internet
components to automatically manage connections to databases, allowing a single
DLL to handle numerous simultaneous, thread-safe database connections.
The following sections in this chapter explain how you use the Web Broker
components to create a Web server application.
• Apache: Selecting one of these two application types (1.x and 2.x) sets up your
project as a DLL, with the exported methods expected by the applicable Apache
Web server. It adds the library header to the project file and the required entries
to the uses list and exports clause of the project file.
• Web Application Debugger stand-alone executable: Selecting this type of
application sets up an environment for developing and testing Web server
applications. This type of application is not intended for deployment.
Choose the type of Web Server Application that communicates with the type of Web
Server your application will use. This creates a new project configured to use Internet
components and containing an empty Web Module.
Note The Web module that you set up at design time is actually a template. In ISAPI and
NSAPI applications, each request message spawns a separate thread, and separate
instances of the Web module and its contents are created dynamically for each
thread.
Warning The Web module in a DLL-based Web server application is cached for later reuse to
increase response time. The state of the dispatcher and its action list is not
reinitialized between requests. Enabling or disabling action items during execution
may cause unexpected results when that module is used for subsequent client
requests.
The action items are responsible for reading the request and assembling a response
message. Specialized content producer components aid the action items in
dynamically generating the content of response messages, which can include custom
HTML code or other MIME content. The content producers can make use of other
content producers or descendants of THTMLTagAttributes, to help them create the
content of the response message. For more information on content producers, see
“Generating the content of response messages” on page 34-13.
If you are creating the Web Client in a multi-tiered database application, your Web
server application may include additional, auto-dispatching components that
represent database information encoded in XML and database manipulation classes
encoded in javascript. If you are creating a server that implements a Web Service,
your Web server application may include an auto-dispatching component that passes
SOAP-based messages on to an invoker that interprets and executes them. The
dispatcher calls on these auto-dispatching components to handle the request message
after it has tried all of its action items.
When all action items (or auto-dispatching components) have finished creating the
response by filling out the TWebResponse object, the dispatcher passes the result back
to the Web application. The application sends the response on to the client via the
Web server.
After checking all its action items, if the message is not handled the dispatcher checks
any specially registered auto-dispatching components that do not use action items.
These components are specific to multi-tiered database applications, which are
described in “Building Web applications using InternetExpress” on page 31-33
If, after checking all the action items and any specially registered auto-dispatching
components, the request message has still not been fully handled, the dispatcher calls
the default action item. The default action item does not need to match either the
target URL or the method of the request.
If the dispatcher reaches the end of the action list (including the default action, if any)
and no actions have been triggered, nothing is passed back to the server. The server
simply drops the connection to the client.
If the request is handled by the action items, the dispatcher generates an
AfterDispatch event. This provides a final opportunity for your application to check
the response that was generated, and make any last minute changes.
Action items
Each action item (TWebActionItem) performs a specific task in response to a given
type of request message.
Action items can completely respond to a request or perform part of the response and
allow other action items to complete the job. Action items can send the HTTP
response message for the request, or simply set up part of the response for other
action items to complete. If a response is completed by the action items but not sent,
the Web server application sends the response message.
Use path information to indicate where your Web application should look for
information when servicing requests, or to divide you Web application into logical
subservices.
The dispatcher does not check the PathInfo or MethodType of the default action item.
The dispatcher does not even check the Enabled property of the default action item.
Thus, you can make sure the default action item is only called at the very end by
setting its Enabled property to False.
The default action item should be prepared to handle any request that is
encountered, even if it is only to return an error code indicating an invalid URI or
MethodType. If the default action item does not handle the request, no response is sent
to the Web client.
Caution Changing the Default property of an action during execution may cause unexpected
results for the current request. If the Default property of an action that has already
been triggered is set to True, that action will not be reevaluated and the dispatcher
will not trigger that action when it reaches the end of the action list.
The Method property may indicate any other method that the Web client requests of
the server.
The Web server application does not need to provide a response for every possible
value of Method. The HTTP standard does require that it service both GET and HEAD
requests, however.
The MethodType property indicates whether the value of Method is GET (mtGet),
HEAD (mtHead), POST (mtPost), PUT (mtPut) or some other string (mtAny). The
dispatcher matches the value of the MethodType property with the MethodType of each
action item.
HTML templates
An HTML template is a sequence of HTML commands and HTML-transparent tags.
An HTML-transparent tag has the form
<#TagName Param1=Value1 Param2=Value2 ...>
The angle brackets (< and >) define the entire scope of the tag. A pound sign (#)
immediately follows the opening angle bracket (<) with no spaces separating it from
the angle bracket. The pound sign identifies the string to the page producer as an
HTML-transparent tag. The tag name immediately follows the pound sign with no
spaces separating it from the pound sign. The tag name can be any valid identifier
and identifies the type of conversion the tag represents.
Following the tag name, the HTML-transparent tag can optionally include
parameters that specify details of the conversion to be performed. Each parameter is
of the form ParamName=Value, where there is no space between the parameter name,
the equals symbol (=) and the value. The parameters are separated by whitespace.
The angle brackets (< and >) make the tag transparent to HTML browsers that do not
recognize the #TagName construct.
While you can create your own HTML-transparent tags to represent any kind of
information processed by your page producer, there are several predefined tag
names associated with values of the TTag data type. These predefined tag names
correspond to HTML commands that are likely to vary over response messages. They
are listed in the following table:
Any other tag name is associated with tgCustom. The page producer supplies no
built-in processing of the predefined tag names. They are simply provided to help
applications organize the conversion process into many of the more common tasks.
Note The predefined tag names are case insensitive.
The general form of the calendar is stored in a template file. It looks like this:
<HTML>
<Head></HEAD>
<BODY>
<#MonthlyImage> <#TitleLine><#MainBody>
</BODY>
</HTML>
The OnHTMLTag event handler of the first page producer looks up the month and
year from the request message. Using that information and the template file, it does
the following:
• Replaces <#MonthlyImage> with <#Image Month=January Year=2000>.
• Replaces <#TitleLine> with <#Calendar Month=December Year=1999
Size=Small> January 2000 <#Calendar Month=February Year=2000 Size=Small>.
• Replaces <#MainBody> with <#Calendar Month=January Year=2000 Size=Large>.
The OnHTMLTag event handler of the next page producer uses the content produced
by the first page producer, and replaces the <#Image Month=January Year=2000> tag
with the appropriate HTML <IMG> tag. Yet another page producer resolves the
#Calendar tags with appropriate HTML tables.
Separate instances of the Web module are generated for each thread at runtime. Each
of those modules contains the session object. Each of those sessions must have a
separate name, so that the threads that handle separate request messages do not
interfere with each other’s database connections. To cause the session objects in each
module to dynamically generate unique names for themselves, set the
AutoSessionName property of the session object. Each session object will dynamically
generate a unique name for itself and set the SessionName property of all datasets in
the module to refer to that unique name. This allows all interaction with the database
for each request thread to proceed without interfering with any of the other request
messages. For more information on sessions, see “Managing database sessions” on
page 26-16
If you are getting the name of the dataset from the HTTP request message, you can’t
bind the fields in the Columns editor at design time. However, you can still
customize the columns programmatically at runtime, by setting up the appropriate
THTMLTableColumn objects and using the methods of the Columns property to add
them to the table. If you do not set up the Columns property, the table producer
creates a default set of columns that match the fields of the dataset and specify no
special display characteristics.
The page module views show the result of server-side scripting without running the
application. You can view the generated HTML in an embedded browser using the
Preview tab, or in text form using the HTML Result tab. The HTML Script tab shows
the page with server-side scripting, which is used to generate HTML for the page.
The following sections of this chapter explain how you use the WebSnap components
to create a Web server application.
Web modules
Web modules are the basic building block of WebSnap applications. Every WebSnap
server application must have at least one Web module. More can be added as
needed. There are four Web module types:
• Web application page modules (TWebAppPageModule objects)
• Web application data modules (TWebAppDataModule objects)
• Web page modules (TWebPageModule objects)
• Web data modules (TWebDataModule objects)
Web page modules and Web application page modules provide content for Web
pages. Web data modules and Web application data modules act as containers for
components shared across your application; they serve the same purpose in
WebSnap applications that ordinary data modules serve in regular applications. You
can include any number of Web page or data modules in your server application.
You may be wondering how many Web modules your application needs. Every
WebSnap application needs one (and only one) Web application module of some
type. Beyond that, you can add as many Web page or data modules as you need.
For Web page modules, a good rule of thumb is one per page style. If you intend to
implement a page that can use the format of an existing page, you may not need a
new Web page module. Modifications to an existing page module may suffice. If the
page is very different from your existing modules, you will probably want to create a
new module. For example, let’s say you are trying to build a server to handle online
catalog sales. Pages which describe available products might all share the same Web
page module, since the pages can all contain the same basic information types using
the same layout. An order form, however, would probably require a different Web
page module, since the format and function of an order form is different from that of
an item description page.
The rules are different for Web data modules. Components that can be shared by
many different Web modules should be placed in a Web data module to simplify
shared access. You will also want to place components that can be used by many
different Web applications in their own Web data module. That way you can easily
share those items among applications. Of course, if neither of these circumstances
applies you might choose not to use Web data modules at all. Use them the same way
you would use regular data modules, and let your own judgment and experience be
your guide.
Web application modules act as containers for components that perform functions
for the application as a whole—such as dispatching requests, managing sessions, and
maintaining user lists. If you are already familiar with the Web Broker architecture,
you can think of Web application modules as being similar to TWebApplication
objects. Web application modules also contain the functionality of a regular Web
module, either page or data, depending on the Web application module type. Your
project can contain only one Web application module. You will never need more than
one anyway; you can add regular Web modules to your server to provide whatever
extra features you want.
Use the Web application module to contain the most basic features of your server
application. If your server will maintain a home page of some sort, you may want to
make your Web application module a TWebAppPageModule instead of a
TWebAppDataModule, so you don’t have to create an extra Web page module for that
page.
Page name
Web page modules have a page name that can be used to reference the page in an
HTTP request or within the application's logic. A factory in the Web page module’s
unit specifies the page name for the Web page module.
Producer template
Most page producers use a template. HTML templates typically contain some static
HTML mixed in with transparent tags or server-side scripting. When page producers
create their content, they replace the transparent tags with appropriate values and
execute the server-side script to produce the HTML that is displayed by a client
browser. (The XSLPageProducer is an exception to this. It uses XSL templates, which
contain XSL rather than HTML. The XSL templates do not support transparent tags
or server-side script.)
Web page modules may have an associated template file that is managed as part of
the unit. A managed template file appears in the Project Manager and has the same
base file name and location as the unit service file. If the Web page module does not
have an associated template file, the properties of the page producer component
specify the template.
Adapters
Adapters define a script interface to your server application. They allow you to insert
scripting languages into a page and retrieve information by making calls from your
script code to the adapters. For example, you can use an adapter to define data fields
to be displayed on an HTML page. A scripted HTML page can then contain HTML
content and script statements that retrieve the values of those data fields. This is
similar to the transparent tags used in Web Broker applications. Adapters also
support actions that execute commands. For example, clicking on a hyperlink or
submitting an HTML form can initiate adapter actions.
Adapters simplify the task of creating HTML pages dynamically. By using adapters
in your application, you can include object-oriented script that supports conditional
logic and looping. Without adapters and server-side scripting, you must write more
of your HTML generation logic in event handlers. Using adapters can significantly
reduce development time.
See “Server-side scripting in WebSnap”on page 35-19 and “Dispatching requests and
responses” on page 35-22 for more details about scripting.
Four types of adapter components can be used to create page content: fields, actions,
errors and records.
Fields
Fields are components that the page producer uses to retrieve data from your
application and to display the content on a Web page. Fields can also be used to
retrieve an image. In this case, the field returns the address of the image written to
the Web page. When a page displays its content, a request is sent to the Web server
application, which invokes the adapter dispatcher to retrieve the actual image from
the field component.
Actions
Actions are components that execute commands on behalf of the adapter. When a
page producer generates its page, the scripting language calls adapter action
components to return the name of the action along with any parameters necessary to
execute the command. For example, consider clicking a button on an HTML form to
delete a row from a table. This returns, in the HTTP request, the action name
associated with the button and a parameter indicating the row number. The adapter
dispatcher locates the named action component and passes the row number as a
parameter to the action.
Errors
Adapters keep a list of errors that occur while executing an action. Page producers
can access this list of errors and display them in the Web page that the application
returns to the end user.
Records
Some adapter components, such as TDataSetAdapter, represent multiple records. The
adapter provides a scripting interface which allows iteration through the records.
Some adapters support paging and iterate only through the records on the current
page.
Page producers
Page producers to generate content on behalf of a Web page module. Page producers
provide the following functionality:
• They generate HTML content.
• They can reference an external file using the HTMLFile property, or the internal
string using the HTMLDoc property.
• When the producers are used with a Web page module, the template can be a file
associated with a unit.
• Producers dynamically generate HTML that can be inserted into the template
using transparent tags or active scripting. Transparent tags can be used in the
same way as WebBroker applications. To learn more about using transparent tags,
see “Converting HTML-transparent tags” on page 34-16. Active scripting support
allows you to embed JScript or VBScript inside the HTML page.
The standard WebSnap method for using page producers is as follows. When you
create a Web page module, you must choose a page producer type in the Web Page
Module wizard. You have many choices, but most WebSnap developers prototype
their pages by using an adapter page producer, TAdapterPageProducer. The adapter
page producer lets you build a prototype Web page using a process analogous to the
standard component model. You add a type of form, an adapter form, to the adapter
page producer. As you need them, you can add adapter components (such as adapter
grids) to the adapter form. Using adapter page producers, you can create Web pages
in a way that is similar to the standard technique for building user interfaces.
There are some circumstances where switching from an adapter page producer to a
regular page producer is more appropriate. For example, part of the function of an
adapter page producer is to dynamically generate script in a page template at
runtime. You may decide that static script would help optimize your server. Also,
users who are experienced with script may want to make changes to the script
directly. In this case, a regular page producer must be used to avoid conflicts
between dynamic and static script. To learn how to change to a regular page
producer, see “Advanced HTML design” on page 35-11
You can also use page producers the same way you would use them in Web Broker
applications, by associating the producer with a Web dispatcher action item. The
advantages of using the Web page module are
• the ability to preview the page’s layout without running the application, and
• the ability to associate a page name with the module, so that the page dispatcher
can call the page producer automatically.
For each of the above components, the component types listed are the default types
shipped with the IDE. Users can create their own component types or use third-party
component types.
• Template: When New File is checked, choose the default content for the template
file from the Template drop-down. The standard template displays the title of the
application, the title of the page, and hyperlinks to published pages. The blank
template creates a blank page.
• Page: Enter a page name and title for the page module. The page name is used to
reference the page in an HTTP request or within the application's logic, whereas
the title is the name that the end user will see when the page is displayed in a
browser.
• Published: Check Published to allow the page to automatically respond to HTTP
requests where the page name matches the pathinfo in the request message.
• Login Required: Check Login Required to require the user to log on before the
page can be accessed.
You have now learned how to begin creating a WebSnap server application. The
WebSnap tutorial describes how to develop a more complete application.
To edit an HTML template, open the unit which contains that template. In the Edit
window, right-click and select html Editor from the context menu. The HTML editor
displays the template for editing in a separate window. The editor runs independent
of the IDE; save the template and close the editor when you’re finished.
After the product has been deployed, you may wish to change the look of the final
HTML pages. Perhaps your software development team is not even responsible for
the final page layout. That duty may belong to a dedicated Web page designer in
your organization, for example. Your page designers may not have any experience
with software development. Fortunately, they don’t have to. They can edit the page
templates at any point in the product development and maintenance cycle, without
ever changing the source code. Thus, WebSnap HTML templates can make server
development and maintenance more efficient.
Login support
Many Web server applications require login support. For example, a server
application may require a user to login before granting access to some parts of a Web
site. Pages may have a different appearance for different users; logins may be
necessary to enable the Web server to send the right pages. Also, because servers
have physical limitations on memory and processor cycles, server applications
sometimes need the ability to limit the number of users at any given time.
With WebSnap, incorporating login support into your Web server application is
fairly simple and straightforward. In this section, you will learn how you can add
login support, either by designing it in from the beginning of your development
process or by retrofitting it onto an existing application.
Figure 35.3 Web App Components dialog with options for login support selected
If you are adding login support to an existing Web application module, you can drop
these components directly into your module from the WebSnap tab of the component
palette. The Web application module will configure itself automatically.
The sessions service and the end user adapter may not require your attention during
your design phase, but the Web user list probably will. You can add default users
and set their read/modify permissions through the WebUserList component editor.
Double-click on the component to display an editor which lets you set usernames,
passwords and access rights. For more information on how to set up access rights,
see “User access rights” on page 35-17.
There are two important properties in the sessions service which you can use to
change default server behavior. The MaxSessions property specifies how many users
can be logged into the system at any given time. The default value for MaxSessions is
-1, which places no software limitation on the number of allowed users. Of course,
your server hardware can still run short of memory or processor cycles for new users,
which can adversely affect system performance. If you are concerned that excessive
numbers of users might overwhelm your server, be sure to set MaxSessions to an
appropriate value.
The DefaultTimeout property specifies the defaut time-out period in minutes. After
DefaultTimeout minutes have passed without any user activity, the session is
automatically terminated. If the user had logged in, all login information is lost. The
default value is 20. You can override the default value in any given session by
changing its TimeoutMinutes property.
Login pages
Of course, your application also needs a login page. Users enter their username and
password for authentication, either while trying to access a restricted page or prior to
such an attempt. The user can also specify which page they receive when
authentication is completed. If the username and password match a user in the Web
user list, the user acquires the appropriate access rights and is forwarded to the page
specified on the login page. If the user isn’t authenticated, the login page may be
redisplayed (the default action) or some other action may occur.
Fortunately, WebSnap makes it easy to create a simple login page using a Web page
module and the adapter page producer. To create a login page, start by creating a
new Web page module. Select File|New|Other to display the New Items dialog box,
then select WebSnap Page Module from the WebSnap tab. Select
AdapterPageProducer as the page producer type. Fill in the other options however
you like. Login tends to be a good name for the login page.
Now you should add the most basic login page fields: a username field, a password
field, a selection box for selecting which page the user receives after logging in, and a
Login button which submits the page and authenticates the user. To add these fields:
1 Add a LoginFormAdapter component (which you can find on the WebSnap tab of
the component palette) to the Web page module you just created.
2 Double-click the AdapterPageProducer component to display a Web page editor
window.
3 Right-click the AdapterPageProducer in the top left pane and select New
Component. In the Add Web Component dialog box, select AdapterForm and click
OK.
4 Add an AdapterFieldGroup to the AdapterForm. (Right-click the AdapterForm in the
top left pane and select New Component. In the Add Web Component dialog box,
select AdapterFieldGroup and click OK.)
5 Now go to the Object Inspector and set the Adapter property of your
AdapterFieldGroup to your LoginFormAdapter. The UserName, Password and
NextPage fields should appear automatically in the Browser tab of the Web page
editor.
So, WebSnap takes care of most of the work in a few simple steps. The login page is
still missing a Login button, which submits the information on the form for
authentication. To add a Login button:
1 Add an AdapterCommandGroup to the AdapterForm.
2 Add an AdapterActionButton to the AdapterCommandGroup.
3 Click on the AdapterActionButton (listed in the upper right pane of the Web page
editor) and change its ActionName property to Login using the Object Inspector.
You can see a preview of your login page in the Web page editor’s Browser tab.
Your Web page editor should look similar to the one shown below.
Figure 35.4 An example of a login page as seen from a Web page editor
If the button doesn’t appear below the AdapterFieldGroup, make sure that the
AdapterCommandGroup is listed below the AdapterFieldGroup on the Web page editor.
If it appears above, select the AdapterCommandGroup and click the down arrow on the
Web page editor. (In general, Web page elements appear vertically in the same order
as they appear in the Web page editor.)
There is one more step necessary before your login page becomes functional. You
need to specify which of your pages is the login page in your end user session
adapter. To do so, select the EndUserSessionAdapter component in your Web
application module. In the Object Inspector, change the LoginPage property to the
name of your login page. Your login page is now enabled for all the pages in your
Web server application.
The scripting used in WebSnap is object-oriented and supports conditional logic and
looping, which can greatly simplify your page generation tasks. For example, your
pages may include a data field that can be edited by some users but not others. With
scripting, conditional logic can be placed in your template pages which displays an
edit box for authorized users and simple text for others. With a tag-based approach,
you must program such decision-making into your HTML generating source code.
Active scripting
WebSnap relies on active scripting to implement server-side script. Active scripting is
a technology created by Microsoft to allow a scripting language to be used with
application objects through COM interfaces. Microsoft ships two active scripting
languages, VBScript and JScript. Support for other languages is available through
third parties.
Script engine
The page producer’s ScriptEngine property identifies the active scripting engine that
evaluates the script within a template. It is set to support JScript by default, but it can
also support other scripting languages (such as VBScript).
Note WebSnap’s adapters are designed to produce JScript. You will need to provide your
own script generation logic for other scripting languages.
Script blocks
Script blocks, which appear in HTML templates, are delimited by <% and %>. The
script engine evaluates any text inside script blocks. The result becomes part of the
page producer's content. The page producer writes text outside of a script block after
translating any embedded transparent tags. Script blocks can also enclose text,
allowing conditional logic and loops to dictate the output of text. For example, the
following JScript block generates a list of five numbered lines:
<ul>
<% for (i=0;i<5;i++) { %>
<li>Item <%=i %></li>
<% } %>
</ul>
(The <%= delimiter is short for Response.Write.)
Creating script
Developers can take advantage of WebSnap features to automatically generate script.
Wizard templates
When creating a new WebSnap application or page module, WebSnap wizards
provide a template field that is used to select the initial content for the page module
template. For example, the Default template generates JScript which, in turn,
displays the application title, page name, and links to published pages.
TAdapterPageProducer
The TAdapterPageProducer builds forms and tables by generating HTML and JScript.
The generated JScript calls adapter objects to retrieve field values, field image
parameters, and action parameters.
Script objects
Script objects are objects that script commands can reference. You make objects
available for scripting by registering an IDispatch interface to the object with the
active scripting engine. The following objects are available for scripting:
Script objects on the current page, which all use the same adapter, can be referenced
without qualification. Script objects on other pages are part of another page module
and have a different adapter object. They can be accessed by starting the script object
reference with the name of the adapter object. For example,
<%= FirstName %>
displays the contents of the FirstName property of the current page’s adapter. The
following script line displays the FirstName property of Adapter1, which is in another
page module:
<%= Adapter1.FirstName %>
For more complete descriptions of script objects, see the ”WebSnap server-side
scripting reference”appendix.
Before handling any requests, the Web application module initializes the Web
context object (of type TWebContext). The Web context object, which is accessed by
calling the global WebContext function, provides global access to variables used by
components servicing the request. For example, the Web context contains the
TWebRequest and TWebResponse objects to represent the HTTP request message and
the response that should be returned.
Dispatcher components
The dispatcher components in the Web application module control the flow of the
application. The dispatchers determine how to handle certain types of HTTP request
messages by examining the HTTP request.
The adapter dispatcher component (TAdapterDispatcher) looks for a content field, or a
query field, that identifies an adapter action component or an adapter image field
component. If the adapter dispatcher finds a component, it passes control to that
component.
The Web dispatcher component (TWebDispatcher) maintains a collection of action
items (of type TWebActionItem) that know how to handle certain types of HTTP
request messages. The Web dispatcher looks for an action item that matches the
request. If it finds one, it passes control to that action item. The Web dispatcher also
looks for auto-dispatching components that can handle the request.
The page dispatcher component (TPageDispatcher) examines the PathInfo property of
the TWebRequest object, looking for the name of a registered Web page module. If the
dispatcher finds a Web page module name, it passes control to that module.
To reduce errors in constructing the HTML page, adapter components indicate the
names and values of HTML elements. Adapter components have methods that
retrieve the names and values of hidden fields that must appear on an HTML form
designed to update adapter fields. Typically, page producers use server-side
scripting to retrieve names and values from adapter components and then uses this
information to generate HTML. For example, the following script constructs an
<IMG> element that references the field called Graphic from Adapter1:
<img src="<%=Adapter1.Graphic.Image.AsHREF%>" alt="<%=Adapter1.Graphic.DisplayText%>">
When the Web application evaluates the script, the HTML src attribute will contain
the information necessary to identify the field and any parameters that the field
component needs to retrieve the image. The resulting HTML might look like this:
<img src="?_lSpecies No=90090&__id=DM.Adapter1.Graphic" alt="(GRAPHIC)">
When the browser sends an HTTP request to retrieve this image to the Web
application, the adapter dispatcher will be able to determine that the Graphic field of
Adapter1, in the module DM, should be called with "Species No=90090" as a
parameter. The adapter dispatcher will call the Graphic field to write an appropriate
HTTP response.
The following script constructs an <A> element referencing the EditRow action of
Adapter1 and creates a hyperlink to a page called Details:
<a href="<%=Adapter1.EditRow.LinkToPage("Details", Page.Name).AsHREF%>">Edit...</a>
The resulting HTML might look like this:
<a href="?&_lSpecies No=90310&__sp=Edit&__fp=Grid&__id=DM.Adapter1.EditRow">Edit...</a>
The end user clicks this hyperlink, and the browser sends an HTTP request. The
adapter dispatcher can determine that the EditRow action of Adapter1, in the module
DM, should be called with the parameter Species No=903010. The adapter dispatcher
also displays the Edit page if the action executes successfully, and displays the Grid
page if action execution fails. It then calls the EditRow action to locate the row to be
edited, and the page named Edit is called to generate an HTTP response. Figure 35.5
shows how adapter components are used to generate content.
Figure 35.5 Generating content flow
WebSnap Application
Template
Action requests
Action request objects are responsible for breaking the HTTP request down into
information needed to execute an adapter action. The types of information needed
for executing an adapter action may include the following request information:
WebSnap Application
Web Page
Module
Image request
The image request object is responsible for breaking the HTTP request down into the
information required by the adapter image field to generate an image. The types of
information represented by the Image Request include:
• Component name - Identifies the adapter field component.
• Image request parameters - Identifies the parameters needed by the adapter
image. For example, the TDataSetAdapterImageField object needs key values to
identify the record that contains the image.
Image response
The image response object contains the TWebResponse object. Adapter fields respond
to an adapter request by writing an image to the Web response object.
Figure 35.7 illustrates how adapter image fields respond to a request.
Figure 35.7 Image response to a request
WebSnap Application
Server
Web
Response
WebSnap Application
Server Web
Page Module
Response
end;
2 Using the editor, add code to the event handler so it looks like the following:
procedure TformMain.IWButton1Click(Sender: TObject);
var s: string;
begin
s := editName.Text;
if Length(s) = 0 then
WebApplication.ShowMessage('Please enter your name.')
else
begin
WebApplication.ShowMessage('Hello, ' + s +'!');
editName.Text := '';
end;
end;
3 Assume your name is World. Type World in the edit box and click the OK button.
A modal dialog box will appear:
Figure 36.5 A greeting from the IntraWeb application
You have now completed a simple IntraWeb application using only forms and
Delphi language code. When you are finished using your application, you can
terminate it by closing the browser window and then closing the IntraWeb
Application Server.
For a more complete example, refer to the document “IntraWeb and WebSnap.pdf”.
This example illustrates a number of typical elements in an XML document. The first
line is a processing instruction called an XML declaration. The XML declaration is
optional but you should include it, because it supplies useful information about the
document. In this example, the XML declaration says that the document conforms to
version 1.0 of the XML specification, that it uses UTF-8 character encoding, and that it
relies on an external file for its document type declaration (DTD).
The second line, which begins with the <!DOCType> tag, is a document type
declaration (DTD). The DTD is how XML defines the structure of the document. It
imposes syntax rules on the elements (tags) contained in the document. The DTD in
this example references another file (sth.dtd). In this case, the structure is defined in
an external file, rather than in the XML document itself. Other types of files that
describe the structure of an XML document include Reduced XML Data (XDR) and
XML schemas (XSD).
The remaining lines are organized into a hierarchy with a single root node (the
<StockHoldings> tag). Each node in this hierarchy contains either a set of child
nodes, or a text value. Some of the tags (the <Stock> and <shares> tags) include
attributes, which are Name=Value pairs that provide details on how to interpret the
tag.
Although it is possible to work directly with the text in an XML document, typically
applications use additional tools for parsing and editing the data. W3C defines a set
of standard interfaces for representing a parsed XML document called the Document
Object Model (DOM). A number of vendors provide XML parsers that implement the
DOM interfaces to let you interpret and edit XML documents more easily.
Delphi provides a number of additional tools for working with XML documents.
These tools use a DOM parser that is provided by another vendor, and make it even
easier to work with XML documents. This chapter describes those tools.
Note In addition to the tools described in this chapter, Delphi comes with tools and
components for converting XML documents to data packets that integrate into the
Delphi database architecture. For details on tools for integrating XML documents
into database applications, see Chapter 32, “Using XML in database applications.”
The XMLDOM unit includes declarations for all the DOM interfaces defined in the
W3C XML DOM level 2 specification. Each DOM vendor provides an
implementation for these interfaces.
• To use one of the DOM vendors for which Delphi already includes support, locate
the unit that represents the DOM implementation. These units end in the string
‘xmldom.’ For example, the unit for the Microsoft implementation is
MSXMLDOM, the unit for the IMB implementation is IBMXMLDOM, and the unit
for the Open XML implementation is OXMLDOM. If you add the unit for the
desired implementation to your project, the DOM implementation is
automatically registered so that it is available to your code.
• To use another DOM implementation, you must create a unit that defines a
descendant of the TDOMVendor class. This unit can then work like one of the
built-in DOM implementations, making your DOM implementation available
when it is included in a project.
• In your descendant class, you must override two methods: the Description
method, which returns a string identifying the vendor, and the
DOMImplementation method, which returns the top-level interface
(IDOMImplementation).
• Your new unit must register the vendor by calling the global
RegisterDOMVendor procedure. This call typically goes in the initialization
section of the unit.
• When your unit is unloaded, it needs to unregister itself to indicate that the
DOM implementation is no longer available. Unregister the vendor by calling
the global UnRegisterDOMVendor procedure. This call typically goes in the
finalization section.
Some vendors supply extensions to the standard DOM interfaces. To allow you to
uses these extensions, the XMLDOM unit also defines an IDOMNodeEx interface.
IDOMNodeEx is a descendant of the standard IDOMNode that includes the most
useful of these extensions.
You can work directly with the DOM interfaces to parse and edit XML documents.
Simply call the GetDOM function to obtain an IDOMImplementation interface, which
you can use as a starting point.
Note For detailed descriptions of the DOM interfaces, see the declarations in the
XMLDOM unit, the documentation supplied by your DOM Vendor, or the
specifications provided on the W3C web site (www.w3.org).
You may find it more convenient to use special XML classes rather than working
directly with the DOM interfaces. These are described below.
Using TXMLDocument
The starting point for working with an XML document is the TXMLDocument
component. The following steps describe how to use TXMLDocument to work
directly with an XML document:
1 Add a TXMLDocument component to your form or data module. TXMLDocument
appears on the Internet page of the Component palette.
2 Set the DOMVendor property to specify the DOM implementation you want the
component to use for parsing and editing an XML document. The Object Inspector
lists all the currently registered DOM vendors. For information on DOM
implementations, see “Using the Document Object Model” on page 37-2.
3 Depending on your implementation, you may want to set the ParseOptions
property to configure how the underlying DOM implementation parses the XML
document.
4 If you are working with an existing XML document, specify the document:
• If the XML document is stored in a file, set the FileName property to the name of
that file.
• You can specify the XML document as a string instead by using the XML
property.
5 Set the Active property to True.
Once you have an active TXMLDocument object, you can traverse the hierarchy of its
nodes, reading or setting their values. The root node of this hierarchy is available as
the DocumentElement property.
</Stock>
<Stock exchange="NYSE">
<name>Pfizer</name>
<price>42.75</price>
<symbol>PFE</symbol>
<shares type="preferred">25</shares>
</Stock>
</StockHoldings>
TXMLDocument would generate a hierarchy of nodes as follows: The root of the
hierarchy would be the StockHoldings node. StockHoldings would have two child
nodes, which correspond to the two Stock tags. Each of these two child nodes would
have four child nodes of its own (name, price, symbol, and shares). Those four child
nodes would act as leaf nodes. The text they contain would appear as the value of
each of the leaf nodes.
Note This division into nodes differs slightly from the way a DOM implementation
generates nodes for an XML document. In particular, a DOM parser treats all tagged
elements as internal nodes. Additional nodes (of type text node) are created for the
values of the name, price, symbol, and shares nodes. These text nodes then appear as
the children of the name, price, symbol, and shares nodes.
Each node is accessed through an IXMLNode interface, starting with the root node,
which is the value of the XML document component’s DocumentElement property.
The Data Binding wizard generates the following interface (along with a class to
implement it):
ICustomer = interface(IXMLNode)
property id: Integer read Getid write Setid;
property name: DOMString read Getname write Setname;
property phone: DOMString read Getphone write Setphone;
function Getid: Integer;
function Getname: DOMString;
function Getphone: DOMString;
procedure Setid(Value: Integer);
procedure Setname(Value: DOMString);
procedure Setphone(Value: DOMString);
end;
Every child node is mapped to a property whose name matches the tag name of the
child node and whose value is the interface of the child node (if the child is an
internal node) or the value of the child node (for leaf nodes). Every node attribute is
also mapped to a property, where the property name is the attribute name and the
property value is the attribute value.
In addition to creating interfaces (and implementation classes) for each tagged
element in the XML document, the wizard creates global functions for obtaining the
interface to the root node. For example, if the XML above came from a document
whose root node had the tag <Customers>, the Data Binding wizard would create
the following global routines:
function GetCustomers(XMLDoc: IXMLDocument): ICustomers;
function LoadCustomers(const FileName: WideString): ICustomers;
function NewCustomers: ICustomers;
The Get... function takes the interface for a TXMLDocument instance . The Load...
function dynamically creates a TXMLDocument instance and loads the specified XML
file as its value before returning an interface pointer. The New... function creates a
new (empty) TXMLDocument instance and returns the interface to the root node.
Using the generated interfaces simplifies your code, because they reflect the structure
of the XML document more directly. For example, instead of writing code such as the
following:
CustIntf := XMLDocument1.DocumentElement;
CustName := CustIntf.ChildNodes[0].ChildNodes['name'].Value;
Your code would look as follows:
CustIntf := GetCustomers(XMLDocument1);
CustName := CustIntf[0].Name;
Note that the interfaces generated by the Data Binding wizard all descend from
IXMLNode. This means you can still add and delete child nodes in the same way as
when you do not use the Data Binding wizard. (See “Adding and deleting child
nodes” on page 37-6.) In addition, when child nodes represent repeating elements
(when all of the children of a node are of the same type), the parent node is given two
methods, Add, and Insert, for adding additional repeats. These methods are simpler
than using AddChild, because you do not need to specify the type of node to create.
6 Once you have specified what code you want the wizard to generate for each
node, move to the third page. This page lets you choose some global options about
how the wizard generates its code and lets you preview the code that will be
generated, and lets you tell the wizard how to save your choices for future use.
• To preview the code the wizard generates, select an interface in the Binding
Summary list and view the resulting interface definition in the Code Preview
control.
• Use the Data Binding Settings to indicate how the wizard should save your
choices. You can store the settings as annotations in a schema file that is
associated with the document (the schema file specified on the first page of the
dialog), or you can name an independent schema file that is used only by the
wizard.
7 When you click Finish, the Data Binding wizard generates a new unit that defines
interfaces and implementation classes for all of the node types in your XML
document. In addition, it creates a global function that takes a TXMLDocument
object and returns the interface for the root node of the data hierarchy.
• Call the generated Load... function to create and bind the TXMLDocument
instance and obtain its interface all in one step. For example, using the same
XML document described above:
var
StockList: IStockListType;
begin
StockList := LoadStockListType('Stocks.xml');
• Call the generated New... function to create the TXMLDocument instance for an
empty document when you want to create all the data in your application:
var
StockList: IStockListType;
begin
StockList := NewStockListType;
2 This interface has properties that correspond to the subnodes of the document’s
root element, as well as properties that correspond to that root element’s
attributes. You can use these to traverse the hierarchy of the XML document,
modify the data in the document, and so on.
3 To save any changes you make using the interfaces generated by the wizard, call
the TXMLDocument component’s SaveToFile method or read its XML property.
Tip If you set the Options property of the TXMLDocument object to include doAutoSave,
then you do not need to explicitly call the SaveToFile method.
38
Using Web Services
Chapter38
Web Services are self-contained modular applications that can be published and
invoked over the Internet. Web Services provide well-defined interfaces that describe
the services provided. Unlike Web server applications that generate Web pages for
client browsers, Web Services are not designed for direct human interaction. Rather,
they are accessed programmatically by client applications.
Web Services are designed to allow a loose coupling between client and server. That
is, server implementations do not require clients to use a specific platform or
programming language. In addition to defining interfaces in a language-neutral
fashion, they are designed to allow multiple communications mechanisms as well.
Support for Web Services is designed to work using SOAP (Simple Object Access
Protocol). SOAP is a standard lightweight protocol for exchanging information in a
decentralized, distributed environment. It uses XML to encode remote procedure
calls and typically uses HTTP as a communications protocol. For more information
about SOAP, see the SOAP specification available at
http://www.w3.org/TR/SOAP/
Note Although the components that support Web Services are built to use SOAP and
HTTP, the framework is sufficiently general that it can be expanded to use other
encoding and communications protocols.
In addition to letting you build SOAP-based Web Service applications (servers),
special components and wizards let you build clients of Web Services that use either
a SOAP encoding or a Document Literal style. The Document Literal style is used in
.Net Web Services.
The components that support Web Services are available on both Windows and
Linux, so you can use them as the basis of cross-platform distributed applications.
There is no special client runtime software to install, as you must have when
distributing applications using CORBA. Because this technology is based on HTTP
messages, it has the advantage that it is widely available on a variety of machines.
Support for Web Services is built on the Web server application architecture (Web
Broker).
Web Service applications publish information on what interfaces are available and
how to call them using a WSDL (Web Service Definition Language) document. On
the server side, your application can publish a WSDL document that describes your
Web Service. On the client side, a wizard or command-line utility can import a
published WSDL document, providing you with the interface definitions and
connection information you need. If you already have a WSDL document that
describes the Web service you want to implement, you can generate the server-side
code as well when importing the WSDL document.
Before a Web Service application can use this invokable interface, it must be
registered with the invocation registry. On the server, the invocation registry entry
allows the invoker component (THTTPSOAPPascalInvoker) to identify an
implementation class to use for executing interface calls. On client applications, an
invocation registry entry allows remote interfaced objects (THTTPRio) to look up
information that identifies the invokable interface and supplies information on how
to call it.
Typically, your Web Service client or server creates the code to define invokable
interfaces either by importing a WSDL document or using the Web Service wizard.
By default, when the WSDL importer or Web Service wizard generates an interface,
the definition is added to a unit with the same name as the Web Service. This unit
includes both the interface definition and code to register the interface with the
invocation registry. The invocation registry is a catalog of all registered invokable
interfaces, their implementation classes, and any functions that create instances of the
implementation classes. It is accessed using the global InvRegistry function, which is
defined in the InvokeRegistry unit.
The definition of the invokable interface is added to the interface section of the unit,
and the code to register the interface goes in the initialization section. The registration
code looks like the following:
initialization
InvRegistry.RegisterInterface(TypeInfo(IEncodeDecode));
end.
Note The implementation section’s uses clause must include the InvokeRegistry unit so
that the call to the InvRegistry function is defined.
The interfaces of Web Services must have a namespace to identify them among all the
interfaces in all possible Web Services. The previous example does not supply a
namespace for the interface. When you do not explicitly supply a namespace, the
invocation registry automatically generates one for you. This namespace is built from
a string that uniquely identifies the application (the AppNamespacePrefix variable), the
interface name, and the name of the unit in which it is defined. If you do not want to
use the automatically-generated namespace, you can specify one explicitly using a
second parameter to the RegisterInterface call.
You can use the same unit file to define an invokable interface for both client and
server applications. If you are doing this, it is a good idea to keep the unit that defines
your invokable interfaces separate from the unit in which you write the classes that
implement them. Because the generated namespace includes the name of the unit in
which the interface is defined, sharing the same unit in both client and server
applications enables them to automatically use the same namespace, as long as they
both use the same value for the AppNamespacePrefix variable.
• Boolean • LongInt
• ByteBool • Int64
• WordBool • Single
• LongBool • Double
• Char • Extended
• Byte • string
• ShortInt • WideString
• SmallInt • Currency
• Word • TDateTime
• Integer • Variant
• Cardinal
You need do nothing special when you use these scalar types on an invokable
interface. If your interface includes any properties or methods that use other types,
however, your application must register those types with the remotable type registry.
For more information on the remotable type registry, see “Registering nonscalar
types” on page 38-5.
Dynamic arrays can be used in invokable interfaces. They must be registered with the
remotable type registry, but this registration happens automatically when you
register the interface. The remotable type registry extracts all the information it needs
from the type information that the compiler generates.
Note You should avoid defining multiple dynamic array types with the same element
type. Because the compiler treats these as transparent types that can be implicitly cast
one to another, it doesn’t distinguish their runtime type information. As a result, the
remotable type registry can’t distinguish the types. This is not a problem for servers,
but can result in clients using the wrong type definition. As an alternate approach,
you can use remotable clases to represent array types.
Note The dynamic array types defined in the Types unit are automatically registered for
you, so your application does not need to add any special registration code for them.
One of these in particular, TByteDynArray, deserves special notice because it maps to
a ‘base64’ block of binary data, rather than mapping each array element separately
the way the other dynamic array types do.
Enumerated types and types that map directly to one of the automatically-marshaled
scalar types can also be used in an invokable interface. As with dynamic array types,
they are automatically registered with the remotable type registry.
For any other types, such as static arrays, structs or records, sets, interfaces, or
classes, you must map the type to a remotable class. A remotable class is a class that
includes runtime type information (RTTI). Your interface must then use the
remotable class instead of the corresponding static array, struct or record, set,
interface, or class. Any remotable classes you create must be registered with the
remotable type registry. As with other types, this registration happens automatically.
The first parameter is class reference for the remotable class that represents the type.
The second is a uniform resource identifier (URI) that uniquely identifies the
namespace of the new class. If you supply an empty string, the registry generates a
URI for you. The third and fourth parameters specify the native and external names
of the data type your class represents. If you omit the fourth parameter, the type
registry uses the third parameter for both values. If you supply an empty string for
both parameters, the registry uses the class name. The fifth parameter indicates
whether the value of class instances can be transmitted as a string. You can optionally
add a sixth parameter (not shown here) to control how multiple references to the
same object instance should be represented in SOAP packets.
After you define a remotable class, it must be registered with the remotable type
registry, as described in “Registering nonscalar types” on page 38-5.This registration
happens automatically on servers when you register the interface that uses the class.
On clients, the code to register the class is generated automatically when you import
the WSDL document that defines the type.
Tip It is a good idea to implement and register TRemotable descendants in a separate unit
from the rest of your server application, including from the units that declare and
register invokable interfaces. In this way, you can use the type for more than one
interface.
Representing attachments
One important TRemotable descendant is TSoapAttachment. This class represents an
attachment. It can be used as the value of a parameter or the return value of a method
on an invokable interface. Attachments are sent with SOAP messages as separate
parts in a multipart form.
When a Web Service application or the client of a Web Service receives an
attachment, it writes the attachment to a temporary file. TSoapAttachment lets you
access that temporary file or save its content to a permanent file or stream. When the
application needs to send an attachment, it creates an instance of TSoapAttachment
and assigns its content by specifying the name of a file, supplying a stream from
which to read the attachment, or providing a string that represents the content of the
attachment.
Because the new class is not scalar, it descends from TRemotable rather than
TRemotableXS. It includes a published property for every property of the string list
you want to communicate between the client and server. Each of these remotable
properties corresponds to a remotable type. In addition, the new remotable class
includes methods to convert to and from a string list.
TRemotableStringList = class(TRemotable)
private
FCaseSensitive: Boolean;
FSorted: Boolean;
FDuplicates: TDuplicates;
FStrings: TStringDynArray;
public
procedure Assign(SourceList: TStringList);
procedure AssignTo(DestList: TStringList);
published
property CaseSensitive: Boolean read FCaseSensitive write FCaseSensitive;
property Sorted: Boolean read FSorted write FSorted;
property Duplicates: TDuplicates read FDuplicates write FDuplicates;
property Strings: TStringDynArray read FStrings write FStrings;
end;
Note that TRemotableStringList exists only as a transport class. Thus, although it has a
Sorted property (to transport the value of a string list’s Sorted property), it does not
need to sort the strings it stores, it only needs to record whether the strings should be
sorted. This keeps the implementation very simple. You only need to implement the
Assign and AssignTo methods, which convert to and from a string list:
procedure TRemotableStringList.Assign(SourceList: TStrings);
var I: Integer;
begin
SetLength(Strings, SourceList.Count);
for I := 0 to SourceList.Count - 1 do
Strings[I] := SourceList[I];
CaseSensitive := SourceList.CaseSensitive;
Sorted := SourceList.Sorted;
Duplicates := SourceList.Duplicates;
end;
procedure TRemotableStringList.AssignTo(DestList: TStrings);
var I: Integer;
begin
DestList.Clear;
DestList.Capacity := Length(Strings);
DestList.CaseSensitive := CaseSensitive;
DestList.Sorted := Sorted;
DestList.Duplicates := Duplicates;
for I := 0 to Length(Strings) - 1 do
DestList.Add(Strings[I]);
end;
Optionally, you may want to register the new remotable class so that you can specify
its class name. If you do not register the class, it is registered automatically when you
register the interface that uses it. Similarly, if you register the class but not the
TDuplicates and TStringDynArray types that it uses, they are registered automatically.
This code shows how to register the TRemotableStringList class and the TDuplicates
type. TStringDynArray is registered automatically because it is one of the built-in
dynamic array types declared in the Types unit.
This registration code goes in the initialization section of the unit where you define
the remotable class:
RemClassRegistry.RegisterXSInfo(TypeInfo(TDuplicates), MyNameSpace, 'duplicateFlag');
RemClassRegistry.RegisterXSClass(TRemotableStringList, MyNameSpace, 'stringList', '',False);
add additional interfaces (or you want to define the interfaces at a later time),
choose File|New|Other, and on the WebServices tab, double-click the SOAP Web
Service interface icon. For details on using the Add New Web Service wizard and
completing the code it generates, see “Adding new Web Services” on page 38-11.
3 If you are implementing a Web Service that has already been defined in a WSDL
document, you can use the WSDL importer to generate the interfaces,
implementation classes, and registration code that your application needs. You
need only fill in the body of the methods the importer generates for the
implementation classes. For details on using the WSDL importer, see “Using the
WSDL importer” on page 38-13.
4 If you want to use the headers in the SOAP envelope that encodes messages
between your application and clients, you can define classes to represent those
headers and write code to process them. This is described in “Defining and using
SOAP headers” on page 38-16.
5 If your application raises an exception when attempting to execute a SOAP
request, the exception will be automatically encoded in a SOAP fault packet,
which is returned instead of the results of the method call. If you want to convey
more information than a simple error message, you can create your own exception
classes that are encoded and passed to the client. This is described in “Creating
custom exception classes for Web Services” on page 38-18.
6 The SOAP Server Application wizard adds a publisher component
(TWSDLHTMLPublish) to new Web Service applications. This enables your
application to publish WSDL documents that describe your Web Service to clients.
For information on the WSDL publisher, see “Generating WSDL documents for a
Web Service application” on page 38-19.
The wizard generates a new Web server application that includes a Web module
which contains three components:
• An invoker component (THTTPSoapPascalInvoker). The invoker converts between
SOAP messages and the methods of any registered invokable interfaces in your
Web Service application.
• A dispatcher component (THTTPSoapDispatcher). The dispatcher automatically
responds to incoming SOAP messages and forwards them to the invoker. You can
use its WebDispatch property to identify the HTTP request messages to which your
application responds. This involves setting the PathInfo property to indicate the
path portion of any URL directed to your application, and the MethodType
property to indicate the method header for request messages.
• A WSDL publisher (TWSDLHTMLPublish). The WSDL publisher publishes a
WSDL document that describes your interfaces and how to call them. The WSDL
document tells clients that how to call on your Web Service application. For details
on using the WSDL publisher, see “Generating WSDL documents for a
Web Service application” on page 38-19.
The SOAP dispatcher and WSDL publisher are auto-dispatching components. This
means they automatically register themselves with the Web module so that it
forwards any incoming requests addressed using the path information they specify
in their WebDispatch properties. If you right-click on the Web module, you can see
that in addition to these auto-dispatching components, it has a single Web action
item named DefaultHandler.
DefaultHandler is the default action item. That is, if the Web module receives a request
for which it can’t find a handler (can’t match the path information), it forwards that
message to the default action item. DefaultHandler generates a Web page that
describes your Web Service. To change the default action, edit this action item’s
OnAction event handler.
You can tell the invocation registry how to obtain instances of your implementation
class by supplying it with a factory procedure. Even if you have an implementation
class that descends from TInvokableClass and that uses the inherited constructor, you
may want to supply a factory procedure anyway. For example, you can use a single
global instance of your implementation class rather than requiring the invocation
registry to create a new instance every time your application receives a call to the
invokable interface.
The factory procedure must be of type TCreateInstanceProc. It returns an instance of
your implementation class. If the procedure creates a new instance, the
implementation object should free itself when the reference count on its interface
drops to zero, as the invocation registry does not explicitly free object instances. The
following code illustrates another approach, where the factory procedure returns a
single global instance of the implementation class:
procedure CreateEncodeDecode(out obj: TObject);
begin
if FEncodeDecode = nil then
begin
FEncodeDecode := TEncodeDecode.Create;
{save a reference to the interface so that the global instance doesn’t free itself }
FEncodeDecodeInterface := FEncodeDecode as IEncodeDecode;
end;
obj := FEncodeDecode; { return global instance }
end;
Note In this example, FEncodeDecodeInterface is a variable of type IEncodeDecode.
You register the factory procedure with an implementation class by supplying it as a
second parameter to the call that registers the class with the invocation registry. First,
locate the call the wizard generated to register the implementation class. This
appears in initialization section of the unit that defines the class. It looks something
like the following:
InvRegistry.RegisterInvokableClass(TEncodeDecode);
Add a second parameter to this call that specifies the factory procedure:
InvRegistry.RegisterInvokableClass(TEncodeDecode, CreateEncodeDecode);
If the WSDL document is on a server that requires authentication (or must be reached
using a proxy server that requires authentication), you need to provide a user name
and password before the wizard can retrieve the WSDL document. To supply this
information, click the Options button and provide the appropriate connection
information.
When you click the Next button, the WSDL importer displays the code it generates
for every definition in the WSDL document that is compatible with the Web Services
framework. That is, it only uses those port types that have a SOAP binding. You can
configure the way the importer generates code by clicking the Options button and
choosing the options you want.
You can use the WSDL importer when writing either a server or a client application.
When writing a server, click the Options button and in the resulting dialog, check the
option that tells the importer to generate server code. When you select this option,
the importer generates implementation classes for the invokable interfaces, and you
need only fill in the bodies of the methods.
Warning If you import a WSDL document to create a server that implements a Web Service
that is already defined, you must still publish your own WSDL document for that
service. There may be minor differences in the imported WSDL document and the
generated implementation. For example, if the WSDL document or XML schema file
uses identifiers that are also keywords, the importer automatically adjusts their
names so that the generated code can compile.
When you click Finish, the importer creates new units that define and register
invokable interfaces for the operations defined in the document, and that define and
register remotable classes for the types that the document defines.
As an alternate approach, you can use the command line WSDL importer instead. For
a server, call the command line importer with the -Os option, as follows:
WSDLIMP -Os -P -V MyWSDLDoc.wsdl
For a client application, call the command line importer without the -Os option:
WSDLIMP -P -V MyWSDLDoc.wsdl
Tip The command line interpreter includes some options that are not available when you
use the WSDL importer in the IDE. For details, see the help for WSDLIMP.
Understanding UDDI
UDDI stands for Universal Description, Discovery, and Integration. It is a generic
format for registering services available through the Web. A number of public
registries exist, which make information about registered services available. Ideally,
these public registries all contain the same information, although there may be minor
discrepancies due to differences in when they update their information.
UDDI registries contain information about more than just Web Services. The format
is sufficiently general that it can be used to describe any business service. Entries in
the UDDI registry are organized hierarchically; first by business, then by type of
service, and lastly by detailed information within a service. This detailed information
is called a TModel. A Web Service, which can include one or more invokable
interfaces, makes up a single TModel. Thus, a single business service can include
multiple Web Services, as well as other business information. Each TModel can
include a variety of information, including contact information for people within the
business, a description of the service, and technical details such as a WSDL
document.
For example, consider a hypothetical business, Widgets Inc. This business might
have two services, widget manufacturing and custom widget design. Under the
widget manufacturing service, you might find two TModels, one for selling parts to
Widgets Inc, and one for ordering widgets. Each of these could be a Web Service.
Under the custom widget design service, you might find a Web Service for obtaining
cost estimates, and another TModel that is not a Web Service, which gives the address
of a Web site for viewing past custom designs.
Use the Get method of ISOAPHeaders to access the headers by name. For example:
TServiceImpl.GetQuote(Symbol: string): Double;
var
Headers: ISOAPHeaers;
H: TAuthHeader;
begin
Headers := Self as ISOAPHeaders;
Headers.Get(AuthHeader, TSOAPHeader(H)); { Retrieve the authentication header }
try
if H = nil then
raise ERemotableException.Create('SOAP header for authentication required');
{ code here to check name and password }
finally
H.Free;
end;
{ now that user is authenticated, look up and return quote }
end;
If you want to include any headers in the response your application generates to a
request message, you can use the same interface. ISOAPHeaders defines a Send
method to add headers to the outgoing response. Simply create an instance of each
header class that corresponds to a header you want to send, set its properties, and
call Send:
TServiceImpl.GetQuote(Symbol: string): Double;
var
Headers: ISOAPHeaers;
H: TQuoteDelay;
TXSDuration Delay;
begin
Headers := Self as ISOAPHeaders;
{ code to lookup the quote and set the return value }
{ this code sets the Delay variable to the time delay on the quote }
H := TQuoteDelay.Create;
H.Delay := Delay;
Headers.OwnsSentHeaders := True;
Headers.Send(H);
end;
If the client also defines and registers your ERemotableException descendant, then
when it receives the SOAP fault packet, it automatically raises an instance of the
appropriate exception class, with all properties set to the values in the SOAP fault
packet.
To allow clients to import information about your ERemotableException descendant,
you must register it with the invocation registry as well as the remotable type
registry. Add a call to the RegisterException method of the object that the global
InvRegistry function returns.
To change the URL, use the WSDL administrator. The first step is to enable the
administrator. You do this by setting the AdminEnabled property of the
TWSDLHTMLPublish component to true. Then, when you use your browser to
display the list of WSDL documents, it includes a button to administer them as well.
Use the WSDL administrator to specify the locations (URLs) where you have
deployed your Web Service application.
Once you have an instance of THTTPRio, provide it with the information it needs to
identify the server interface and locate the server. There are two ways to supply this
information:
• If you do not expect the URL for the Web Service or the namespaces and soap
Action headers it requires to change, you can simply specify the URL for the Web
Service you want to access. THTTPRio uses this URL to look up the definition of
the interface, plus any namespace and header information, based on the
information in the invocation registry. Specify the URL by setting the URL
property to the location of the server:
X.URL := 'http://www.myco.com/MyService.dll/SOAP/IServerInterface';
• If you want to look up the URL, namespace, or Soap Action header from the
WSDL document dynamically at runtime, you can use the WSDLLocation, Service,
and Port properties, and it will extract the necessary information from the WSDL
document:
X.WSDLLocation := 'Cryptography.wsdl';
X.Service := 'Cryptography';
X.Port := 'SoapEncodeDecode';
After specifying how to locate the server and identify the interface, you can obtain an
interface pointer for the invokable interface from the THTTPRio object. You obtain
this interface pointer using the as operator. Simply cast the THTTPRio instance to the
invokable interface:
InterfaceVariable := X as IEncodeDecode;
Code := InterfaceVariable.EncodeValue(5);
When you obtain the interface pointer, THTTPRio creates a vtable for the associated
interface dynamically in memory, enabling you to make interface calls.
THTTPRio relies on the invocation registry to obtain information about the invokable
interface. If the client application does not have an invocation registry, or if the
invokable interface is not registered, THTTPRio can’t build its in-memory vtable.
Warning If you assign the interface you obtain from THTTPRio to a global variable, you must
change that assignment to nil before shutting down your application. For example, if
InterfaceVariable in the previous code sample is a global variable, rather than stack
variable, you must release the interface before the THTTPRio object is freed.
Typically, this code goes in the OnDestroy event handler of the form or data module:
procedure TForm1.FormDestroy(Sender: TObject);
begin
InterfaceVariable := nil;
end;
The reason you must reassign a global interface variable to nil is because THTTPRio
builds its vtable dynamically in memory. That vtable must still be present when the
interface is released. If you do not release the interface along with the form or data
module, it is released when the global variable is freed on shutdown. The memory
for global variables may be freed after the form or data module that contains the
THTTPRio object, in which case the vtable will not be available when the interface is
released.
39
Working with sockets
Chapter39
This chapter describes the socket components that let you create an application that
can communicate with other systems using TCP/IP and related protocols. Using
sockets, you can read and write over connections to other machines without
worrying about the details of the underlying networking software. Sockets provide
connections based on the TCP/IP protocol, but are sufficiently general to work with
related protocols such as User Datagram Protocol (UDP), Xerox Network System
(XNS), Digital’s DECnet, or Novell’s IPX/SPX family.
Using sockets, you can write network servers or client applications that read from
and write to other systems. A server or client application is usually dedicated to a
single service such as Hypertext Transfer Protocol (HTTP) or File Transfer Protocol
(FTP). Using server sockets, an application that provides one of these services can
link to client applications that want to use that service. Client sockets allow an
application that uses one of these services to link to server applications that provide
the service.
Implementing services
Sockets provide one of the pieces you need to write network servers or client
applications. For many services, such as HTTP or FTP, third party servers are readily
available. Some are even bundled with the operating system, so that there is no need
to write one yourself. However, when you want more control over the way the
service is implemented, a tighter integration between your application and the
network communication, or when no server is available for the particular service you
need, then you may want to create your own server or client application. For
example, when working with distributed data sets, you may want to write a layer to
communicate with databases on other systems.
Client connections
Client connections connect a client socket on the local system to a server socket on a
remote system. Client connections are initiated by the client socket. First, the client
socket must describe the server socket to which it wishes to connect. The client socket
then looks up the server socket and, when it locates the server, requests a connection.
The server socket may not complete the connection right away. Server sockets
maintain a queue of client requests, and complete connections as they find time.
When the server socket accepts the client connection, it sends the client socket a full
description of the server socket to which it is connecting, and the connection is
completed by the client.
Listening connections
Server sockets do not locate clients. Instead, they form passive “half connections”
that listen for client requests. Server sockets associate a queue with their listening
connections; the queue records client connection requests as they come in. When the
server socket accepts a client connection request, it forms a new socket to connect to
the client, so that the listening connection can remain open to accept other client
requests.
Server connections
Server connections are formed by server sockets when a listening socket accepts a
client request. A description of the server socket that completes the connection to the
client is sent to the client when the server accepts the connection. The connection is
established when the client socket receives this description and completes the
connection.
Describing sockets
Sockets let your network application communicate with other systems over the
network. Each socket can be viewed as an endpoint in a network connection. It has an
address that specifies:
• The system on which it is running.
• The types of interfaces it understands.
• The port it is using for the connection.
A full description of a socket connection includes the addresses of the sockets on both
ends of the connection. You can describe the address of each socket endpoint by
supplying both the IP address or host and the port number.
Before you can make a socket connection, you must fully describe the sockets that
form its endpoints. Some of the information is available from the system your
application is running on. For instance, you do not need to describe the local IP
address of a client socket—this information is available from the operating system.
The information you must provide depends on the type of socket you are working
with. Client sockets must describe the server they want to connect to. Listening
server sockets must describe the port that represents the service they provide.
Server sockets do not need to specify a host. The local IP address can be read from the
system. If the local system supports more than one IP address, server sockets will
listen for client requests on all IP addresses simultaneously. When a server socket
accepts a connection, the client socket provides the remote IP address.
Client sockets must specify the remote host by providing either its host name or IP
address.
Using ports
While the IP address provides enough information to find the system on the other
end of a socket connection, you also need a port number on that system. Without port
numbers, a system could only form a single connection at a time. Port numbers are
unique identifiers that enable a single system to host multiple connections
simultaneously, by giving each connection a separate port number.
Earlier, we described port numbers as numeric codes for the services implemented
by network applications. This is actually just a convention that allows listening server
connections to make themselves available on a fixed port number so that they can be
found by client sockets. Server sockets listen on the port number associated with the
service they provide. When they accept a connection to a client socket, they create a
separate socket connection that uses a different, arbitrary, port number. This way, the
listening connection can continue to listen on the port number associated with the
service.
Client sockets use an arbitrary local port number, as there is no need for them to be
found by other sockets. They specify the port number of the server socket to which
they want to connect so that they can find the server application. Often, this port
number is specified indirectly, by naming the desired service.
Connecting to clients
A listening server socket component automatically accepts client connection requests
when they are received. You receive notification every time this occurs in an
OnAccept event.
Error events
Client and server sockets generate OnError events when they receive error messages
from the connection. You can write an OnError event handler to respond to these
error messages. The event handler is passed information about
• What socket object received the error notification.
• What the socket was trying to do when the error occurred.
• The error code that was provided by the error message.
You can respond to the error in the event handler, and change the error code to 0 to
prevent the socket from raising an exception.
Client events
When a client socket opens a connection, the following events occur:
• The socket is set up and initialized for event notification.
• An OnCreateHandle event occurs after the server and server socket is created. At
this point, the socket object available through the Handle property can provide
information about the server or client socket that will form the other end of the
connection. This is the first chance to obtain the actual port used for the
connection, which may differ from the port of the listening sockets that accepted
the connection.
• The connection request is accepted by the server and completed by the client
socket.
• When the connection is established, the OnConnect notification event occurs.
Server events
Server socket components form two types of connections: listening connections and
connections to client applications. The server socket receives events during the
formation of each of these connections.
Non-blocking connections
Non-blocking connections read and write asynchronously, so that the transfer of data
does not block the execution of other code in you network application. To create a
non-blocking connection for client or server sockets, set the BlockMode property to
bmNonBlocking.
When the connection is non-blocking, reading and writing events inform your socket
when the socket on the other end of the connection tries to read or write information.
Blocking connections
When the connection is blocking, your socket must initiate reading or writing over
the connection. It cannot wait passively for a notification from the socket connection.
Use a blocking socket when your end of the connection is in charge of when reading
and writing takes place.
For client or server sockets, set the BlockMode property to bmBlocking to form a
blocking connection. Depending on what else your client application does, you may
want to create a new execution thread for reading or writing, so that your application
can continue executing code on other threads while it waits for the reading or writing
over the connection to be completed.
For server sockets, set the BlockMode property to bmBlocking or bmThreadBlocking to
form a blocking connection. Because blocking connections hold up the execution of
all other code while the socket waits for information to be written or read over the
connection, server socket components always spawn a new execution thread for
every client connection when the BlockMode is bmThreadBlocking. When the BlockMode
is bmBlocking, program execution is blocked until a new connection is established.
IV
Developing COM-based applications
Part IV
COM extensions
As COM has evolved, it has been extended beyond the basic COM services. COM
serves as the basis for other technologies such as Automation, ActiveX controls,
Active Documents, and Active Directories. For details on COM extensions, see
“COM extensions” on page 40-10.
In addition, when working in a large, distributed environment, you can create
transactional COM objects. Prior to Windows 2000, these objects were not
architecturally part of COM, but rather ran in the Microsoft Transaction Server (MTS)
environment. With the advent of Windows 2000, this support is integrated into
COM+. Transactional objects are described in detail in Chapter 46, “Creating MTS or
COM+ objects.”
Delphi provides wizards to easily implement applications that incorporate the above
technologies in the Delphi environment. For details, see “Implementing COM objects
with wizards” on page 40-19.
COM interface The way in which an object exposes its services externally to clients.
A COM object provides an interface for each set of related methods
and properties. Note that COM properties are not identical to
properties on VCL objects. COM properties always use read and
write access methods.
COM server A module, either an EXE, DLL, or OCX, that contains the code for a
COM object. Object implementations reside in servers. A COM
object implements one or more interfaces.
COM client The code that calls the interfaces to get the requested services from
the server. Clients know what they want to get from the server
(through the interface); clients do not know the internals of how the
server provides the services. Delphi eases the process in creating a
client by letting you install COM servers (such as a Word document
or PowerPoint slide) as components on the Component Palette.
This allows you to connect to the server and hook its events
through the Object Inspector.
COM interfaces
COM clients communicate with objects through COM interfaces. Interfaces are
groups of logically or semantically related routines which provide communication
between a provider of a service (server object) and its clients. The standard way to
depict a COM interface is shown in Figure 40.1:
Figure 40.1 A COM interface
Interface
COM
Object
For example, every COM object must implement the basic interface, IUnknown.
Through a routine called QueryInterface in IUnknown, clients can request other
interfaces implemented by the server.
Objects can have multiple interfaces, where each interface implements a feature. An
interface provides a way to convey to the client what service it provides, without
providing implementation details of how or where the object provides this service.
COM servers
A COM server is an application or a library that provides services to a client
application or library. A COM server consists of one or more COM objects, where a
COM object is a set of properties and methods.
Clients do not know how a COM object performs its service; the object’s
implementation remains encapsulated. An object makes its services available
through its interfaces as described previously.
In addition, clients do not need to know where a COM object resides. COM provides
transparent access regardless of the object’s location.
When a client requests a service from a COM object, the client passes a class identifier
(CLSID) to COM. A CLSID is simply a GUID that identifies a COM object. COM uses
this CLSID, which is registered in the system registry, to locate the appropriate server
implementation. Once the server is located, COM brings the code into memory, and
has the server instantiate an object instance for the client. This process is handled
indirectly, through a special object called a class factory (based on interfaces) that
creates instances of objects on demand.
As a minimum, a COM server must perform the following:
• Register entries in the system registry that associate the server module with the
class identifier (CLSID).
• Implement a class factory object, which manufactures another object of a
particular CLSID.
• Expose the class factory to COM.
• Provide an unloading mechanism through which a server that is not servicing
clients can be removed from memory.
Note Delphi wizards automate the creation of COM objects and servers as described in
“Implementing COM objects with wizards” on page 40-19.
In-process server A library (DLL) running in the same process space as the client,
for example, an ActiveX control embedded in a Web page
viewed under Internet Explorer or Netscape. Here, the
ActiveX control is downloaded to the client machine and
invoked within the same process as the Web browser.
The client communicates with the in-process server using
direct calls to the COM interface.
Out-of-process server Another application (EXE) running in a different process space
(or local server) but on the same machine as the client. For example, an Excel
spreadsheet embedded in a Word document are two
separate applications running on the same machine.
The local server uses COM to communicate with the client.
Remote server A DLL or another application running on a different machine
from that of the client. For example, a Delphi database
application is connected to an application server on another
machine in the network.
The remote server uses distributed COM (DCOM) to access
interfaces and communicate with the application server.
As shown in Figure 40.3, for in-process servers, pointers to the object interfaces are in
the same process space as the client, so COM makes direct calls into the object
implementation.
Figure 40.3 In-process server
Client Process
In-process
Object
Client
Server
Note This is not always true under COM+. When a client makes a call to an object in a
different context, COM+ intercepts the call so that it behaves like a call to an out-of-
process server (see below), even if the server is in-process. See Chapter 46, “Creating
MTS or COM+ objects” for more information working with COM+.
In-process In-process
COM Stub
Proxy Object
RPC
Client
Remote machine
DCOM
RPC Remote machine
In-process
Stub
Object
The difference between out-of-process and remote servers is the type of interprocess
communication used. The proxy uses COM to communicate with an out-of-process
server, it uses distributed COM (DCOM) to communicate with a remote machine.
DCOM transparently transfers a local object request to the remote object running on
a different machine.
Note For remote procedure calls, DCOM uses the RPC protocol provided by Open
Group’s Distributed Computing Environment (DCE). For distributed security,
DCOM uses the NT LAN Manager (NTLM) security protocol. For directory services,
DCOM uses the Domain Name System (DNS).
For any interface call, the client pushes arguments onto a stack and makes a function
call through the interface pointer. If the call to the object is not in-process, the call gets
passed to the proxy. The proxy packs the arguments into a marshaling packet and
transmits the structure to the remote object. The object’s stub unpacks the packet,
pushes the arguments onto the stack, and calls the object’s implementation. In
essence, the object recreates the client’s call in its own address space.
The type of marshaling that occurs depends on what interface the COM object
implements. Objects can use a standard marshaling mechanism provided by the
IDispatch interface. This is a generic marshaling mechanism that enables
communication through a system-standard remote procedure call (RPC). For details
on the IDispatch interface, see “Automation interfaces” on page 43-13. Even if the
object does not implement IDispatch, if it limits itself to automation-compatible types
and has a registered type library, COM automatically provides marshaling support.
Applications that do not limit themselves to automation-compatible types or register
a type library must provide their own marshaling. Marshaling is provided either
through an implementation of the IMarshal interface, or by using a separately
generated proxy/stub DLL. Delphi does not support the automatic generation of
proxy/stub DLLs.
Aggregation
Sometimes, a server object makes use of another COM object to perform some of its
functions. For example, an inventory management object might make use of a
separate invoicing object to handle customer invoices. If the inventory management
object wants to present the invoice interface to clients, however, there is a problem:
Although a client that has the inventory interface can call QueryInterface to obtain the
invoice interface, when the invoice object was created it did not know about the
inventory management object and can’t return an inventory interface in response to a
call to QueryInterface. A client that has the invoice interface can’t get back to the
inventory interface.
To avoid this problem, some COM objects support aggregation. When the inventory
management object creates an instance of the invoice object, it passes it a copy of its
own IUnknown interface. The invoice object can then use that IUnknown interface to
handle any QueryInterface calls that request an interface, such as the inventory
interface, that it does not support. When this happens, the two objects together are
called an aggregate. The invoice object is called the inner, or contained object of the
aggregate, and the inventory object is called the outer object.
Note In order to act as the outer object of an aggregate, a COM object must create the inner
object using the Windows API CoCreateInstance or CoCreateInstanceEx, passing its
IUnknown pointer as a parameter that the inner object can use for QueryInterface calls.
In order to create an object that can act as the inner object of an aggregate, it must
descend from TContainedObject. When the object is created, the IUnknown interface of
the outer object is passed to the constructor so that it can be used by the
QueryInterface method on calls that the inner object can’t handle.
COM clients
Clients can always query the interfaces of a COM object to determine what it is
capable of providing. All COM objects allow clients to request known interfaces. In
addition, if the server supports the IDispatch interface, clients can query the server for
information about what methods the interface supports. Server objects have no
expectations about the client using its objects. Similarly, clients don’t need to know
how (or even where) an object provides the services; they simply rely on server
objects to provide the services they advertise through their interfaces.
There are two types of COM clients, controllers and containers. Controllers launch
the server and interact with it through its interface. They request services from the
COM object or drive it as a separate process. Containers host visual controls or
objects that appear in the container’s user interface. They use predefined interfaces to
negotiate display issues with server objects. It is impossible to have a container
relationship over DCOM; for example, visual controls that appear in the container's
user interface must be located locally. This is because the controls are expected to
paint themselves, which requires that they have access to local GDI resources.
Delphi makes it easier for you to develop COM clients by letting you import a type
library or ActiveX control into a component wrapper so that server objects look like
other VCL components. For details on this process, see Chapter 42, “Creating COM
clients.”
COM extensions
COM was originally designed to provide core communication functionality and to
enable the broadening of this functionality through extensions. COM itself has
extended its core functionality by defining specialized sets of interfaces for specific
purposes.
The following lists some of the services COM extensions currently provide.
Subsequent sections describe these services in greater detail.
The following diagram illustrates the relationship of the COM extensions and how
they are built upon COM:
Figure 40.5 COM-based technologies
COM objects can be visual or non-visual. Some must run in the same process space as
their clients; others can run in different processes or remote machines, as long as the
objects provide marshaling support. Table 40.1 summarizes the types of COM objects
that you can create, whether they are visual, process spaces they can run in, how they
provide marshaling, and whether they require a type library.
Automation servers
Automation refers to the ability of an application to control the objects in another
application programmatically, like a macro that can manipulate more than one
application at the same time. The server object being manipulated is called the
Automation object, and the client of the Automation object is referred to as an
Automation controller.
Automation can be used on in-process, local, and remote servers.
Automation is characterized by two key points:
• The Automation object defines a set of properties and commands, and describes
their capabilities through type descriptions. In order to do this, it must have a way
to provide information about its interfaces, the interface methods, and those
methods’ arguments. Typically, this information is available in a type library. The
Automation server can also generate type information dynamically when queried
via its IDispatch interface (see following).
• Automation objects make their methods accessible so that other applications can
use them. For this, they implement the IDispatch interface. Through this interface
an object can expose all of its methods and properties. Through the primary
method of this interface, the object’s methods can be invoked, once having been
identified through type information.
Developers often use Automation to create and use non-visual OLE objects that run
in any process space because the Automation IDispatch interface automates the
marshaling process. Automation does, however, restrict the types that you can use.
For a list of types that are valid for type libraries in general, and Automation
interfaces in particular, see “Valid types” on page 41-12.
For information on writing an Automation server, see Chapter 43, “Creating simple
COM servers.”
ActiveX controls
ActiveX is a technology that allows COM components, especially controls, to be more
compact and efficient. This is especially necessary for controls that are intended for
Intranet applications that need to be downloaded by a client before they are used.
ActiveX controls are visual controls that run only as in-process servers, and can be
plugged into an ActiveX control container application. They are not complete
applications in themselves, but can be thought of as prefabricated OLE controls that
are reusable in various applications. ActiveX controls have a visible user interface,
and rely on predefined interfaces to negotiate I/O and display issues with their host
containers.
ActiveX controls make use of Automation to expose their properties, methods, and
events. Features of ActiveX controls include the ability to fire events, bind to data
sources, and support licensing.
One use of ActiveX controls is on a Web site as interactive objects in a Web page. As
such, ActiveX is a standard that targets interactive content for the World Wide Web,
including the use of ActiveX Documents used for viewing non-HTML documents
through a Web browser. For more information about ActiveX technology, see the
Microsoft ActiveX Web site.
Delphi wizards allow you to easily create ActiveX controls. For more information
about creating and using these types of objects, see Chapter 45, “Creating an ActiveX
control.”
Active Documents
Active Documents (previously referred to as OLE documents) are a set of COM
services that support linking and embedding, drag-and-drop, and visual editing.
Active Documents can seamlessly incorporate data or objects of different formats,
such as sound clips, spreadsheets, text, and bitmaps.
Unlike ActiveX controls, Active Documents are not limited to in-process servers; they
can be used in cross-process applications.
Unlike Automation objects, which are almost never visual, Active Document objects
can be visually active in another application. Thus, Active Document objects are
associated with two types of data: presentation data, used for visually displaying the
object on a display or output device, and native data, used to edit an object.
Active Document objects can be document containers or document servers. While
Delphi does not provide an automatic wizard for creating Active Documents, you
can use the VCL class, TOleContainer, to support linking and embedding of existing
Active Documents.
You can also use TOleContainer as a basis for an Active Document container. To
create objects for Active Document servers, use the COM object wizard and add the
appropriate interfaces, depending on the services the object needs to support. For
more information about creating and using Active Document servers, see the
Microsoft ActiveX Web site.
Note While the specification for Active Documents has built-in support for marshaling in
cross-process applications, Active Documents do not run on remote servers because
they use types that are specific to a system on a given machine such as window
handles, menu handles, and so on.
Transactional objects
Delphi uses the term “transactional objects” to refer to objects that take advantage of
the transaction services, security, and resource management supplied by Microsoft
Transaction Server (MTS) (for versions of Windows prior to Windows 2000) or
COM+ (for Windows 2000 and later). These objects are designed to work in a large,
distributed environment.
The transaction services provide robustness so that activities are always completed
or rolled back (the server never partially completes an activity). The security services
allow you to expose different levels of support to different classes of clients. The
resource management allows an object to handle more clients by pooling resources or
keeping objects active only when they are in use. To enable the system to provide
these services, the object must implement the IObjectControl interface. To access the
services, transactional objects use an interface called IObjectContext, which is created
on their behalf by MTS or COM+.
Under MTS, the server object must be built into a library (DLL), which is then
installed in the MTS runtime environment. That is, the server object is an in-process
server that runs in the MTS runtime process space. Under COM+, this restriction
does not apply because all COM calls are routed through an interceptor. To clients,
the difference between MTS and COM+ is transparent.
MTS or COM+ servers group transactional objects that run in the same process space.
Under MTS, this group is called an MTS package, while under COM+ it is called a
COM+ application. A single machine can be running several different MTS packages
(or COM+ applications), where each one is running in a separate process space.
To clients, the transactional object may appear like any other COM server object. The
client need never know about transactions, security, or just-in-time activation unless
it is initiating a transaction itself.
Both MTS and COM+ provide a separate tool for administering transactional objects.
This tool lets you configure objects into packages or COM+ applications, view the
packages or COM+ applications installed on a computer, view or change the
attributes of the included objects, monitor and manage transactions, make objects
available to clients, and so on. Under MTS, this tool is the MTS Explorer. Under
COM+ it is the COM+ Component Manager.
Type libraries
Type libraries provide a way to get more type information about an object than can
be determined from an object’s interface. The type information contained in type
libraries provides needed information about objects and their interfaces, such as
what interfaces exist on what objects (given the CLSID), what member functions exist
on each interface, and what arguments those functions require.
You can obtain type information either by querying a running instance of an object or
by loading and reading type libraries. With this information, you can implement a
client which uses a desired object, knowing specifically what member functions you
need, and what to pass those member functions.
Clients of Automation servers, ActiveX controls, and transactional objects expect
type information to be available. All of Delphi’s wizards generate a type library
automatically, although the COM object wizard makes this optional. You can view or
edit this type information by using the Type Library Editor as described in
Chapter 41, “Working with type libraries.”
This section describes what a type library contains, how it is created, when it is used,
and how it is accessed. For developers wanting to share interfaces across languages,
the section ends with suggestions on using type library tools.
Interface Description
ITypeLib Provides methods for accessing a library of type descriptions.
ITypeLib2 Augments ITypeLib to include support for documentation strings, custom data, and
statistics about the type library.
ITypeInfo Provides descriptions of individual objects contained in a type library. For example,
a browser uses this interface to extract information about objects from the type
library.
ITypeInfo2 Augments ITypeInfo to access additional type library information, including methods
for accessing custom data elements.
ITypeComp Provides a fast way to access information that compilers need when binding to an
interface.
Delphi can import and use type libraries from other applications by choosing
Project|Import Type Library. Most of the VCL classes used for COM applications
support the essential interfaces that are used to store and retrieve type information
from type libraries and from running instances of an object. The VCL class
TTypedComObject supports interfaces that provide type information, and is used as a
foundation for the ActiveX object framework.
The wizards handle many of the tasks involved in creating each type of COM object.
They provide the required COM interfaces for each type of object. As shown in
Figure 40.6, with a simple COM object, the wizard implements the one required
COM interface, IUnknown, which provides an interface pointer to the object.
Figure 40.6 Simple COM object interface
IUnknown
COM
Object
The COM object wizard also provides an implementation for IDispatch if you specify
that you are creating an object that supports an IDispatch descendant.
As shown in Figure 40.7, for Automation and Active Server objects, the wizard
implements IUnknown and IDispatch, which provides automatic marshaling.
Figure 40.7 Automation object interface
IUnknown
Automation
IDispatch Object
As shown in Figure 40.8, for ActiveX control objects and ActiveX forms, the wizard
implements all the required ActiveX control interfaces, from IUnknown, IDispatch,
IOleObject, IOleControl, and so on. For a complete list of interfaces, see the reference
page for TActiveXControl object.
Figure 40.8 ActiveX object interface
IUnknown
IDispatch
IOleObject
ActiveX
IOleControl Control
Object
IOleInPlaceObject
·
·
·
ISpecifyPropertyPages
Table 40.2 lists the various wizards and the interfaces they implement:
Table 40.2 Delphi wizards for implementing COM, Automation, and ActiveX objects
Wizard Implemented interfaces What the wizard does
COM server IUnknown (and IDispatch if • Exports routines that handle server registration, class
you select a default interface registration, loading and unloading the server, and
that descends from object instantiation.
IDispatch) • Creates and manages class factories for objects
implemented on the server.
• Provides registry entries for the object that specify the
selected threading model.
• Declares the methods that implement a selected
interface, providing skeletal implementations for you
to complete.
• Provides a type library, if requested.
• Allows you to select an arbitrary interface that is
registered in the type library and implement it. If you
do this, you must use a type library.
Automation server IUnknown, IDispatch Performs the tasks of a COM server wizard (described
above), plus:
• Implements the interface that you specify, either dual
or dispatch.Provides server-side support for
generating events, if requested.
• Provides a type library automatically.
Active Server Object IUnknown, IDispatch, Performs the tasks of an Automation object wizard
(IASPObject) (described above) and
• optionally generates an .ASP page which can be
loaded into a Web browser. It leaves you in the Type
Library editor so that you can modify the object’s
properties and methods if needed.
• Surfaces the ASP intrinsics as properties so that you
can easily obtain information about the ASP
application and the HTTP messages that launched it.
ActiveX Control IUnknown, IDispatch, Performs the tasks of the Automation server wizard
IPersistStreamInit, (described above), plus:
IOleInPlaceActiveObject, • Generates a CoClass that corresponds to the VCL
IPersistStorage, IViewObject, control on which the ActiveX control is based and
IOleObject, IViewObject2, which implements all the ActiveX interfaces.
IOleControl,
IPerPropertyBrowsing, • Leaves you in the source code editor so that you can
IOleInPlaceObject, modify the implementation class.
ISpecifyPropertyPages
Table 40.2 Delphi wizards for implementing COM, Automation, and ActiveX objects (continued)
Wizard Implemented interfaces What the wizard does
ActiveForm Same interfaces as ActiveX Performs the tasks of the ActiveX control wizard, plus:
Control • Creates a TActiveForm descendant that takes the place
of the pre-existing VCL class in the ActiveX control
wizard. This new class lets you design the Active
Form the same way you design a form in a Windows
application.
Transactional object IUnknown, IDispatch, Adds a new unit to the current project containing the
IObjectControl MTS or COM+ object definition. It inserts proprietary
GUIDs into the type library so that Delphi can install the
object properly, and leaves you in the Type Library
editor so that you can define the interface that the object
exposes to clients. You must install the object separately
after it is built.
Property Page IUnknown, IPropertyPage Creates a new property page that you can design in the
Form Designer.
COM+ Event object None, by default Creates a COM+ event object that you can define using
the Type Library editor. Unlike the other object wizards,
the COM+ Event object wizard does not create an
implementation unit because event objects have no
implementation (it is provided by event subscriber
objects).
Type Library None, by default Creates a new type library and associates it with the
active project.
ActiveX library None, by default Creates a new ActiveX or Com server DLL and exposes
the necessary export functions.
Each wizard generates an implementation unit that implements your COM server
object. The COM server object (the implementation object) descends from one of the
classes in DAX:
Corresponding to the classes in Figure 40.9 is a hierarchy of class factory objects that
handle the creation of these COM objects. The wizard adds code to the initialization
section of your implementation unit that instantiates the appropriate class factory for
your implementation class.
The wizards also generate a type library and its associated unit, which has a name of
the form Project1_TLB. The Project1_TLB unit includes the definitions your
application needs to use the type definitions and interfaces defined in the type
library. For more information on the contents of this file, see “Code generated when
you import type library information” on page 42-5.
You can modify the interface generated by the wizard using the type library editor.
When you do this, the implementation class is automatically updated to reflect those
changes. You need only fill in the bodies of the generated methods to complete the
implementation.
using the Type Library editor. The Type Library editor automatically updates the
implementation unit for your object, so that all you need do is fill in the bodies of the
generated methods.
You can also use the Delphi Type Library Editor in the development of Common
Object Request Broker Architecture (CORBA) applications. With traditional CORBA
tools, you must define object interfaces separately from your application, using the
CORBA Interface Definition Language (IDL). You then run a utility that generates
stub-and-skeleton code from that definition. However, Delphi generates the stub,
skeleton, and IDL for you automatically. You can easily edit your interface using the
Type Library editor and Delphi automatically updates the appropriate source files.
These parts are illustrated in Figure 41.1, which shows the Type Library editor
displaying type information for a COM object named cyc.
Figure 41.1 Type Library editor
Toolbar
Pages
Status bar
Toolbar
The Type Library editor’s toolbar located at the top of the Type Library Editor,
contains buttons that you click to add new objects into your type library.
The first group of buttons let you add elements to the type library. When you click a
toolbar button, the icon for that element appears in the object list pane. You can then
customize its attributes in the right pane. Depending on the type of icon you select,
different pages of information appear to the right.
The following table lists the elements you can add to your type library:
Icon Meaning
An interface description.
A CoClass.
An enumeration.
An alias.
A record.
A union.
A module.
When you select one of the elements listed above in the object list pane, the second
group of buttons displays members that are valid for that element. For example,
when you select Interface, the Method and Property icons in the second box become
enabled because you can add methods and properties to your interface definition.
When you select Enum, the second group of buttons changes to display the Const
member, which is the only valid member for Enum type information.
The following table lists the members that can be added to elements in the object list
pane:
Icon Meaning
A write-only property. (available from the drop-down list on the property button)
A read-write property. (available from the drop-down list on the property button)
A read-only property. (available from the drop-down list on the property button)
The third group of buttons let you refresh, register, or export your type library (save
it as an IDL file), as described in “Saving and registering type library information” on
page 41-25.
Descending from the type library node are the elements in the type library:
Figure 41.2 Object list pane
When you select any of these elements (including the type library itself), the pages of
type information to the right change to reflect only the relevant information for that
element. You can use these pages to edit the definition and properties of the selected
element.
You can manipulate the elements in the object list pane by right clicking to get the
object list pane context menu. This menu includes commands that let you use the
Windows clipboard to move or copy existing elements as well as commands to add
new elements or customize the appearance of the Type Library editor.
Status bar
When editing or saving a type library, syntax, translation errors, and warnings are
listed in the Status bar pane.
For example, if you specify a type that the Type Library editor does not support, you
will get a syntax error. For a complete list of types supported by the Type Library
editor, see “Valid types” on page 41-12.
Note For more detailed information about the various options you can set on type
information pages, see the online Help for the Type Library editor.
You can use each of the pages of type information to view or edit the values it
displays. Most of the pages organize the information into a set of controls so that you
can type in values or select them from a list without requiring that you know the
syntax of the corresponding declarations. This can prevent many small mistakes such
as typographic errors when specifying values from a limited set. However, you may
find it faster to type in the declarations directly. To do this, use the Text page.
All type library elements have a text page that displays the syntax for the element.
This syntax appears in an IDL subset of Microsoft Interface Definition Language, or
Delphi. Any changes you make in other pages of the element are reflected on the text
page. If you add code directly in the text page, changes are reflected in the other
pages of the Type Library editor.
The Type Library editor generates syntax errors if you add identifiers that are
currently not supported by the editor; the editor currently supports only those
identifiers that relate to type library support (not RPC support or constructs used by
the Microsoft IDL compiler for C++ code generation or marshaling support).
Interfaces
An interface describes the methods (and any properties expressed as ‘get’ and ‘set’
functions) for an object that must be accessed through a virtual function table
(vtable). If an interface is flagged as dual, it will inherit from IDispatch, and your
object can provide both early-bound, vtable access, and runtime binding through
OLE automation. By default, the type library flags all interfaces you add as dual.
Interfaces can be assigned members: methods and properties. These appear in the
object list pane as children of the interface node. Properties for interfaces are
represented by the ‘get’ and ‘set’ methods used to read and write the property’s
underlying data. They are represented in the tree view using special icons that
indicate their purpose.
Note When a property is specified as Write By Reference, it means it is passed as a pointer
rather than by value. Some applications, such a Visual Basic, use Write By Reference,
if it is present, to optimize performance. To pass the property only by reference
rather than by value, use the property type By Reference Only. To pass the property by
reference as well as by value, select Read|Write|Write By Ref. To invoke this menu,
go to the toolbar and select the arrow next to the property icon.
Once you add the properties or methods using the toolbar button or the object list
pane context menu, you describe their syntax and attributes by selecting the property
or method and using the pages of type information.
The Attributes page lets you give the property or method a name and dispatch ID (so
that it can be called using IDispatch). For properties, you also assign a type. The
function signature is created using the Parameters page, where you can add, remove,
and rearrange parameters, set their type and any modifiers, and specify function
return types.
Note Members of interfaces that need to raise exceptions should return an HRESULT and
specify a return value parameter (PARAM_RETVAL) for the actual return value.
Declare these methods using the safecall calling convention.
Note that when you assign properties and methods to an interface, they are implicitly
assigned to its associated CoClass. This is why the Type Library editor does not let
you add properties and methods directly to a CoClass.
Dispinterfaces
Interfaces are more commonly used than dispinterfaces to describe the properties
and methods of an object. Dispinterfaces are only accessible through dynamic
binding, while interfaces can have static binding through a vtable.
You can add methods and properties to dispinterfaces in the same way you add them
to interfaces. However, when you create a property for a dispinterface, you can’t
specify a function kind or parameter types.
CoClasses
A CoClass describes a unique COM object that implements one or more interfaces.
When defining a CoClass, you must specify which implemented interface is the
default for the object, and optionally, which dispinterface is the default source for
events. Note that you do not add properties or methods to a CoClass in the Type
Library editor. Properties and methods are exposed to clients by interfaces, which are
associated with the CoClass using the Implements page.
Type definitions
Enumerations, aliases, records, and unions all declare types that can then be used
elsewhere in the type library.
Enums consist of a list of constants, each of which must be numeric. Numeric input is
usually an integer in decimal or hexadecimal format. The base value is zero by
default. You can add constants to your enumeration by selecting the enumeration in
the object list pane and clicking the Const button on the toolbar or selecting New|
Const command from the object list pane context menu.
Note It is strongly recommended that you provide help strings for your enumerations to
make their meaning clearer. The following is a sample entry of an enumeration type
for a mouse button and includes a help string for each enumeration element.
mbLeft = 0 [helpstring 'mbLeft'];
mbRight = 1 [helpstring 'mbRight'];
mbMiddle = 3 [helpstring 'mbMiddle'];
An alias creates an alias (type definition) for a type. You can use the alias to define
types that you want to use in other type info such as records or unions. Associate the
alias with the underlying type definition by setting the Type attribute on the
Attributes page.
A record consists of a list of structure members or fields. A union is a record with
only a variant part. Like a record, a union consists of a list of structure members or
fields. However, unlike the members of records, each member of a union occupies
the same physical address, so that only one logical value can be stored.
Add the fields to a record or union by selecting it in the object list pane and clicking
the field button in the toolbar or right clicking and choosing field from the object list
pane context menu. Each field has a name and a type, which you assign by selecting
the field and assigning values using the Attributes page. Records and unions can be
defined with an optional tag.
Members can be of any built-in type, or you can specify a type using alias before you
define the record.
Modules
A module defines a group of functions, typically a set of DLL entry points. You
define a module by
• Specifying a DLL that it represents on the attributes page.
• Adding methods and constants using the toolbar or the object list pane context
menu. For each method or constant, you must then define its attributes by
selecting the it in the object list pane and setting the values on the Attributes page.
For module methods, you must assign a name and DLL entry point using the
attributes page. Declare the function’s parameters and return type using the
parameters page.
For module constants, use the Attributes page to specify a name, type, and value.
Note The Type Library editor does not generate any declarations or implementation
related to a module. The specified DLL must be created as a separate project.
Valid types
In the Type Library editor, you use different type identifiers, depending on whether
you are working in IDL or Delphi. Specify the language you want to use in the
Environment options dialog.
The following types are valid in a type library for COM development. The
Automation compatible column specifies whether the type can be used by an
interface that has its Automation or Dispinterface flag checked. These are the types
that COM can marshal via the type library automatically.
Automation
Delphi type IDL type variant type compatible Description
Smallint short VT_I2 Yes 2-byte signed integer
Integer long VT_I4 Yes 4-byte signed integer
Single single VT_R4 Yes 4-byte real
Double double VT_R8 Yes 8-byte real
Currency CURRENCY VT_CY Yes currency
TDateTime DATE VT_DATE Yes date
WideString BSTR VT_BSTR Yes binary string
IDispatch IDispatch VT_DISPATCH Yes pointer to IDispatch interface
SCODE SCODE VT_ERROR Yes Ole Error Code
WordBool VARIANT_BOOL VT_BOOL Yes True = -1, False = 0
OleVariant VARIANT VT_VARIANT Yes Ole Variant
IUnknown IUnknown VT_UNKNOWN Yes pointer to IUnknown interface
Shortint byte VT_I1 No 1 byte signed integer
Byte unsigned char VT_UI1 Yes 1 byte unsigned integer
Word unsigned short VT_UI2 Yes* 2 byte unsigned integer
LongWord unsigned long VT_UI4 Yes* 4 byte unsigned integer
Int64 __int64 VT_I8 No 8 byte signed integer
Largeuint uint64 VT_UI8 No 8 byte unsigned integer
SYSINT int VT_INT Yes* system dependent integer
(Win32=Integer)
SYSUINT unsigned int VT_UINT Yes* system dependent unsigned integer
HResult HRESULT VT_HRESULT No 32 bit error code
Pointer VT_PTR -> VT_VOID No untyped pointer
SafeArray SAFEARRAY VT_SAFEARRAY No OLE Safe Array
PChar LPSTR VT_LPSTR No pointer to Char
PWideCha LPWSTR VT_LPWSTR No pointer to WideChar
r
* Word, LongWord, SYSINT, and SYSUINT are Automation-compatible in most applications, but in older
applications they may not be.
The Type Library editor stores type information in the generated type library (.TLB)
file in binary form.
If a parameter type is specified as a Pointer type, the Type Library editor usually
translates that type into a variable parameter. When the type library is saved, the
variable parameter’s associated ElemDesc’s IDL flags are marked IDL_FIN or
IDL_FOUT.
Often, ElemDesc IDL flags are not marked by IDL_FIN or IDL_FOUT when the type
is preceded with a Pointer. Or, in the case of dispinterfaces, IDL flags are not
typically used. In these cases, you may see a comment next to the variable identifier
such as {IDL_None} or {IDL_In}. These comments are used when saving a type
library to correctly mark the IDL flags.
SafeArrays
COM requires that arrays be passed via a special data type known as a SafeArray.
You can create and destroy SafeArrays by calling special COM functions to do so,
and all elements within a SafeArray must be valid automation-compatible types. The
Delphi compiler has built-in knowledge of COM SafeArrays and automatically calls
the COM API to create, copy, and destroy SafeArrays.
In the Type Library editor, a SafeArray must specify the type of its elements. For
example, the following line from the text page declares a method with a parameter
that is a SafeArray with an element type of Integer:
procedure HighLightLines(Lines: SafeArray of Integer);
Note Although you must specify the element type when declaring a SafeArray type in the
Type Library editor, the declaration in the generated _TLB unit does not indicate the
element type.
Attribute specifications
Delphi has been extended to allow type libraries to include attribute specifications.
Attribute specifications appear enclosed in square brackets and separated by
commas. Each attribute specification consists of an attribute name followed (if
appropriate) by a value.
The following table lists the attribute names and their corresponding values.
Interface syntax
The Delphi syntax for declaring interface type information has the form
interfacename = interface[(baseinterface)] [attributes]
functionlist
[propertymethodlist]
end;
For example, the following text declares an interface with two methods and one
property:
Interface1 = interface (IDispatch)
[uuid '{7B5687A1-F4E9-11D1-92A8-00C04F8C8FC4}', version 1.0]
function Calculate(optional seed:Integer=0): Integer;
procedure Reset;
procedure PutRange(Range: Integer) [propput, dispid $00000005]; stdcall;
function GetRange: Integer;[propget, dispid $00000005]; stdcall;
end;
The corresponding syntax in Microsoft IDL is
[uuid '{5FD36EEF-70E5-11D1-AA62-00C04FB16F42}',version 1.0]
interface Interface1 :IDispatch
{
long Calculate([in, optional, defaultvalue(0)] long seed);
void Reset(void);
[propput, id(0x00000005)] void _stdcall PutRange([in] long Value);
[propput, id(0x00000005)] void _stdcall getRange([out, retval] long *Value);
};
CoClass syntax
The Delphi syntax for declaring CoClass type information has the form
classname = coclass(interfacename[interfaceattributes], ...); [attributes];
For example, the following text declares a coclass for the interface IMyInt and
dispinterface DmyInt:
myapp = coclass(IMyInt [source], DMyInt);
[uuid '{2MD36ABF-90E3-11D1-AA75-02C04FB73F42}',
version 1.0,
helpstring ’A class’,
appobject]
The corresponding syntax in Microsoft IDL is
[uuid '{2MD36ABF-90E3-11D1-AA75-02C04FB73F42}',
version 1.0,
helpstring "A class",
appobject]
coclass myapp
{
methods:
[source] interface IMyInt);
dispinterface DMyInt;
};
Enum syntax
The Delphi syntax for declaring Enum type information has the form
enumname = ([attributes] enumlist);
For example, the following text declares an enumerated type with three values:
location = ([uuid '{2MD36ABF-90E3-11D1-AA75-02C04FB73F42}',
helpstring 'location of booth']
Inside = 1 [helpstring 'Inside the pavillion'];
Outside = 2 [helpstring 'Outside the pavillion'];
Offsite = 3 [helpstring 'Not near the pavillion'];);
The corresponding syntax in Microsoft IDL is
[uuid '{2MD36ABF-90E3-11D1-AA75-02C04FB73F42}',
helpstring "location of booth"]
typedef enum
{
[helpstring "Inside the pavillion"] Inside = 1,
[helpstring "Outside the pavillion"] Outside = 2,
[helpstring "Not near the pavillion"] Offsite = 3
} location;
Alias syntax
The Delphi syntax for declaring Alias type information has the form
aliasname = basetype[attributes];
For example, the following text declares DWORD as an alias for integer:
DWORD = Integer [uuid '{2MD36ABF-90E3-11D1-AA75-02C04FB73F42}'];
The corresponding syntax in Microsoft IDL is
[uuid '{2MD36ABF-90E3-11D1-AA75-02C04FB73F42}'] typedef long DWORD;
Record syntax
The Delphi syntax for declaring Record type information has the form
recordname = record [attributes] fieldlist end;
For example, the following text declares a record:
Tasks = record [uuid '{2MD36ABF-90E3-11D1-AA75-02C04FB73F42}',
helpstring 'Task description']
ID: Integer;
StartDate: TDate;
EndDate: TDate;
Ownername: WideString;
Subtasks: safearray of Integer;
end;
The corresponding syntax in Microsoft IDL is
[uuid '{2MD36ABF-90E3-11D1-AA75-02C04FB73F42}',
helpstring "Task description"]
typedef struct
{
long ID;
DATE StartDate;
DATE EndDate;
BSTR Ownername;
SAFEARRAY (int) Subtasks;
} Tasks;
Union syntax
The Delphi syntax for declaring Union type information has the form
unionname = record [attributes]
case Integer of
0: field1;
1: field2;
ƒ
end;
Module syntax
The Delphi syntax for declaring Module type information has the form
modulename = module constants entrypoints end;
For example, the following text declares the type information for a module:
MyModule = module [uuid '{2MD36ABF-90E3-11D1-AA75-02C04FB73F42}',
dllname ‘circle.dll’]
PI: Double = 3.14159;
function area(radius: Double): Double [ entry 1 ]; stdcall;
function circumference(radius: Double): Double [ entry 2 ]; stdcall;
end;
The corresponding syntax in Microsoft IDL is
[uuid '{2MD36ABF-90E3-11D1-AA75-02C04FB73F42}',
dllname("circle.dll")]
module MyModule
{
double PI = 3.14159;
[entry(1)] double _stdcall area([in] double radius);
[entry(2)] double _stdcall circumference([in] double radius);
};
If the interface is associated with a CoClass that was generated by a wizard, you can
tell the Type Library editor to apply your changes to the implementation file by
clicking the Refresh button on the toolbar. The Type Library editor adds new
methods to your implementation class to reflect the new members. You can then
locate the new methods in implementation unit’s source code and fill in their bodies
to complete the implementation.
If you have the Apply Updates dialog enabled, the Type Library editor notifies you of
all changes before updating the sources and warns you of potential problems.
When you make type libraries other than the primary project type library available to
application developers, the type libraries can be in any of the following forms:
• A resource. This resource should have the type TYPELIB and an integer ID. If you
choose to build type libraries with a resource compiler, it must be declared in the
resource (.RC) file as follows:
1 typelib mylib1.tlb
2 typelib mylib2.tlb
There can be multiple type library resources in an ActiveX library. Application
developers use the resource compiler to add the .TLB file to their own ActiveX
library.
• Stand-alone binary files. The .TLB file output by the Type Library editor is a binary
file.
42
Creating COM clients
Chapter42
COM clients are applications that make use of a COM object implemented by another
application or library. The most common types are applications that control an
Automation server (Automation controllers) and applications that host an ActiveX
control (ActiveX containers).
At first glance these two types of COM client are very different: The typical
Automation controller launches an external server EXE and issues commands to
make that server perform tasks on its behalf. The Automation server is usually
nonvisual and out-of-process. The typical ActiveX client, on the other hand, hosts a
visual control, using it much the same way you use any control on the Component
palette. ActiveX servers are always in-process servers.
However, the task of writing these two types of COM client is remarkably similar:
The client application obtains an interface for the server object and uses its properties
and methods. Delphi makes this particularly easy by letting you wrap the server
CoClass in a component on the client, which you can even install on the Component
palette. Samples of such component wrappers appear on two pages of the
Component palette: sample ActiveX wrappers appear on the ActiveX page and
sample Automation objects appear on the Servers page.
When writing a COM client, you must understand the interface that the server
exposes to clients, just as you must understand the properties and methods of a
component from the Component palette to use it in your application. This interface
(or set of interfaces) is determined by the server application, and typically published
in a type library. For specific information on a particular server application’s
published interfaces, you should consult that application’s documentation.
Even if you do not choose to wrap a server object in a component wrapper and install
it on the Component palette, you must make its interface definition available to your
application. To do this, you can import the server’s type information.
Note You can also query the type information directly using COM APIs, but Delphi
provides no special support for this.
Some older COM technologies, such as object linking and embedding (OLE), do not
provide type information in a type library. Instead, they rely on a standard set of
predefined interfaces. These are discussed in “Creating clients for servers that do not
have a type library” on page 42-16.
Note The Import Type Library dialog does not create class wrappers for COM+ event
objects. To write a client that responds to events generated by a COM+ event object,
you must create the event sink programmatically. This process is described in
“Handling COM+ events” on page 42-15.
For more details about the code generated when you import a type library, see “Code
generated when you import type library information” on page 42-5.
In addition, if you installed the generated component wrapper, a server object that
the type library described now resides on the Component palette. You can use the
Object Inspector to set properties or write an event handler for the server. If you add
the component to a form or data module, you can right-click on it at design time to
see its property page (if it supports one).
Note The Servers page of the Component palette contains a number of example
Automation servers that were imported this way for you.
The declarations for generated VCL wrappers appear at the bottom of the interface
section. Component wrappers for ActiveX controls are descendants of TOleControl.
Component wrappers for Automation objects descend from TOleServer. The
generated component wrapper adds the properties, events, and methods exposed by
the CoClass’s interface. You can use this component like any other VCL component.
Warning You should not edit the generated TypeLibName_TLB unit. It is regenerated each time
the type library is refreshed, so any changes will be overwritten.
Note For the most up-to-date information about the generated code, refer to the comments
in the automatically-generated TypeLibName_TLB unit.
ActiveX wrappers
You should always use a component wrapper when hosting ActiveX controls,
because the component wrapper integrates the control’s window into the VCL
framework.
The properties and methods an ActiveX control inherits from TOleControl allow you to
access the underlying interface or obtain information about the control. Most
applications, however, do not need to use these. Instead, you use the imported control
the same way you would use any other VCL control.
Typically, ActiveX controls provide a property page that lets you set their properties.
Property pages are similar to the component editors some components display when
you double-click on them in the form designer. To display an ActiveX control’s
property page, right click and choose Properties.
The way you use most imported ActiveX controls is determined by the server
application. However, ActiveX controls use a standard set of notifications when they
represent the data from a database field. See “Using data-aware ActiveX controls” on
page 42-8 for information on how to host such ActiveX controls.
Servers that have an application object expose a Quit method on that object to let
clients terminate the connection. Quit typically exposes functionality that is
equivalent to using the File menu to quit the application. Code to call the Quit
method is generated in your component’s Disconnect method. If it is possible to call
the Quit method with no parameters, the component wrapper also has an AutoQuit
property. AutoQuit causes your controller to call Quit when the component is freed.
If you want to disconnect at some other time, or if the Quit method requires
parameters, you must call it explicitly. Quit appears as a public method on the
generated component.
5 From the ActiveX tab, drop a TCalendarAXControl object, which you just added to
the Palette, onto the form.
6 Drop a DataSource object from the Data Access tab, and a Table object from the BDE
tab onto the form.
7 Select the DataSource object and set its DataSet property to Table1.
8 Select the Table object and do the following:
• Set the DatabaseName property to DBDEMOS.
• Set the TableName property to EMPLOYEE.DB.
• Set the Active property to true.
9 Select the TCalendarAXControl object and set its DataSource property to
DataSource1.
10 Select the TCalendarAXControl object, right-click, and choose Data Bindings to
invoke the ActiveX Control Data Bindings Editor.
Field Name lists all the fields in the active database. Property Name lists those
properties of the ActiveX Control that can be bound to a database field. The dispID
of the property is in parentheses.
11 Select the HireDate field and the Value property name, choose Bind, and OK.
The field name and property are now bound.
12 From the Data Controls tab, drop a DBGrid object onto the form and set its
DataSource property to DataSource1.
13 From the Data Controls tab, drop a DBNavigator object onto the form and set its
DataSource property to DataSource1.
14 Run the application.
15 Test the application as follows:
With the HireDate field displayed in the DBGrid object, navigate through the
database using the Navigator object. The dates in the ActiveX control change as
you move through the database.
var
FileName: OleVariant;
begin
if OpenDialog1.Execute then
begin
FileName := OpenDialog1.FileName;
WordApplication1.Documents.Open(FileName,
EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam);
WordApplication1.ActiveDocument.PrintOut(
EmptyParam,EmptyParam,EmptyParam,
EmptyParam, EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam);
end;
end;
2 Build and run the program. By clicking the button, Word prompts you for a file to
print.
Connecting to a server
Before you can drive an Automation server from your controller application, you
must obtain a reference to an interface it supports. Typically, you connect to a server
through its main interface. For example, you connect to Microsoft Word through the
WordApplication component.
If the main interface is a dual interface, you can use the creator objects in the
TypeLibName_TLB.pas file. The creator classes have the same name as the CoClass,
with the prefix “Co” added. You can connect to a server on the same machine by
calling the Create method, or a server on a remote machine using the CreateRemote
method. Because Create and CreateRemote are class methods, you do not need an
instance of the creator class to call them.
MyInterface := CoServerClassName.Create;
MyInterface := CoServerClassName.CreateRemote('Machine1');
Create and CreateRemote return the default interface for the CoClass.
If the default interface is a dispatch interface, then there is no Creator class generated
for the CoClass. Instead, you can call the global CreateOleObject function, passing in
the GUID for the CoClass (there is a constant for this GUID defined at the top of the
_TLB unit). CreateOleObject returns an IDispatch pointer for the default interface.
Once you have an instance of your event sink, you must inform the server object of
its existence so that the server can call it. To do this, you call the global
InterfaceConnect procedure, passing it
• The interface to the server that generates events.
• The GUID for the event interface that your event sink handles.
• An IUnknown interface for your event sink.
• A variable that receives a Longint that represents the connection between the
server and your event sink.
{MyInterface is the server interface you got when you connected to the server }
InterfaceConnect(MyInterface, DIID_TheServerEvents,
MyEventSinkObject as IUnknown, cookievar);
After calling InterfaceConnect, your event sink is connected and receives calls from the
server when events occur.
You must terminate the connection before you free your event sink. To do this, call
the global InterfaceDisconnect procedure, passing it all the same parameters except for
the interface to your event sink (and the final parameter is ingoing rather than
outgoing):
InterfaceDisconnect(MyInterface, DIID_TheServerEvents, cookievar);
Note You must be certain that the server has released its connection to your event sink
before you free it. Because you don’t know how the server responds to the disconnect
notification initiated by InterfaceDisconnect, this may lead to a race condition if you
free your event sink immediately after the call. The easiest way to guard against
problems is to have your event sink maintain its own reference count that is not
decremented until the server releases the event sink’s interface.
Once you create the subscriber object, you must subscribe to the event object’s
interface or to individual methods (events) on that interface. There are three types of
subscriptions from which you can choose:
• Transient subscriptions. Like traditional event sinks, transient subscriptions are
tied to the lifetime of an object instance. When the subscriber object is freed, the
subscription ends and COM+ no longer forwards events to it.
• Persistent subscriptions. These are tied to the object class rather than a specific
object instance. When the event occurs, COM locates or launches an instance of the
subscriber object and calls its event handler. In-process objects (DLLs) use this
type of subscription.
• Per-user subscriptions. These subscriptions provide a more secure version of
transient subscriptions. Both the subscriber object and the server object that fires
events must be running under the same user account on the same machine.
Note Objects that subscribe to COM+ events must be installed in a COM+ application.
7 Once the object is bound, you can access its interface using the OleObjectInterface
property. However, because communication with Ole2 objects was based on OLE
verbs, you will most likely want to send commands to the server using the DoVerb
method.
8 When you want to release the server object, call the DestroyObject method.
.NET components are exposed to unmanaged code through the use of proxy objects
called COM Callable Wrappers (CCW). Since COM mechanisms are used to make
the bridge between unmanaged and managed code, you must register the .NET
assemblies that contain components you wish to use. Use the .NET Framework
utiltity called regasm to create the necessary registry entries. The process is similar to
registering any other COM object, and will be covered in more detail later in this
section.
The .NET assembly mscorlib.dll contains the types that are integral to the .NET
Framework. All .NET assemblies must reference the mscorlib assembly, simply
because it provides the core functionality of the .NET Framework on the Microsoft
Windows platform. If you will be using types directly contained in the mscorlib
assembly, then you must run the regasm utility on mscorlib.dll. The Delphi installer
registers the mscorlib assembly for you, if it is not already registered.
.NET components can be deployed in two ways: In a global, shared location called
the Global Assembly Cache (GAC), or together in the same directory as the
executable. Components that are shared among multiple applications should be
deployed in the GAC. Because they are shared, and because of the side-by-side
deployment capabilities of the .NET Framework, assemblies deployed in the GAC
must be given a strong name (i.e. they must be digitally signed). The .NET
Framework contains a utility called sn, which is used to generate the encryption
keys. After the keys have been generated and the component has been built, the
assembly is installed into the global assembly cache using another .NET utility called
gacutil.
A .NET component can also be deployed in the same directory as the unmanaged
executable. In this deployment scenario, the strong key and GAC installation utility
are not required. However, the component must still be registered using the regasm
utility. Unlike an ordinary COM object, registering a .NET component does not make
it accessible to an application outside of the directory where the component is
deployed.
Because you can access a .NET component through late binding, creating a type
library for the component is not strictly required. All that is required is that the
assembly be registered. In fact, unmanaged clients are restricted to late binding by
default. Depending on how the .NET component was designed and built, you might
find only an “empty” class interface if you inspect its type library. Such a type library
is useless, in terms of enabling clients to use vtable binding instead of late binding
through IDispatch.
The following example demonstrates how to late bind to the ArrayList collection
class contained in mscorlib.dll. The mscorlib assembly must be registered prior to
using any type in the manner described here. The Delphi installer automatically
registers mscorlib, but you can run the regasm utility again if need be (e.g. you
unregistered mscorlib with the /u regasm option). Execute the command
regasm mscorlib.dll
in the .NET Framework directory to register the mscorlib assembly.
Note Do not use the /tlb option when registering mscorlib.dll. The .NET Framework
already includes a type library for the mscorlib assembly; you do not need to create a
new one.
The following code is attached to a button click event of a Delphi form:
procedure TForm1.Button1Click(Sender: TObject);
var
capacity: Integer;
item: Variant;
dotNetArrayList:Variant;
begin
{ Create the object }
dotNetArrayList := CreateOleObject('System.Collections.ArrayList');
{ Add an item }
dotNetArrayList.Add('A string item');
[assembly:AssemblyKeyFile("KeyFile.snk")]
namespace InteropTest1 {
public interface MyInterface {
void myMethod1();
void myMethod2(string msg);
}
Once the .NET component has been built, registered, and installed into the GAC (or,
copied to the directory of the unmanaged executable), accessing it in Delphi is the
same as for any other COM object. Open or create your project, and then select
Project|Import Type Library from the menu. Scroll through the list of registered type
libraries until you find the one for your component. You can create a package for the
component and install it on the Component Palette by selecting the Install checkbox.
The type library importer will create a _TLB file to wrap the component, making it
accessible to unmanaged Delphi code through vtable binding.
The Add button of the type library import dialog box will not correctly register a type
library exported for a .NET assembly. Instead, you must always use the regasm
utility on the command line.
The type library importer will automatically create _TLB files (and their
corresponding .dcr and .dcu files) for any .NET assemblies that are referenced in the
imported type library. Importing the type library for the example C# component
above would cause the creation of _TLB, .dcr, and .dcu files for the mscorlib and
System.Windows.Forms assemblies.
The example below demonstrates calling methods on the .NET component, after its
type library has been imported into Delphi. The class and method names come from
the earlier C# example, and the variable MyClass1 is assumed to be previously
declared (e.g. as a member variable of a class, or a local variable of a procedure or
function).
MyClass1 := TMyClass.Create(self);
MyClass1.myMethod1;
MyClass1.myMethod2('Display this message');
MyClass1.Free;
You can optionally add a description of your COM object. This description appears
in the type library for your object.
The Automation object implements a dual interface, which supports both early
(compile-time) binding through the VTable and late (runtime) binding through the
IDispatch interface. For more information, see “Dual interfaces” on page 43-13.
Instancing Meaning
Internal The object can only be created internally. An external application cannot
create an instance of the object directly, although your application can create
the object and pass an interface for it to clients.
Single Instance Allows clients to create only a single instance of the object for each executable
(application), so creating multiple instances results in launching multiple
instances of the application. Each client has its own dedicated instance of the
server application.
Multiple Instances Specifies that multiple clients can create instances of the object in the same
process space.
Table 43.1 lists the different threading models you can specify.
Note Local variables (except those in callbacks) are always safe, regardless of the threading
model. This is because local variables are stored on the stack and each thread has its
own stack. Local variables may not be safe in callbacks when using free-threading.
The threading model you choose in the wizard determines how the object is
registered in the system Registry. You must make sure that your object
implementation adheres to the threading model you have chosen. For general
information on writing thread-safe code, see Chapter 13, “Writing multi-threaded
applications.”
For in-process servers, setting the threading model in the wizard sets the threading
model key in the CLSID registry entry.
Out-of-process servers are registered as EXE, and Delphi initializes COM for the
highest threading model required. For example, if an EXE includes a free-threaded
object, it is initialized for free threading, which means that it can provide the
expected support for any free-threaded or apartment-threaded objects contained in
the EXE. To manually override threading behavior in EXEs, use the CoInitFlags
variable, which is described in the online help.
When a client has implemented the outgoing event interface, it registers its interest in
receiving event notification by querying the server’s IConnectionPointContainer
interface. The IConnectionPointContainer interface returns the server’s
IConnectionPoint interface, which the client then uses to pass the server a pointer to its
implementation of the event handlers (known as a sink).
The server maintains a list of all client sinks and calls methods on them when an
event occurs, as described above.
Automation interfaces
The Automation Object wizard implements a dual interface by default, which means
that the Automation object supports both
• Late binding at runtime, which is through the IDispatch interface. This is
implemented as a dispatch interface, or dispinterface.
• Early binding at compile-time, which is accomplished through directly calling one
of the member functions in the object’s virtual function table (VTable). This is
referred to as a custom interface.
Note Any interfaces generated by the COM object wizard that do not descend from
IDispatch only support VTable calls.
Dual interfaces
A dual interface is a custom interface and a dispinterface at the same time. It is
implemented as a COM VTable interface that derives from IDispatch. For those
controllers that can access the object only at runtime, the dispinterface is available.
For objects that can take advantage of compile-time binding, the more efficient
VTable interface is used.
Dual interfaces offer the following combined advantages of VTable interfaces and
dispinterfaces:
• For VTable interfaces, the compiler performs type checking and provides more
informative error messages.
• For Automation controllers that cannot obtain type information, the dispinterface
provides runtime access to the object.
• For in-process servers, you have the benefit of fast access through VTable
interfaces.
• For out-of-process servers, COM marshals data for both VTable interfaces and
dispinterfaces. COM provides a generic proxy/stub implementation that can
marshal the interface based on the information contained in a type library. For
more information on marshaling, see, “Marshaling data,” on page 43-15.
The following diagram depicts the IMyInterface interface in an object that supports a
dual interface named IMyInterface. The first three entries of the VTable for a dual
interface refer to the IUnknown interface, the next four entries refer to the IDispatch
interface, and the remaining entries are COM entries for direct access to members of
the custom interface.
Figure 43.1 Dual interface VTable
QueryInterface
IUnknown
AddRef
methods
Release
GetIDsOfNames
GetTypeInfo
IDispatch
methods GetTypeInfoCount
Invoke
Method1
IMyInterface Method2
methods
Remaining methods
of IMyInterface
Dispatch interfaces
Automation controllers are clients that use the COM IDispatch interface to access the
COM server objects. The controller must first create the object, then query the object’s
IUnknown interface for a pointer to its IDispatch interface. IDispatch keeps track of
methods and properties internally by a dispatch identifier (dispID), which is a
unique identification number for an interface member. Through IDispatch, a
controller retrieves the object’s type information for the dispatch interface and then
maps interface member names to specific dispIDs. These dispIDs are available at
runtime, and controllers get them by calling the IDispatch method, GetIDsOfNames.
Once it has the dispID, the controller can then call the IDispatch method, Invoke, to
execute the appropriate code (property or method), packaging the parameters for the
property or method into one of the Invoke parameters. Invoke has a fixed compile-time
signature that allows it to accept any number of arguments when calling an interface
method.
The Automation object’s implementation of Invoke must then unpackage the
parameters, call the property or method, and be prepared to handle any errors that
occur. When the property or method returns, the object passes its return value back
to the controller.
This is called late binding because the controller binds to the property or method at
runtime rather than at compile time.
Note When importing a type library, Delphi will query for dispIDs at the time it generates
the code, thereby allowing generated wrapper classes to call Invoke without calling
GetIDsOfNames. This can significantly increase the runtime performance of
controllers.
Custom interfaces
Custom interfaces are user-defined interfaces that allow clients to invoke interface
methods based on their order in the VTable and knowledge of the argument types.
The VTable lists the addresses of all the properties and methods that are members of
the object, including the member functions of the interfaces that it supports. If the
object does not support IDispatch, the entries for the members of the object’s custom
interfaces immediately follow the members of IUnknown.
If the object has a type library, you can access the custom interface through its VTable
layout, which you can get using the Type Library editor. If the object has a type
library and also supports IDispatch, a client can also get the dispIDs of the IDispatch
interface and bind directly to a VTable offset. Delphi’s type library importer
(TLIBIMP) retrieves dispIDs at import time, so clients that use dispinterfaces can
avoid calls to GetIDsOfNames; this information is already in the _TLB unit. However,
clients still need to call Invoke.
Marshaling data
For out-of-process and remote servers, you must consider how COM marshals data
outside the current process. You can provide marshaling:
• Automatically, using the IDispatch interface.
• Automatically, by creating a type library with your server and marking the
interface with the OLE Automation flag. COM knows how to marshal all the
Automation-compatible types in the type library and can set up the proxies and
stubs for you. Some type restrictions apply to enable automatic marshaling.
• Manually by implementing all the methods of the IMarshal interface. This is called
custom marshaling.
Note The first method (using IDispatch) is only available on Automation servers. The
second method is automatically available on all objects that are created by wizards
and which use a type library.
Note One way to bypass the Automation types restrictions is to implement a separate
IDispatch interface and a custom interface. By doing so, you can use the full range of
possible argument types. This means that COM clients have the option of using the
custom interface, which Automation controllers can still access. In this case, though,
you must implement the marshaling code manually.
Custom marshaling
Typically, you use automatic marshaling in out-of-process and remote servers
because it is easier—COM does the work for you. However, you may decide to
provide custom marshaling if you think you can improve marshaling performance.
When implementing your own custom marshaling, you must support the IMarshal
interface. For more information, on this approach, see the Microsoft documentation.
This chapter shows how to create an Active Server Object using the Delphi Active
Server Object wizard. This special Automation control can then be called by an
Active Server Page and supply it with content.
Here are the steps for creating an Active Server Object:
• Create an Active Server Object for the application.
• Define the Active Server Object’s interface.
• Register the Active Server Object.
• Test and debug the application.
• If you are working with IIS5 or later, you use the Object Context type. Under this
model, your object fetches an IObjectContext interface, which it uses to access the
ASP intrinsics. Again, these interfaces are surfaced as properties in the inherited
base class (TASPMTSObject). One advantage of this latter approach is that your
object has access to all of the other services available through IObjectContext. To
access the IObjectContext interface, simply call GetObjectContext (defined in the mtx
unit) as follows:
ObjectContext := GetObjectContext;
For more information about the services available through IObjectContext, see
Chapter 46, “Creating MTS or COM+ objects.”
You can tell the wizard to generate a simple ASP page to host your new Active Server
Object. The generated page provides a minimal script (written in VBScript) that
creates your Active Server Object based on its ProgID, and indicates where you can
call its methods. This script calls Server.CreateObject to launch your Active Server
Object.
Note Although the generated test script uses VBScript, Active Server Pages also can be
written using Jscript.
When you exit the wizard, a new unit is added to the current project that contains the
definition for the Active Server Object. In addition, the wizard adds a type library
project and opens the Type Library editor. Now you can expose the properties and
methods of the interface through the type library as described in “Defining a COM
object’s interface” on page 43-9. As you write the implementation of your object’s
properties and methods, you can take advantage of the ASP intrinsics (described
below) to obtain information about the ASP application and the HTTP messages it
uses to communicate with browsers.
The Active Server Object, like any other Automation object, implements a dual
interface, which supports both early (compile-time) binding through the VTable and
late (runtime) binding through the IDispatch interface. For more information on dual
interfaces, see “Dual interfaces” on page 43-13.
Application
The Application object is accessed through an IApplicationObject interface. It
represents the entire ASP application, which is defined as the set of all .asp files in a
virtual directory and its subdirectories. The Application object can be shared by
multiple clients, so it includes locking support that you should use to prevent thread
conflicts.
IApplicationObject includes the following:
Request
The Request object is accessed through an IRequest interface. It provides information
about the HTTP request message that caused the Active Server Page to be opened.
IRequest includes the following:
Response
The Request object is accessed through an IResponse interface. It lets you specify
information about the HTTP response message that is returned to the client browser.
IResponse includes the following:
Session
The Session object is accessed through the ISessionObject interface. It allows you to
store variables that persist for the duration of a client’s interaction with the ASP
application. That is, these variables are not freed when the client moves from page to
page within the ASP application, but only when the client exits the application
altogether.
ISessionObject includes the following:
Server
The Server object is accessed through an IServer interface. It provides various utilities
for writing your ASP application.
IServer includes the following:
VCL control
The underlying implementation of an ActiveX control in Delphi is a VCL control.
When you create an ActiveX control, you must first design or choose the VCL control
from which you will make your ActiveX control.
The underlying VCL control must be a descendant of TWinControl, because it must
have a window that can be parented by the host application. When you create an
Active form, this object is a descendant of TActiveForm.
Note The ActiveX control wizard lists the available TWinControl descendants from which
you can choose to make an ActiveX control. This list does not include all TWinControl
descendants, however. Some controls, such as THeaderControl, are registered as
incompatible with ActiveX (using the RegisterNonActiveX procedure) and do not
appear in the list.
ActiveX wrapper
The actual COM object is an ActiveX wrapper object for the VCL control. For Active
forms, this class is always TActiveFormControl. For other ActiveX controls, it has a
name of the form TVCLClassX, where TVCLClass is the name of the VCL control class.
Thus, for example, the ActiveX wrapper for TButton would be named TButtonX.
The wrapper class is a descendant of TActiveXControl, which provides support for the
ActiveX interfaces. The ActiveX wrapper inherits this support, which allows it to
forward Windows messages to the VCL control and parent its window in the host
application.
The ActiveX wrapper exposes the VCL control’s properties and methods to clients
via its default interface. The wizard automatically implements most of the wrapper
class’s properties and methods, delegating method calls to the underlying VCL
control. The wizard also provides the wrapper class with methods that fire the VCL
control’s events on clients and assigns these methods as event handlers on the VCL
control.
Type library
The ActiveX control wizards automatically generate a type library that contains the
type definitions for the wrapper class, its default interface, and any type definitions
that these require. This type information provides a way for your control to advertise
its services to host applications. You can view and edit this information using the
Type Library editor. Although this information is stored in a separate, binary type
library file (.TLB extension), it is also automatically compiled into the ActiveX control
DLL as a resource.
Property page
You can optionally give your ActiveX control a property page. The property page
allows the user of a host (client) application to view and edit your control’s
properties. You can group several properties on a page, or use a page to provide a
dialog-like interface for a property. For information on how to create property pages,
see “Creating a property page for an ActiveX control” on page 45-12.
Once you have selected a VCL control, the wizard automatically generates a name for
the CoClass, the implementation unit for the ActiveX wrapper, and the ActiveX
library project. (If you currently have an ActiveX library project open, and it does not
contain a COM+ event object, the current project is automatically used.) You can
change any of these in the wizard (unless you have an ActiveX library project already
open, in which case the project name is not editable).
The wizard always specifies Apartment as the threading model. This is not a problem
if your ActiveX project usually contains only a single control. However, if you add
additional objects to your project, you are responsible for providing thread support.
The wizard also lets you configure various options on your ActiveX control:
• Enabling licensing: You can make your control licensed to ensure that users of the
control can't open it either for design purposes or at runtime unless they have a
license key for the control.
• Including Version information: You can include version information, such as a
copyright or a file description, in the ActiveX control. This information can be
viewed in a browser. Some host clients, such as Visual Basic 4.0, require Version
information or they will not host the ActiveX control. Specify version information
by choosing Project|Options and selecting the Version Info page.
• Including an About box: You can tell the wizard to generate a separate form that
implements an About box for your control. Users of the host application can
display this About box in a development environment. By default, the About box
includes the name of the ActiveX control, an image, copyright information, and an
OK button. You can modify this default form, which the wizard adds to your
project.
When you exit the wizard, it generates the following:
• An ActiveX Library project file, which contains the code required to start an
ActiveX control. You usually don’t change this file.
• A type library, which defines and CoClass for your control, the interface it exposes
to clients, and any type definitions that these require. For more information about
the type library, refer to Chapter 41, “Working with type libraries.”
• An ActiveX implementation unit, which defines and implements the ActiveX
control, a descendant of TActiveXControl. This ActiveX control is a fully-
functioning implementation that requires no additional work on your part.
However, you can modify this class if you want to customize the properties,
methods, and events that the ActiveX control exposes to clients.
• An About box form and unit if you requested them.
• A .LIC file if you enabled licensing.
Typically, you can implement these methods by simply delegating to the associated
VCL control, which can be accessed using the FDelphiControl member of the wrapper
class:
function TButtonX.Get_Caption: WideString;
begin
Result := WideString(FDelphiControl.Caption);
end;
procedure TButtonX.Set_Caption(const Value: WideString);
begin
FDelphiControl.Caption := TCaption(Value);
end;
In some cases, you may need to add code to convert the COM data types to native
Delphi types. The preceding example manages this with typecasting.
Note Because the Automation interface methods are declared safecall, you do not have to
implement COM exception code for these methods—the Delphi compiler handles
this for you by generating code around the body of safecall methods to catch Delphi
exceptions and to convert them into COM error info structures and return codes.
Adding events
The ActiveX control can fire events to its container in the same way that an
automation object fires events to clients. This mechanism is described in “Exposing
events to clients” on page 43-11.
If the VCL control you are using as the basis of your ActiveX control has any
published events, the wizards automatically add the necessary support for managing
a list of client event sinks to your ActiveX wrapper class and define the outgoing
dispinterface that clients must implement to respond to events.
You add events to this outgoing dispinterface. To add an event in the type library
editor, select the event interface and click on the method icon. Then manually add the
list of parameters you want include using the parameter page.
Next, you must declare a method in your wrapper class that is of the same type as the
event handler for the event in the underlying VCL control. This is not generated
automatically, because Delphi does not know which event handler you are using:
procedure KeyPressEvent(Sender: TObject; var Key: Char);
Implement this method to use the host application’s event sink, which is stored in the
wrapper class’s FEvents member:
procedure TButtonX.KeyPressEvent(Sender: TObject; var Key: Char);
var
TempKey: Smallint;
begin
TempKey := Smallint(Key); {cast to an OleAutomation compatible type }
if FEvents <> nil then
FEvents.OnKeyPress(TempKey)
Key := Char(TempKey);
end;
Note When firing events in an ActiveX control, you do not need to iterate through a list of
event sinks because the control only has a single host application. This is simpler
than the process for most Automation servers.
Finally, you must assign this event handler to the underlying VCL control, so that it
is called when the event occurs. You make this assignment in the InitializeControl
method:
procedure TButtonX.InitializeControl;
begin
FDelphiControl := Control as TButton;
FDelphiControl.OnClick := ClickEvent;
FDelphiControl.OnKeyPress := KeyPressEvent;
end;
4 Click the Refresh button on the toolbar to update the type library.
To test a data-binding control, you must register it first.
For example, to convert a TEdit control into a data-bound ActiveX control, create the
ActiveX control from a TEdit and then change the Text property flags to Bindable,
Display Bindable, Default Bindable, and Immediate Bindable. After the control is
registered and imported, it can be used to display data.
The list box allows the user to select from a list of sample masks. The edit controls
allow the user to test the mask before applying it to the ActiveX control. You add
controls to the property page the same as you would to a form.
You can access the ActiveX control using the property page’s OleObject property,
which is an OleVariant that contains the ActiveX control’s interface.
For example, the following code updates the property page’s edit control
(InputMask) with the current value of the ActiveX control’s EditMask property:
procedure TPropertyPage1.UpdatePropertyPage;
begin
{ Update your controls from OleObject }
InputMask.Text := OleObject.EditMask;
end;
Note It is also possible to write a property page that represents more than one ActiveX
control. In this case, you don’t use the OleObject property. Instead, you must iterate
through a list of interfaces that is maintained by the OleObjects property.
4 Set the HTML Dir to the location (as a path) where the HTML file that contains a
reference to the ActiveX control should be placed, for example, C:\INETPUB\
wwwroot. This path can be a standard path name or a UNC path.
5 Set desired Web deployment options as described in “Setting options” on
page 45-16.
6 Choose OK.
7 Choose Project|Web Deploy.
This creates a deployment code base that contains the ActiveX control in an
ActiveX library (with the OCX extension). Depending on the options you specify,
this deployment code base can also contain a cabinet (with the CAB extension) or
information (with the INF extension).
The ActiveX library is placed in the Target Directory you specified in step 2. The
HTML file has the same name as the project file but with the HTM extension. It is
created in the HTML Directory specified in step 4. The HTML file contains a URL
reference to the ActiveX library at the location specified in step 3.
Note If you want to put these files on your Web server, use an external utility such
as ftp.
8 Invoke your ActiveX-enabled Web browser and view the created HTML page.
When this HTML page is viewed in the Web browser, your form or control is
displayed and runs as an embedded application within the browser. That is, the
library runs in the same process as the browser application.
Setting options
Before deploying an ActiveX control, specify the Web deployment options that
should be followed when creating the ActiveX library.
Web deployment options include settings to allow you to set the following:
• Including additional files: If your ActiveX control depends on any packages or
other additional files, you can indicate that these should be deployed with the
project. By default, these files use the same options that you specify for the entire
project, but you can override these settings using the Packages or Additional files
tab. When you include packages or additional files, Delphi creates a file with the
.INF extension (for INFormation). This file specifies the various files that need to
be downloaded and set up for the ActiveX library to run. The syntax of the INF file
allows URLs pointing to packages or additional files to download.
• CAB file compression: A cabinet is a single file, usually with a CAB file extension,
that stores compressed files in a file library. Cabinet compression can dramatically
decrease download time (up to 70%) of a file. During installation, the browser
decompresses the files stored in a cabinet and copies them to the user’s system.
Each file that you deploy can be CAB file compressed. You can specify that the
ActiveX library use CAB file compression on the Project tab of the Web
Deployment options dialog.
• Version information: You can specify that you want version information included
with your ActiveX control. This information is set in the VersionInfo page of the
Project Options dialog. Part of this information is the release number, which you
can have automatically updated every time you deploy your ActiveX control. If
you include additional packages or files, their Version information resources can
get added to the INF file as well.
Depending on whether you include additional files and whether you use CAB file
compression, the resulting ActiveX library may be an OCX file, a CAB file containing
an OCX file, or an INF file. The following table summarizes the results of choosing
different combinations.
By letting MTS or COM+ provide these underlying services, you can concentrate on
developing the specifics for your particular distributed application. Which
technology you choose (MTS or COM+) depends on the server on which you choose
to run your application. To clients, the difference between the two (or, for that matter,
the fact that the server object uses any of these services) is transparent (unless the
client explicitly manipulates transactional services via a special interface).
Managing resources
Transactional objects can be administered to better manage the resources used by
your application. These resources include everything from the memory for the object
instances themselves to any resources they use (such as database connections).
In general, you configure how your application manages resources by the way you
install and configure your object. You set your transactional object so that it takes
advantage of the following:
• Just-in-time activation
• Resource pooling
• Object pooling (COM+ only)
If you want your object to take full advantage of these services, however, it must use
the IObjectContext interface to indicate when resources can safely be released.
Just-in-time activation
The ability for an object to be deactivated and reactivated while clients hold
references to it is called just-in-time activation. From the client's perspective, only a
single instance of the object exists from the time the client creates it to the time it is
finally released. Actually, it is possible that the object has been deactivated and
reactivated many times. By having objects deactivated, clients can hold references to
the object for an extended time without affecting system resources. When an object is
deactivated, all its resources can be released. For example, when an object is
deactivated, it can release its database connection so that other objects can use it.
A transactional object is created in a deactivated state and becomes active upon
receiving a client request. When the transactional object is created, a corresponding
context object is also created. This context object exists for the entire lifetime of the
transactional object, across one or more reactivation cycles. The context object,
accessed by the IObjectContext interface, keeps track of the object during deactivation
and coordinates transactions.
Transactional objects are deactivated as soon as it is safe to do so. This is called as-
soon-as-possible deactivation. A transactional object is deactivated when any of the
following occurs:
• The object requests deactivation with SetComplete or SetAbort: An object calls
the IObjectContext SetComplete method when it has successfully completed its work
and it does not need to save the internal object state for the next call from the
client. An object calls SetAbort to indicate that it cannot successfully complete its
work and its object state does not need to be saved. That is, the object’s state rolls
back to the state prior to the current transaction. Often, objects can be designed to
be stateless, which means that objects deactivate upon return from every method.
• A transaction is committed or aborted: When an object's transaction is committed
or aborted, the object is deactivated. Of these deactivated objects, the only ones
that continue to exist are the ones that have references from clients outside the
transaction. Subsequent calls to these objects reactivate them and cause them to
execute in a new transaction.
• The last client releases the object: Of course, when a client releases the object, the
object is deactivated, and the object context is also released.
Note If you install the transactional object under COM+ from the IDE, you can specify
whether object supports just-in-time activation using the COM+ page of the Type
Library editor. Just select the object (CoClass) in the Type Library editor, go to the
COM+ page, and check or uncheck the box for Just In Time Activation. Otherwise, a
system administrator specifies this attribute using the COM+ Component Manager
or MTS Explorer. (The system administrator can also override any settings you
specify using the Type Library editor.)
Resource pooling
Since idle system resources are freed during a deactivation, the freed resources are
available to other server objects. For example, a database connection that is no longer
used by a server object can be reused by another client. This is called resource
pooling. Pooled resources are managed by a resource dispenser.
A resource dispenser caches resources, so that transactional objects that are installed
together can share them. The resource dispenser also manages nondurable shared
state information. In this way, resource dispensers are similar to resource managers
such as the SQL Server, but without the guarantee of durability.
When writing your transactional object, you can take advantage of two types of
resource dispenser that are provided for you already:
• Database resource dispensers
• Shared Property Manager
Before other objects can use pooled resources, you must explicitly release them.
Objects sharing properties must have the same activation attribute. If two
components in the same package have different activation attributes, they generally
won't be able to share properties. For example, if one component is configured to run
in a client's process and the other is configured to run in a server process, their objects
will usually run in different processes, even though they're in the same MTS package
or COM+ application.
The following example shows how to add code to support the Shared Property
Manager in a transactional object:
procedure Tfoobar.OnDeactivate;
begin
Group := nil;
end;
initialization
TAutoObjectFactory.Create(ComServer, Tfoobar, Class_foobar, ciMultiInstance, tmApartment);
end.
Releasing resources
You are responsible for releasing resources of an object. Typically, you do this by
calling the IObjectContext methods SetComplete and SetAbort after servicing a client
request. These methods release the resources allocated by the resource dispenser.
At this same time, you must release references to all other resources, including
references to other objects (including transactional objects and context objects) and
memory held by any instances of the component (freeing the component).
The only time you would not include these calls is if you want to maintain state
between client calls. For details, see “Stateful and stateless objects” on page 46-11.
Object pooling
Just as you can pool resources, under COM+ you can also pool objects. When an
object is deactivated, COM+ calls the IObjectControl interface method, CanBePooled,
which indicates that the object can be pooled for reuse. If CanBePooled is returns True,
then instead of being destroyed on deactivation, the object is moved to the object
pool. It remains in the object pool for a specified time-out period, during which time
it is available for use to any client requesting it. Only when the object pool is empty is
a new instance of the object created. Objects that return False or that do not support
the IObjectControl interface are destroyed when they are deactivated.
Note To take advantage of object pooling, you must use the “Both” threading model. For
information on threading models, see “Choosing a threading model for a
transactional object” on page 46-17.
Object pooling is not available under MTS. MTS calls CanBePooled as described, but
no pooling takes place. If your object will only run under COM+ and you want to
allow object pooling, set the object’s Pooled property to True.
Even if an object’s CanBePooled method returns True, it can be configured so that
COM+ does not move it to the object pool. If you install the transactional object
under COM+ from the IDE, you can specify whether COM+ tries to pool the object
using the COM+ page of the Type Library editor. Just select the object (CoClass) in
the type library editor, go to the COM+ page, and check or uncheck the box for
Object Pooling. Otherwise, a system administrator specifies this attribute using the
COM+ Component Manager or MTS Explorer.
Similarly, you can configure the time a deactivated object remains in the object pool
before it is freed If you are installing from the IDE, you can specify this duration
using the Creation TimeOut setting on the COM+ page of the type library editor.
Otherwise, a system administrator specifies this attribute using the COM+
Component Manager.
Work from multiple objects can be composed into a single transaction. Allowing an
object to either live in its own transaction or be part of a larger group of objects that
belong to a single transaction is a major advantage of MTS and COM+. It allows an
object to be used in various ways, so that application developers can reuse
application code in different applications without rewriting the application logic. In
fact, developers can determine how objects are used in transactions when installing
the transactional object. They can change the transaction behavior simply by adding
an object to a different MTS package or COM+ application. For details about
installing transactional objects, see “Installing transactional objects” on page 46-26.
Transaction attributes
Every transactional object has a transaction attribute that is recorded in the MTS
catalog or that is registered with COM+.
Delphi lets you set the transaction attribute at design time using the Transactional
Object wizard or the Type Library editor.
Each transaction attribute can be set to these settings:
Requires a Objects must execute within the scope of a transaction. When a new
transaction object is created, its object context inherits the transaction from
the context of the client. If the client does not have a transaction
context, a new one is automatically created.
Requires a new Objects must execute within their own transactions. When a new
transaction object is created, a new transaction is automatically created for
the object, regardless of whether its client has a transaction. An
object never runs inside the scope of its client's transaction.
Instead, the system always creates independent transactions for
the new objects.
Supports Objects can execute within the scope of their client's transactions.
transactions When a new object is created, its object context inherits the
transaction from the context of the client. This enables multiple
objects to be composed in a single transaction. If the client does
not have a transaction, the new context is also created without
one.
Transactions Objects do not run within the scope of transactions. When a new
Ignored object is created, its object context is created without a
transaction, regardless of whether the client has a transaction.
This setting is only available under COM+.
Does not support The meaning of this setting varies, depending on whether you
transactions install the object under MTS or COM+. Under MTS, this setting
has the same meaning as Transactions Ignored under COM+.
Under COM+, not only is the object context created without a
transaction, this setting prevents the object from being activated if
the client has a transaction.
Initiating transactions
Transactions can be controlled in three ways:
• They can be controlled by the client.
Clients can have direct control over transactions by using a transaction context
object (using the ITransactionContext interface).
• They can be controlled by the server.
Servers can control transactions explicitly creating an object context for them.
When the server creates an object this way, the created object is automatically
enlisted in the current transaction.
Transaction time-out
The transaction time-out sets how long (in seconds) a transaction can remain active.
The system automatically aborts transactions that are still alive after the time-out. By
default, the time-out value is 60 seconds. You can disable transaction time-outs by
specifying a value of 0, which is useful when debugging transactional objects.
To set the time-out value on your computer,
1 In the MTS Explorer or COM+ Component Manager, select Computer, My
Computer.
By default, My Computer corresponds to the local computer.
2 Right-click and choose Properties and then choose the Options tab.
The Options tab is used to set the computer's transaction time-out property.
3 Change the time-out value to 0 to disable transaction time-outs.
4 Click OK to save the setting.
For more information on debugging MTS applications, see “Debugging and testing
transactional objects” on page 46-25.
Role-based security
MTS and COM+ provide role-based security where you assign a role to a logical
group of users. For example, a medical information application might define roles for
Physician, X-ray technician, and Patient.
You define authorization for each object and interface by assigning roles. For
example, in the physicians’ medical application, only the Physician may be
authorized to view all medical records; the X-ray Technician may view only X-rays;
and Patients may view only their own medical record.
Typically, you define roles during application development and assign roles for each
MTS package or COM+ Application. These roles are then assigned to specific users
when the application is deployed. Administrators can configure the roles using the
MTS Explorer or COM+ Component Manager.
If you want to control access to blocks of code rather than entire objects, you can
provide more fine-grained security by using the IObjectContext method,
IsCallerInRole. This method only works if security is enabled, which can be checked
by calling the IObjectContext method IsSecurityEnabled. These methods are
automatically added as methods to your transactional object. For example,
if IsSecurityEnabled then {check if security is enabled }
begin
if IsCallerInRole(‘Physician’) then { check caller’s role }
begin
{ execute the call normally }
end
else
{ not a physician, do something appropriate }
end
end
else
{ no security enabled, do something appropriate }
end;
Note For applications that require stronger security, context objects implement the
ISecurityProperty interface, whose methods allow retrieval of the Window’s security
identifier (SID) for the direct caller and creator of the object, as well as the SID for the
clients which are using the object.
3 When implementing your object’s methods, you can use the IObjectContext
interface to manage transactions, persistent state, and security. In addition, if you
are passing object references, you will need to use extra care so that they are
correctly handled. (See “Passing object references” on page 23.)
4 Debug and test the transactional object.
5 Install the transactional object into an MTS package or COM+ application.
6 Administer your objects using the MTS Explorer or COM+ Component Manager.
It is not strictly necessary to use the transactional object wizard. You can convert any
Automation object into a COM+ transactional object (and any in-process Automation
object into an MTS transactional object) by using the COM+ page of the Type Library
editor and then installing the object into an MTS package or COM+ application.
However, the transactional object wizard provides certain benefits:
• It automatically implements the IObjectControl interface, adding OnActivate and
OnDeactivate events to the object so that you can create event handlers that
respond when the object is activated or deactivated.
• It automatically generates an ObjectContext property so that it is easy for your
object to access the IObjectContext methods to control activation and transactions.
Note These threading models are similar to those defined by COM objects. However,
because the MTS and COM+ provide more underlying support for threads, the
meaning of each threading model differs here. Also, the free threading model does
not apply to transactional objects due to the built-in support for activities.
Activities
In addition to the threading model, transactional objects achieve concurrency
through activities. Activities are recorded in an object’s context, and the association
between an object and an activity cannot be changed. An activity includes the
transactional object created by the base client, as well as any transactional objects
created by that object and its descendants. These objects can be distributed across one
or more processes, executing on one or more computers.
For example, a physician’s medical application may have a transactional object to
add updates and remove records to various medical databases, each represented by a
different object. This record object may use other objects as well, such as a receipt
object to record the transaction. This results in several transactional objects that are
either directly or indirectly under the control of a base client. These objects all belong
to the same activity.
MTS or COM+ tracks the flow of execution through each activity, preventing
inadvertent parallelism from corrupting the application state. This feature results in a
single logical thread of execution throughout a potentially distributed collection of
objects. By having one logical thread, applications are significantly easier to write.
When a transactional object is created from an existing context, using either a
transaction context object or an object context, the new object becomes a member of
the same activity. In other words, the new context inherits the activity identifier of
the context used to create it.
Only a single logical thread of execution is allowed within an activity. This is similar
in behavior to a COM apartment threading model, except that the objects can be
distributed across multiple processes. When a base client calls into an activity, all
other requests for work in the activity (such as from another client thread) are
blocked until after the initial thread of execution returns back to the client.
Under MTS, every transactional object belongs to one activity. Under COM+, you can
configure the way the object participates in activities by setting the call
synchronization. The following options are available:
The following figure depicts the interaction between publishers, subscribers, and the
COM+ Catalog.
Figure 46.1 The COM+ Events system
Event
Event Class Subscription Interface
Publisher
Instantiates Delivers
Generate
event
event
Subscriber
COM+ Events
Event Object
Event
Interface
When you exit, the wizard creates a project containing a type library that defines
your event object and its interface. Use the Type Library editor to define the methods
of that interface. These methods are the event handlers that clients implement to
respond to events.
The Event object project includes the project file, _ATL unit to import the ATL
template classes, and the _TLB unit to define the type library information. It does not
include an implementation unit, however, because COM+ event objects have no
implementation. The implementation of the interface is the responsibility of the
client. When your server object calls a COM+ event object, COM+ intercepts the call
and dispatches it to registered clients. Because COM+ event objects require no
implementation object, all you need to do after defining the object’s interface in the Type
Library editor is compile the project and install it with COM+
COM+ places certain restrictions on the interfaces of event objects. The interface you
define in the Type Library editor for your event object must obey the following rules:
• The event object’s interface must derive from IDispatch.
• All method names must be unique across all interfaces of the event object.
• All methods on the event object’s interface must return an HRESULT value.
• The modifier for all parameters of methods must be blank.
Under MTS, you can pass object references, (for example, for use as a callback) only
in the following ways:
• Through return from an object creation interface, such as CoCreateInstance (or its
equivalent), ITransactionContext.CreateInstance, or IObjectContext.CreateInstance.
• Through a call to QueryInterface.
• Through a method that has called SafeRef to obtain the object reference.
An object reference that is obtained in the above ways is called a safe reference.
Methods invoked using safe references are guaranteed execute within the correct
context.
The MTS runtime environment requires calls to use safe references so that it can
manage context switches and allows transactional objects to have lifetimes that are
independent of client references. Safe references are not necessary under COM+.
Callbacks
Objects can make callbacks to clients and to other transactional objects. For example,
you can have an object that creates another object. The creating object can pass a
reference of itself to the created object; the created object can then use this reference
to call the creating object.
If you choose to use callbacks, note the following restrictions:
• Calling back to the base client or another package requires access-level security on
the client. Additionally, the client must be a DCOM server.
• Intervening firewalls may block calls back to the client.
• Work done on the callback executes in the environment of the object being called.
It may be part of the same transaction, a different transaction, or no transaction.
• Under MTS, the creating object must call SafeRef and pass the returned reference to
the created object in order to call back to itself.
3 Change the time-out value to 0, which shuts down the server as soon as no longer
has a client to service.
4 Click OK to save the setting.
Note When testing outside the MTS environment, you do not reference the ObjectProperty
of TMtsObject directly. The TMtsObject implements methods such as SetComplete and
SetAbort that are safe to call when the object context is nil.
Index I-1
persistent properties 45-12 ADO connections 27-3 to 27-9
property pages 42-6, 45-3, 45-12 to 45-14 asynchronous 27-5
registering 45-15 connecting to data stores 27-3 to 27-7
threading model 45-5 events 27-8 to 27-9
type libraries 40-17, 45-3 executing commands 27-6
using Automation-compatible types 45-4, 45-8 timing out 27-6
Web applications 40-14, 45-1, 45-15 to 45-17 ADO datasets 27-9 to 27-17
Web deployment 45-15 to 45-17 asynchronous fetching 27-12
wizard 45-4 to 45-5 batch updates 27-13 to 27-15
ActiveX page (Component palette) 42-4 connecting 27-10 to 27-11
activities, transactional objects 46-18 to 46-19 data files 27-15 to 27-16
ActnList unit 9-31 index-based searches 24-28
adapter dispatcher requests 35-25 ADO objects 27-1
adapter dispatchers 35-9, 35-23 Connection object 27-5
AdapterPageProducer 35-10 RDS DataSpace 27-17
adapters 35-2, 35-5 to 35-6 Recordset 27-9, 27-11
Add Fields dialog box 25-5 ADO page (Component palette) 19-1, 27-2
Add method ADT fields 25-23, 25-24 to 25-26
menus 9-44 displaying 20-22, 25-24
persistent columns 20-19 flattening 20-22
strings 5-21 persistent fields 25-25
Add New Web Service wizard 38-11 ADTG files 27-15
Add to Interface command 31-16 AfterApplyUpdates event 29-32, 30-8
Add To Repository command 8-22 AfterCancel event 24-21
AddAlias method 26-25 AfterClose event 24-5
AddFieldDef method 24-39 AfterConnect event 23-3, 31-28
AddFontResource function 18-14 AfterDelete event 24-20
AddIndex method 29-8 AfterDisconnect event 23-4, 31-28
AddIndexDef method 24-39 AfterDispatch event 34-6, 34-9
AddObject method 5-22 AfterEdit event 24-18
AddParam method 24-54 AfterGetRecords event 30-8
AddPassword method 26-22 AfterInsert event 24-19
_AddRef method 4-14, 4-18, 4-20 AfterOpen event 24-4
AddRef method 40-4 AfterPost event 24-21
Address property, TSocketConnection 31-24 AfterScroll event 24-6
addresses, socket connections 39-4 AggFields property 29-14
AddStandardAlias method 26-25 aggregate fields 25-6, 29-14
AddStrings method 5-21 defining 25-10 to 25-11
ADO 19-1, 24-2, 27-1, 27-2, 27-3 displaying 25-11
components 27-1 to 27-21 Aggregates property 29-12, 29-13
overview 27-2 aggregation
data stores 27-3, 27-4 client datasets 29-11 to 29-14
deployment 18-7 COM 40-9
implicit transactions 27-7 controlling Unknown 4-18, 4-20
providers 27-3, 27-4 inner objects 4-18
resource dispensers 46-6 interfaces 4-16, 4-18
ADO commands 27-7 to 27-8, 27-18 to 27-21 outer objects 4-18
asynchronous 27-19 aliases
canceling 27-19 BDE 26-3, 26-14, 26-25 to 26-26
executing 27-19 local 26-25
iterating over 23-13 specifying 26-14, 26-14 to 26-15
parameters 27-20 to 27-21 Type Library editor 41-10, 41-18, 41-24
retrieving data 27-20 AliasName property 26-14
specifying 27-18
Index I-3
as operator optimizing 40-18
IInterface and 4-16 type checking 43-13
interfaces 4-16 type compatibility 41-12, 43-16 to 43-17
as reserved word, early binding 31-29 type descriptions 40-12
AS_ApplyUpdates method 30-3 Automation controllers 40-12, 42-1, 42-13 to 42-16,
AS_ATTRIBUTE 38-6 43-14
AS_DataRequest method 30-3 creating objects 42-13
AS_Execute method 30-3 dispatch interfaces 42-14
AS_GetParams method 30-3 dual interfaces 42-13
AS_GetProviderNames method 30-3 events 42-14 to 42-16
AS_GetRecords method 30-3 example 42-9 to 42-12
AS_RowRequest method 30-3 Automation objects 40-12
ASCII tables 26-5 component wrappers 42-7 to 42-8
ASP 40-13, 44-1 to 44-8 example 42-9 to 42-12
comparison to ActiveX 44-7 wizard 43-5 to 43-9
comparison to Web broker 44-1 See also COM objects
generating pages 44-3 Automation servers 40-10, 40-12 to 40-13
HTML documents 44-1 accessing objects 43-14
performance limitations 44-1 type libraries 40-17
scripting language 40-13, 44-3 See also COM objects
UI design 44-1 AutoPopup property 9-52
ASP intrinsics 44-3 to 44-7 AutoSelect property 10-3
accessing 44-2 to 44-3 AutoSessionName property 26-17, 26-30, 34-19
Application object 44-4 AutoSize property 9-5, 10-2, 18-14, 20-8
Request object 44-4 to 44-5 averages, decision cubes 22-5
Response object 44-5 .avi clips 10-19, 12-30, 12-33
Server object 44-6 to 44-7 .avi files 12-33
Session object 44-6
assembler code 15-15 B
Assign Local Data command 29-14
Assign method, string lists 5-21 Background 9-22
AssignedValues property 20-22 backgrounds 17-8
assignment statements, object variables 4-7 Bands property 9-52, 10-9
AssignValue method 25-17 base clients 46-2
Associate property 10-5 base unit 5-34, 5-36
as-soon-as-possible deactivation 31-7 base64 binary data 38-4
at reserved word 14-4 BaseCLX ,defined 3-1, 5-1
atomicity transactions 19-4, 46-9 batch files, Linux 15-18
attachments 38-7 batch operations 26-8, 26-49 to 26-53
Attributes property appending data 26-50, 26-51
parameters 24-46, 24-53 copying datasets 26-51
TADOConnection 27-7 deleting records 26-51
audio clips 12-32 different databases 26-51
AutoCalcFields property 24-23 error handling 26-52 to 26-53
AutoComplete property 31-8 executing 26-52
auto-dispatching components 38-11, 38-19 mapping data types 26-51 to 26-52
AutoDisplay property 20-9, 20-10 modes 26-8, 26-50
AutoEdit property 20-5 setting up 26-49 to 26-50
AutoHotKeys property 9-36 updating data 26-50, 26-51
Automation batch updates 27-13 to 27-15
Active Server Objects 44-2 applying 27-15
early binding 40-18 canceling 27-15
IDispatch interface 43-14 BatchMove method 26-8
interfaces 43-13 to 43-15 BDE resource dispensers 46-6
late binding 43-15 BDE Administration utility 26-14, 26-55
Index I-5
briefcase model 19-14 callbacks
brokering connections 31-27 limits in multi-tiered applications 31-11
Brush property 10-18, 12-4, 12-8 multi-tiered applications 31-17
brushes 12-8 to 12-9 transactional objects 46-25
bitmap property 12-9 CanBePooled method 46-8
colors 12-8 Cancel method 24-18, 24-21, 27-19
styles 12-8 Cancel property 10-7
building packages 16-10 to 16-13 CancelBatch method 15-27, 27-13, 27-15
business rules 31-2, 31-13 CancelRange method 24-35
ASP 44-1 CancelUpdates method 15-27, 26-34, 27-13, 29-6
transactional objects 46-2 CanModify property
business-to-business communication 37-1 data grids 20-26
ButtonAutoSize property 22-10 datasets 20-5, 24-17, 24-38
buttons 10-6 to 10-8 queries 26-11
adding to toolbars 9-47 to 9-49, 9-50 Canvas property 10-19
assigning glyphs to 9-48 canvases
disabling on toolbars 9-50 adding shapes 12-11 to 12-12, 12-14
navigator 20-29 common properties, methods 12-4
toolbars and 9-46 drawing lines 12-5, 12-10, 12-28 to 12-29
ButtonStyle property, data grids 20-20, 20-21, changing pen width 12-6
20-22 event handlers 12-26
ByteType 5-24 drawing vs. painting 12-4, 12-22
overview 12-1 to 12-3
C refreshing the screen 12-2
Caption property
CacheBlobs property 26-4 column headers 20-21
cached updates 29-16 to 29-24 decision grids 22-12
ADO 27-13 to 27-15 group boxes and radio groups 10-13
applying 27-15 invalid entries 9-34
canceling 27-15 labels 10-4
BDE 26-33 to 26-48 TForm 10-15
applying 26-11, 26-35 to 26-38 cascaded deletes 30-6
multiple tables 26-40, 26-45 cascaded updates 30-6
error handling 26-38 to 26-40 case sensitivity
updating read-only datasets 26-11 indexes 29-9
client datasets 19-10 to 19-14, 29-16, Linux 15-18
29-20 to 29-24 CASE tool 11-1
applying 26-11, 29-20 to 29-21 Cast method 5-42
multiple tables 26-40, 26-45 CastTo method 5-43
transactions 23-6 CDaudio disks 12-33
update errors 29-23 to 29-24, 30-11 CellDrawState function 22-13
updating read-only datasets 26-11 CellRect method 10-16
master/detail relationships 29-18 cells (grids) 10-16
overview 29-17 to 29-18 Cells function 22-13
providers 30-8 Cells property 10-16
update objects 29-19 CellValueArray function 22-13
CachedUpdates property 15-27, 26-33 CGI applications 33-5, 33-6, 36-1
calculated fields 24-23, 25-6 creating 34-1, 35-8
assigning values 25-8 change log 29-5, 29-20, 29-34
client datasets 29-11 saving changes 29-6
defining 25-7 to 25-8 undoing changes 29-5
lookup fields and 25-9 ChangeCount property 15-27, 26-33, 29-6
call synchronization 46-19 ChangedTableName property 26-53
Index I-7
Clipboard 7-8, 7-10, 20-9 color grids 12-6
clearing selection 7-10 Color property 10-4, 10-18
graphics and 12-21 to 12-23 brushes 12-8
graphics objects 12-3, 20-10 column headers 20-21
testing contents 7-11 data grids 20-20
testing for images 12-23 decision grids 22-12
Clipbrd unit 7-8 pens 12-5, 12-6
CloneCursor method 29-15 colormaps, menus and toolbars 9-23
Close method colors
connection components 23-4 internationalization and 17-8
database connections 26-20 pens 12-6
datasets 24-4 Cols property 10-16
sessions 26-18 column headers 10-14, 20-17, 20-21
CloseDatabase method 26-20 columns 10-16
CloseDataSets method 23-12 decision grids 22-12
CLSIDs 40-6, 40-16 default state 20-16, 20-22
license package file 45-7 deleting 20-17
CLX including in HTML tables 34-20
defined 3-1 persistent 20-16, 20-17 to 20-18
exception classes 14-10 to 14-11 creating 20-18 to 20-22
system events 15-12 deleting 20-19
units 15-8 to 15-11 inserting 20-19
VCL vs. 3-2 reordering 20-19
WinCLX vs. VisualCLX 15-5 to 15-6 properties 20-17, 20-20 to 20-21
CLX applications resetting 20-22
creating 15-2 Columns editor
database applications 15-21 to 15-27 creating persistent columns 20-18
deploying 18-6 deleting columns 20-19
Internet applications 15-28 reordering columns 20-19
overview 15-1 Columns property 10-10, 20-18
porting 15-2 to 15-16 grids 20-16
clx60.bpl 18-6 radio groups 10-13
CoClasses 40-6 ColWidths property 7-16, 10-16
ActiveX controls 45-5 COM 8-16
CLSIDs 40-6 aggregation 40-9
component wrappers 42-1, 42-3 applications 40-3 to 40-10, 40-19
limitations 42-2 distributed 8-16
creating 40-6, 41-19, 42-5, 42-13 clients 40-3, 40-10, 41-20, 42-1 to 42-17
declarations 42-5 containers 40-10, 42-1
naming 43-3, 43-5 controllers 40-10, 42-1
Type Library editor 41-10, 41-17, 41-23 definition 40-2
updating 41-21 early binding 40-17
code extensions 40-2, 40-10 to 40-12
porting to Linux 15-12 to 15-16 overview 40-1 to 40-24
templates 8-3 proxy 40-8, 40-9
Code editor specification 40-2
event handlers and 6-4 stubs 40-9
opening packages 16-10 wizards 40-19 to 40-24, 43-1
overview 2-4 COM interfaces 40-3 to 40-5, 43-3
Code Insight templates 8-3 adding to type libraries 41-21
code pages 17-3 Automation 43-13 to 43-15
ColCount property 20-29 dispatch identifiers 43-14
collections pane 11-4, 11-5 dual interfaces 43-13 to 43-14
color depths 18-12 implementing 40-6, 40-24
programming for 18-14 interface pointer 40-5
Index I-9
renaming 4-4 to 4-5 HTTP 31-10 to 31-11, 31-25
resizing 10-6 opening 31-27, 39-7
standard 6-7 to 6-9 protocols 31-9 to 31-11, 31-23
streaming 3-8 SOAP 31-11, 31-26
Components property 3-8 TCP/IP 31-9 to 31-10, 31-24, 39-3
ComputerName property 31-24 terminating 39-8
conditional directives, terminating 15-14 ConnectionString property 23-2, 23-5, 27-4, 27-10
ConfigMode property 26-25 ConnectionTimeout property 27-6
configuration files, Linux 15-18 ConnectOptions property 27-5
connected line segments 12-10 consistency, transactions 19-4, 46-9
Connected property 23-3 console applications 8-4
connection components 23-4 CGI 33-6
connection components CONSTRAINT constraint 30-13
database 19-8 to 19-9, 23-1 to 23-15, 26-3, 31-6 ConstraintErrorMessage property 25-11, 25-22,
accessing metadata 23-13 to 23-15 25-23
ADO 27-3 to 27-9 constraints
BDE 26-12 to 26-16 controls 9-5
binding 26-14 to 26-15, 27-3 to 27-4, data 25-22 to 25-23
28-3 to 28-5 client datasets 29-7, 29-30
dbExpress 28-2 to 28-5 creating 25-22
executing SQL commands 23-10 to 23-12, disabling 29-30
27-6 importing 25-23, 29-30, 30-13
implicit 23-2, 26-3, 26-13, 26-19, 26-20, 27-3 Constraints property 9-5, 29-7, 30-13
statements per connection 28-3 constructors 4-9
DataSnap 19-14, 31-3, 31-5, 31-9 to 31-11, 31-22, multiple 9-9
31-23 to 31-30 contained objects 40-9
Connection Editor 28-5 Contains list (packages) 16-7, 16-9
connection names 28-4 to 28-5 Content method, page producers 34-15
changing 28-5 content producers 34-4, 34-14
defining 28-5 event handling 34-16, 34-17, 34-18
deleting 28-5 Content property, web response objects 34-13
connection parameters 26-14 to 26-15 ContentFromStream method, page
ADO 27-4 producers 34-15
dbExpress 28-4, 28-5 ContentFromString method, page producers 34-15
login information 23-5, 27-4 ContentStream property, Web response
Connection property 27-3, 27-10 objects 34-13
Connection String Editor 27-4 context IDs 8-28
ConnectionBroker 29-26 context menus
ConnectionName property 28-4 Menu designer 9-40
ConnectionObject property 27-5 toolbars 9-52
connections context numbers (Help) 10-16
client 39-3 ContextHelp 8-32
database 23-3 to 23-6, 31-7, 31-8 controlling Unknown 4-18, 4-20
asynchronous 27-5 controls 3-9
closing 26-20 as ActiveX control implementation 45-3
managing 26-19 to 26-21 data-aware 20-1 to 20-32
naming 28-4 to 28-5 displaying data 20-4, 25-18
network protocols 26-15 generating ActiveX controls 45-2, 45-4 to 45-6
opening 26-18, 26-19 grouping 10-12 to 10-14
persistent 26-19 owner-draw 7-13, 7-15
temporary 26-20 declaring 7-13
database servers 23-3, 26-15 ControlType property 22-9, 22-16
DCOM 31-9, 31-24
dropping 31-28
Index I-11
display-only 20-8 data members 3-3
entering 24-19 data modules 19-6
formats, internationalizing 17-8 accessing from forms 8-20
graphing 19-15 creating 8-17
printing 19-16 database components 26-16
reporting 19-16 editing 8-17
synchronizing forms 20-4 remote vs. standard 8-17
data access sessions 26-17
components 19-1 Web 35-2, 35-3, 35-5
threads 13-5 Web applications and 34-2
cross-platform 18-7, 19-2 Web Broker applications 34-5
mechanisms 8-13, 19-1 to 19-2, 24-2 data packets 32-4
Data Access page (Component palette) 19-2, 31-2 application-defined information 29-15, 30-6
data binding 45-11 controlling fields 30-4
Data Bindings editor 42-8 converting to XML documents 32-6 to 32-8
data brokers 29-26, 31-1 copying 29-14 to 29-15
data compression, TSocketConnection 31-25 editing 30-7, 31-37
data constraints See constraints ensuring unique records 30-5
data context, Web Service applications 38-7 fetching 29-26 to 29-27, 30-7 to 30-8
Data Controls page (Component palette) 19-15, including field properties 30-6
20-1, 20-2 limiting client edits 30-5
Data Definition Language 23-10, 24-43, 26-9, 28-11 mapping to XML documents 32-2
Data Dictionary 25-13 to 25-14, 26-53 to 26-54, 31-3 read-only 30-5
constraints 30-13 refreshing updated records 30-6
data fields 25-6 XML 31-31, 31-33, 31-36, 31-36 to 31-37
defining 25-6 to 25-7 Data property 29-5, 29-14, 29-15, 29-34
data filters 24-13 to 24-16 data sources 19-7, 20-3 to 20-5
blank fields 24-14 disabling 20-4
client datasets 29-3 to 29-5 enabling 20-4
using parameters 29-29 events 20-4 to 20-5
defining 24-13 to 24-16 data stores 27-3
enabling/disabling 24-13 data types, persistent fields 25-6
operators 24-14 data-aware controls 19-15, 20-1 to 20-32, 25-18
queries vs. 24-13 associating with datasets 20-3 to 20-4
setting at runtime 24-16 common features 20-2
using bookmarks 27-11 to 27-12 disabling repaints 20-6, 24-8
data formats, default 25-15 displaying data 20-6 to 20-7
data grids 20-2, 20-15, 20-15 to 20-27, 20-28 current values 20-8
customizing 20-17 to 20-22 in grids 20-16, 20-28
default state 20-16 displaying graphics 20-10
displaying data 20-16, 20-17, 20-28 editing 20-5 to 20-6, 24-18
ADT fields 20-22 entering data 25-15
array fields 20-22 grids 20-15
drawing 20-26 inserting records 24-19
editing data 20-6, 20-26 list 20-2
events 20-27 read-only 20-8
getting values 20-17 refreshing data 20-7
inserting columns 20-18 representing fields 20-8
properties 20-29 database applications 8-12, 19-1
removing columns 20-17, 20-19 architecture 19-6 to 19-15, 31-31
reordering columns 20-19 deployment 18-6
restoring default state 20-22 distributed 8-13
runtime options 20-24 to 20-25 file-based 19-9 to 19-10, 27-15 to 27-16,
data integrity 19-5, 30-13 29-33 to 29-35
Data Manipulation Language 23-10, 24-43, 26-9 multi-tiered 31-3 to 31-4
Index I-13
states 24-3 to 24-4 DBMemo component 20-2, 20-9
stored procedures 24-24, 24-50 to 24-56 DBMS 31-1
tables 24-24, 24-25 to 24-42 DBNavigator component 20-2, 20-29 to 20-32
undoing changes 24-21 DBRadioGroup component 20-2, 20-14
unidirectional 28-1 to 28-20 DBRichEdit component 20-2, 20-9 to 20-10
unindexed 24-22 DBSession property 26-3
DataSets property 23-13 DBText component 20-2, 20-8
DataSnap page (Component palette) 31-2, 31-5, dbxconnections.ini 28-4, 28-5
31-6 dbxdrivers.ini 28-3 to 28-4
DataSource property DCOM 40-7, 40-8
ActiveX controls 42-8 connecting to application server 29-26, 31-24
data grids 20-16 distributing applications 8-16
data navigators 20-32 InternetExpress applications 31-36
lookup list and combo boxes 20-12 multi-tiered applications 31-9
queries 24-47 DCOM connections 31-9, 31-24
DataType property parameters 24-46, 24-52, 24-53 DCOMCnfg.exe 31-36
date fields, formatting 25-15 .dcp files 16-2, 16-13
dates, internationalizing 17-8 .dcu files 16-2, 16-12, 16-13
DAX 40-2, 40-22 to 40-24 DDL 23-10, 24-43, 24-49, 26-9, 28-11
dBASE tables 26-5 debugging
accessing data 26-9 Active Server Objects 44-8
adding records 24-19, 24-20 ActiveX controls 45-15
DatabaseName 26-3 code 2-5
indexes 26-6 COM objects 43-18
local transactions 26-32 dbExpress applications 28-19 to 28-20
password protection 26-21 to 26-24 service applications 8-10
renaming 26-8 transactional objects 46-25 to 46-26
DBChart component 19-15 Web server applications 33-9 to 33-10, 34-2,
DBCheckBox component 20-2, 20-13 to 20-14 35-8
DBComboBox component 20-2, 20-11 to 20-12 Decision Cube Editor 22-8 to 22-9
DBConnection property 29-17 Cube Capacity 22-20
DBCtrlGrid component 20-2, 20-28 to 20-29 Dimension Settings 22-8
properties 20-29 Memory Control 22-9
DBEdit component 20-2, 20-8 Decision Cube page (Component palette) 19-15,
dbExpress 18-7, 19-2, 28-1 to 28-2 22-1
components 28-1 to 28-20 decision cubes 22-7 to 22-9
cross-platform applications 15-21 to 15-27 design options 22-9
debugging 28-19 to 28-20 dimension maps 22-6, 22-7, 22-8, 22-20, 22-21
deploying 28-1 dimensions
drivers 28-3 to 28-4 opening/closing 22-10
metadata 28-13 to 28-18 paged 22-21
dbExpress applications 18-9 displaying data 22-10, 22-11
dbExpress page (Component palette) 19-2, 28-2 drilling down 22-5, 22-10, 22-12, 22-21
dbGo 27-1 getting data 22-5
DBGrid component 20-2, 20-15 to 20-27 memory management 22-9
events 20-27 pivoting 22-5, 22-10
properties 20-20 properties 22-7
DBGridColumns component 20-16 refreshing 22-7
DBImage component 20-2, 20-10 subtotals 22-5
DBListBox component 20-2, 20-11 to 20-12 decision datasets 22-5 to 22-7
DBLogDlg unit 23-4 decision graphs 22-13 to 22-18
DBLookupComboBox component 20-2, customizing 22-16 to 22-18
20-12 to 20-13 data series 22-18
DBLookupListBox component 20-2, 20-12 to 20-13 dimensions 22-14
Index I-15
directives 15-14 DLLs
$ELSEIF 15-14 COM servers 40-7
$ENDIF 15-14 threading models 43-7
$H compiler 5-30 creating 8-11
$IF 15-14 deployment 18-9
$IFDEF 15-13 embedding in HTML 34-15
$IFEND 15-14 HTTP servers 33-6
$IFNDEF 15-14 installing 18-6
$LIBPREFIX compiler 8-11 internationalizing 17-10, 17-12
$LIBSUFFIX compiler 8-11 MTS 46-2
$LIBVERSION compiler 8-11 packages 16-1, 16-2
$P compiler 5-30 DLLs See shared objects
$V compiler 5-31 DML 23-10, 24-43, 24-49, 26-9
$X compiler 5-31 .dmt files 9-41, 9-43
conditional compilation 15-13 docking 7-4
Linux 15-14 docking site 7-5
string-related 5-30 Document Literal style 38-1
directories, Linux and 15-20 Documentation view 11-4, 11-9
DirtyRead 23-9 DocumentElement property 37-4
DisableCommit method 46-12 DOM 37-2, 37-2 to 37-3
DisableConstraints method 29-30 implementations 37-3, 37-4
DisableControls method 20-6 registering vendors 37-3
DisabledImages property 9-50 Down property 10-8
disconnected model 19-14 speed buttons 9-48
dispatch actions 35-10 .dpk files 16-2, 16-7
dispatch interfaces 43-13, 43-14 to 43-15 .dpkw files 16-2
calling methods 42-14 drag cursors 7-2
identifiers 43-14 drag object 7-3
type compatibility 43-16 drag-and-dock 7-4 to 7-6
type libraries 41-9 drag-and-drop 7-1 to 7-4
Type Library editor 41-16 customizing 7-3
dispatcher 34-2, 34-5 to 34-6, 35-23 DLLs 7-4
auto-dispatching objects 34-6 getting state information 7-3
DLL-based applications and 34-3 mouse pointer 7-4
handling requests 34-9 DragMode property 7-1
selecting action items 34-6, 34-7 grids 20-20
dispatchers 35-28 draw grids 10-16
dispatching requests, WebSnap 35-22 Draw method 12-4
dispIDs 40-16, 43-14 drawing modes 12-29
binding to 43-15 drawing tools
dispinterfaces 31-29, 43-13, 43-14 to 43-15 assigning as default 9-48
dynamic binding 41-9 changing 12-13
type libraries 41-9 handling multiple in an application 12-12
DisplayFormat property 20-26, 25-12, 25-16 testing for 12-12, 12-13
DisplayLabel property 20-17, 25-12 DrawShape 12-15
DisplayWidth property 20-16, 25-12 drill-down forms 20-15
distributed applications drintf unit 26-54
database 8-13 driver names 26-14
MTS and COM+ 8-16 DriverName property 26-14, 28-3
distributed COM 40-7, 40-8 DropConnections method 26-13, 26-20
distributed data processing 31-2 drop-down lists 20-21
DllGetClassObject 46-3 drop-down menus 9-37
DllRegisterServer 46-3 DropDownCount property 10-11, 20-11
DropDownMenu property 9-52
Index I-17
data sources 20-4 to 20-5 executable files
data-aware controls COM servers 40-7
enabling 20-7 internationalizing 17-10, 17-12
default 6-4 on Linux 15-19
field objects 25-16 Execute method
interfaces 43-11 ADO commands 27-19, 27-20
internal 3-4 client datasets 29-28, 30-3
login 23-5 connection components 23-10 to 23-11
mouse 12-24 to 12-27 dialogs 9-17
testing for 12-27 providers 30-3
shared 6-5 TBatchMove 26-52
signalling 13-10 threads 13-4
system 3-4 ExecuteOptions property 27-12
timeout 13-11 ExecuteTarget method 9-31
types 3-4 Expandable property 20-24
user 3-4 Expanded property
VCL component wrappers 42-2 columns 20-23, 20-24
waiting for 13-10 data grids 20-20
XML brokers 31-38 Expression property 29-12
Events view 11-4, 11-9 ExprText property 25-10
EWriteError 5-2
except keyword 14-4 F
Exception 14-10, 14-13
defined 3-5 factory 35-5
exception handlers 14-4 to 14-8 Fetch Params command 29-28
default 14-5 FetchAll method 15-27, 26-34
Delphi 14-4 to 14-5 FetchBlobs method 29-27, 30-3
order 14-6 FetchDetails method 29-27, 30-3
exception handling 14-1, 14-4 to 14-8 fetch-on-demand 29-27
default 14-11 to 14-12 FetchOnDemand property 29-27
Delphi 14-2, 14-4 to 14-5 FetchParams method 29-28, 30-3
scope 14-6 to 14-7 field attributes 25-13 to 25-14
exception objects 14-1, 14-6 assigning 25-14
CLX 14-10 to 14-11 in data packets 30-6
defining classes 14-13 removing 25-14
Delphi 14-3, 14-5 field definitions 24-39
exceptions 3-6, 14-1 to 14-13 Field Link designer 24-36
COM interfaces 41-9 field objects 25-1 to 25-29
flow of control 14-2, 14-4, 14-6 to 14-7 accessing values 25-20 to 25-21
handlers 14-4 to 14-8 defining 25-5 to 25-11
Linux 15-19 deleting 25-11
raising 14-3, 14-7 to 14-8 display and edit properties 25-11
reraising 14-7 to 14-8 dynamic 25-2 to 25-3
resources and 14-8 persistent vs. 25-2
silent 14-12 to 14-13 events 25-16
threads 13-6 persistent 25-3 to 25-16
unhandled 14-11 to 14-12 dynamic vs. 25-2
VCL 14-9 to 14-13 properties 25-1, 25-11 to 25-16
exclusive locks, tables 26-6 runtime 25-13
Exclusive property 26-6 sharing 25-13
ExecProc method 24-55, 28-11 field types, converting 25-17, 25-19 to 25-20
ExecSQL method 24-48, 24-49, 28-11 FieldByName method 24-32, 25-21
update objects 26-47 FieldCount property, persistent fields 20-18
Index I-19
__finally keyword 14-9 IDE 4-2
FindClose procedure 5-8 instantiating 4-3
FindDatabase method 26-20 linking 9-4
FindFirst function 5-8 main 9-3
FindFirst method 24-16 master/detail tables 20-15
FindKey method 24-28, 24-30 memory management 9-6
EditKey vs. 24-30 modal 9-6
FindLast method 24-16 modeless 9-6, 9-8
FindNearest method 24-28, 24-30 passing arguments to 9-8 to 9-9
FindNext function 5-8 querying properties, example 9-10
FindNext method 24-16 referencing 9-4
FindPrior method 24-16 retrieving data from 9-9 to 9-12
FindResourceHInstance function 17-11 sharing event handlers 12-15
FindSession method 26-30 synchronizing data 20-4
First Impression 18-5 using local variables to create 9-8
First method 24-6 Formula One 18-5
FixedColor property 10-16 Found property 24-17
FixedCols property 10-16 FoxPro tables, local transactions 26-32
FixedOrder property 9-52, 10-9 FrameRect method 12-4
FixedRows property 10-16 frames 9-13, 9-14 to 9-16
FixedSize property 10-9 and component templates 9-15, 9-16
FlipChildren method 17-6 graphics 9-16
FloodFill method 12-4 resources 9-16
fly-by help 10-16 sharing and distributing 9-16
fly-over help 20-31 Free method 4-9, 15-11
focus 3-10 free threading 43-8
fields 25-17 FreeBookmark method 24-10
moving 10-6 free-threaded marshaler 43-8
FocusControl method 25-17 FromCommon 5-38
FocusControl property 10-4
Font property 10-2, 10-4, 12-4 G
column headers 20-21
data grids 20-21 $G compiler directive 16-11, 16-13
data-aware memo controls 20-9 Generate event support code 43-11
fonts 18-14 GetAliasDriverName method 26-27
height of 12-5 GetAliasNames method 26-27
Footer property 34-21 GetAliasParams method 26-27
FOREIGN KEY constraint 30-13 GetBookmark method 24-10
foreign translations 17-1 GetConfigParams method 26-27
form files 3-8, 15-17, 17-12 GetData method, fields 25-17
form linking 9-4 GetDatabaseNames method 26-27
Format property 22-12 GetDriverNames method 26-27
formatting data, international applications 17-8 GetDriverParams method 26-27
forms 9-1 GetFieldByName method 34-9
accessing from other forms 4-6 GetFieldNames method 23-14, 26-27
adding fields to 12-27 to 12-28 GetGroupState method 29-10
adding to projects 9-1 to 9-4 GetHandle 8-27
adding unit references 9-4 GetHelpFile 8-27
as object types 4-2 to 4-4 GetHelpStrings 8-27
code editor and 4-2 GetIDsOfNames method 43-14
creating at runtime 9-7 GetIndexNames method 23-14, 24-27
displaying 9-6 GetNextPacket method 15-27, 26-34, 29-26, 29-27,
drill down 20-15 30-3
global variable for 9-6 GetOptionalParam method 29-16, 30-6
Index I-21
Help hints 20-31 HTML tables 34-15, 34-21
Help Manager 8-24, 8-25 to 8-34 captions 34-21
Help selectors 8-30, 8-33 creating 34-20 to 34-21
Help systems 8-24 setting properties 34-20
interfaces 8-25 HTML templates 31-41 to 31-42, 34-14 to 34-18,
registering objects 8-30 35-4
tool buttons 9-52 default 31-39, 31-41
Help viewers 8-24 HTMLDoc property 31-39, 34-15
HelpContext 8-32 HTMLFile property 34-15
HelpContext property 8-31, 10-16 HTML-transparent tags
helper objects 5-1 converting 34-14, 34-16
HelpFile property 8-32, 10-16 parameters 34-14
HelpIntfs unit 8-25 predefined 31-41 to 31-42, 34-15
HelpKeyword 8-32 syntax 34-14
HelpKeyword property 8-31 HTTP 33-3
HelpSystem 8-32 connecting to application server 31-25
HelpType 8-31, 8-32 message headers 33-3
heterogeneous queries 26-9 to 26-10 multi-tiered applications 31-10 to 31-11
Local SQL 26-9 overview 33-5 to 33-6
hidden fields 30-5 request headers 33-4, 34-9, 44-4
Hint property 10-16 request messages See request messages
hints 10-16 response headers 34-13, 44-5
Hints property 20-31 response messages See response messages
horizontal track bars 10-5 SOAP 38-1
HorzScrollBar 10-5 status codes 34-12
host names 39-4 HTTP requests, images 35-26
IP addresses vs. 39-5 HTTP responses
Host property, TSocketConnection 31-24 actions 35-25
hosts 31-24, 39-4 images 35-27
addresses 39-4 httpsrvr.dll 31-10, 31-13, 31-25
URLs 33-3 HyperHelp viewer 8-24, 8-34
hot keys 10-6 hypertext links, adding to HTML 34-15
HotImages property 9-50
HotKey property 10-6 I
HTML commands 34-14
database information 34-19 IApplicationObject interface 44-4
generating 34-15 IAppServer interface 29-31, 29-33, 30-3 to 30-4,
HTML documents 33-5 31-5
ASP and 44-1 calling 31-28
databases and 34-18 extending 31-16
dataset page producers 34-19 local providers 30-3
datasets 34-21 remote providers 30-3
embedded ActiveX controls 45-1 state information 31-19
embedding tables 34-21 transactions 31-18
generated for ActiveForms 45-6 XML brokers 31-34
HTTP response messages 33-6 IAppServerSOAP interface 31-5, 31-26
InternetExpress applications 31-33 IConnectionPoint interface 43-13
page producers 34-14 to 34-18 IConnectionPointContainer interface 43-13
style sheets 31-40 icons 10-18
table producers 34-20 to 34-21 adding to menus 9-22
templates 31-39, 31-41 to 31-42, 34-14 to 34-16 graphics object 12-3
HTML forms 31-40 toolbars 9-50
HTML Result tab 35-2 tree views 10-11
HTML Script tab 35-2 ICustomHelpViewer 8-24, 8-25, 8-27, 8-29
implementing 8-25, 8-26
Index I-23
Insert From Resource command (Menu controlling Unknown 4-20
designer) 9-40, 9-46 CORBA 4-21
Insert from Resource dialog box 9-46 custom 43-15
Insert From Template command (Menu delegation 4-17
designer) 9-40, 9-42 Delphi 4-12
Insert method 24-19 dispatch 43-14
Append vs. 24-19 distributed applications 4-21
menus 9-44 DOM 37-2
strings 5-21 dynamic binding 4-16, 41-9, 43-13
INSERT statement 23-12 Dynamic Invocation Interface 4-21
INSERT statements 26-41, 26-44, 30-10 dynamic querying 4-14
Insert Template dialog box 9-42 early binding 31-29
InsertObject method 5-22 extending single inheritance 4-12, 4-13
InsertRecord method 24-22 Help system 8-25
InsertSQL property 26-41 IIDs 4-16, 4-20
Install COM+ objects command 46-26 IInterface 4-14
Install MTS objects command 46-26 implementing 4-12, 4-15, 40-6, 43-3
installation programs 18-2 internationalizing 17-7, 17-8, 17-12
Installing transactional objects 46-26 invokable 4-21, 38-2 to 38-9
InstallShield Express 2-5, 18-1 late binding 31-29
deploying lifetime management 4-14, 4-18
applications 18-2 marshaling 4-21
BDE 18-9 memory management 4-15, 4-18
packages 18-3 naming 4-12
instancing object destruction 4-18
COM objects 43-6 optimizing code 4-19
remote data modules 31-14 outgoing 43-11, 43-12
IntegralHeight property 10-10, 20-11 polymorphism 4-12, 4-13
integrated debugger 2-5 procedures 4-14
integrity violations 26-52 reference counting 4-14, 4-15, 4-19, 4-20
InterBase page (Component palette) 19-2 reusing code 4-16
InterBase tables 26-9 SOAP 4-21
InterBaseExpress 15-23 syntax 4-12
interceptors 40-5 type libraries 40-12, 40-18, 42-5, 43-9
Interface Definition Language See IDL Type Library editor 41-9, 41-15, 41-21, 43-9
interface pointers 40-5 Web Services 38-1
interfaces 4-12 to 4-21 XML nodes 37-5
ActiveX 40-20 InternalCalc fields 25-6, 29-11
customizing 45-8 to 45-12 indexes and 29-9
adding methods 43-10 to 43-11 internationalizing applications 17-1
adding properties 43-10 abbreviations and 17-7
aggregation 4-16, 4-18 converting keyboard input 17-7
application servers 31-16 to 31-17, localizing 17-11
31-28 to 31-30 Internet Engineering Task Force 33-3
as operator 4-16 Internet Information Server (IIS) 44-1
assignment compatibility 4-13 version 44-2
Automation 43-13 to 43-15 Internet servers 33-1 to 33-10
CLSIDs 4-21 Internet standards and protocols 33-3
COM 4-21, 8-16, 40-1, 40-3 to 40-5, 41-9, 42-1, InternetExpress 31-33 to 31-42
43-3, 43-9 to 43-15 vs. ActiveForms 31-32
declarations 42-5 InternetExpress page (Component palette) 6-8
events 43-11 intranets, host names 39-4
COM+ event objects 46-22 InTransaction property 23-7
components and 4-20
Index I-25
.lic file 45-7 ListSource property 20-12
license agreement 18-16 -LNpath compiler directive 16-13
license keys 45-7 LoadFromFile method
license package file 45-7 ADO datasets 27-15
licensing client datasets 19-10, 29-34
ActiveX controls 45-5, 45-7 graphics 12-19
Internet Explorer 45-7 strings 5-17
lifetime management LoadFromStream method, client datasets 29-34
components 4-20 LoadPackage function 16-4
interfaces 4-14, 4-18 LoadParamListItems procedure 23-15
lines LoadParamsFromIniFile method 28-5
drawing 12-5, 12-10, 12-10, 12-28 to 12-29 LoadParamsOnConnect property 28-5
changing pen width 12-6 local databases 19-3
event handlers 12-26 accessing 26-5
erasing 12-29 aliases 26-25
Lines property 10-2 BDE support 26-5 to 26-8
LineSize property 10-5 renaming tables 26-8
LineTo method 12-4, 12-7, 12-10 Local SQL 26-9, 26-10
Link HTML tag (<A>) 34-15 heterogeneous queries 26-9
linker switches, packages 16-13 local transactions 26-32 to 26-33
Linux locale settings 5-24
batch files 15-18 locales 17-2
cross-platform applications 15-1 to 15-28 data formats and 17-8
directories 15-20 resource modules 17-9
Registry 15-18 LocalHost property
Windows vs. 15-18 to 15-19 client sockets 39-7
list boxes 10-10, 20-2, 20-12 localization 17-12
data-aware 20-10 to 20-13 localizing applications 17-2
dragging items 7-2, 7-3 resources 17-9, 17-10, 17-12
dropping items 7-3 localizing applications 17-12
owner-draw 7-13 LocalPort property, client sockets 39-7
draw-item events 7-17 Locate method 24-11
measure-item events 7-16 Lock method 13-8
populating 20-11 locking objects
storing properties, example 9-9 nesting calls 13-8
list controls 10-9 to 10-12 threads 13-8
List property 26-30 LockList method 13-8
list views, owner draw 7-13 LockType property 27-13
listening connections 39-3, 39-8, 39-9 LogChanges property 29-5, 29-35
closing 39-8 logging in, Web connections 31-26
port numbers 39-5 logical values 20-2, 20-13
ListField property 20-13 Login dialog box 23-4
lists 5-14 to 5-22 login events 23-5
accessing 5-16 login information, specifying 23-5
accessing items 5-16 login pages, WebSnap 35-15 to 35-16
adding 5-15 login scripts 23-4 to 23-6
adding items 5-15 login support, WebSnap 35-13 to 35-19
collections 5-16 LoginPrompt property 23-4
deleting 5-15 logins, requiring 35-17
deleting items 5-15 long strings 5-23
persistent 5-16 lookup combo boxes 20-2, 20-12 to 20-13
rearranging 5-16 in data grids 20-21
rearranging items 5-16 lookup fields 20-12
string 5-16, 5-17 to 5-22 populating 20-21
using in threads 13-5 secondary data sources 20-12
Index I-27
placeholders 9-40 midaslib.dcu 18-7, 31-3
separator bars 9-36 MIDI files 12-33
setting properties 9-39 to 9-40 MIDL 40-19 See IDL
underlining letters 9-36 MIME messages 33-6
Menu property 9-45 MIME types and constants 12-22
menus 9-32 to 9-44 Min property
accessing commands 9-36 progress bars 10-15
action lists 9-19 track bars 10-5
adding 9-34, 9-37 MinSize property 10-6
adding images 9-38 MinValue property 25-12
colormaps 9-23 MM film 12-33
customizing 9-24 mobile computing 19-14
defined 9-19 modal forms 9-6
disabling items 7-11 Mode property 26-50
displaying 9-39, 9-40 pens 12-5
handling events 6-6, 9-44 modeless forms 9-6, 9-8
importing 9-45 ModelMaker 11-1 to 11-10
internationalizing 17-7, 17-8 Classes view 11-4, 11-5
moving among 9-41 collections pane 11-4, 11-5
moving items 9-38 Diagram Editor 11-4, 11-9
naming 9-34 Diagrams view 11-4, 11-6
owner-draw 7-13 Documentation view 11-4, 11-9
pop-up 7-11, 7-12 editors pane 11-4, 11-7
reusing 9-40 Events view 11-4, 11-9
saving as templates 9-41, 9-43 Implementation Editor 11-4, 11-7
shortcuts 9-36 to 9-37 Macros view 11-4, 11-9
styles 9-23 members pane 11-4, 11-7
templates 9-34, 9-40, 9-41, 9-42 Members view 11-4, 11-7
merge modules 18-3 models 11-2 to 11-3
MergeChangeLog method 29-6, 29-34 Patterns view 11-4, 11-9
message headers (HTTP) 33-3, 33-4 starting 11-2
message loop, threads 13-5 Unit Code Editor 11-4, 11-8
message-based servers See Web server Unit Difference view 11-4, 11-9
applications Units view 11-4, 11-5
metadata 23-13 to 23-15 models, ModelMaker 11-2 to 11-3
dbExpress 28-13 to 28-18 Modified property 10-2
modifying 28-11 to 28-12 Modifiers property 10-6
obtaining from providers 29-27 ModifyAlias method 26-26
metafiles 10-18, 12-1, 12-19 ModifySQL property 26-41
when to use 12-3 modules
Method property 34-10 Type Library editor 41-11, 41-19, 41-25
methods 3-4, 4-1, 12-15 types 8-17
abstract 4-12 Web types 35-2
adding to ActiveX controls 45-9 to 45-10 most recenty used lists (MRU) 9-25
adding to interfaces 43-10 to 43-11 mouse buttons 12-25
declaring 12-15 clicking 12-25, 12-26
deleting 6-6 mouse-move events and 12-27
event handlers 4-4 mouse events 12-24 to 12-27
MethodType property 34-7, 34-10 defined 12-24
Microsoft Server DLLs 33-6, 33-7 dragging and dropping 7-1 to 7-4
creating 34-1, 35-8 parameters 12-25
request messages 34-3 state information 12-25
Microsoft Transaction Server 8-16 testing for 12-27
Microsoft Transaction Server See MTS mouse pointer, drag-and-drop 7-4
midas.dll 29-1, 31-3
Index I-29
NotifyID 8-26 Objects property 10-16
NSAPI 36-1 string lists 5-22, 7-16
NSAPI applications 33-6 ObjectView property 20-22, 24-37, 25-24
creating 34-1, 34-2, 35-8 .ocx files 18-5
debugging 33-10 ODBC drivers
request messages 34-3 using with ADO 27-1, 27-2, 27-3
null values, ranges 24-32 using with the BDE 26-15, 26-16
null-terminated routines 5-26 to 5-27 ODL (Object Description Language) 40-17, 41-1
null-terminated strings 5-22 OEM character sets 17-3
numbers, internationalizing 17-8 OEMConvert property 10-3
numeric fields, formatting 25-15 OldValue property 26-39, 30-11
NumericScale property 24-46, 24-52, 24-53 OLE
NumGlyphs property 10-7 containers 6-7
merging menus 9-44
O OLE DB 27-1, 27-2, 27-3
OleObject property 45-14
Object Broker 31-27 OLEView 40-19
object contexts 46-4 OnAccept event 39-8, 39-10
ASP 44-3 server sockets 39-10
transactions 46-9 OnAction event 34-8
object fields 25-23 to 25-29 OnAfterPivot event 22-10
types 25-24 OnBeforePivot event 22-10
Object HTML tag (<OBJECT>) 34-15 OnBeginTransComplete event 23-7, 27-9
Object Inspector 4-4, 6-2 OnCalcFields event 24-23, 25-7, 25-8, 29-11
selecting menus 9-41 OnCellClick event 20-27
object pooling 46-8 to 46-9 OnChange event 25-16
disabling 46-8 OnClick event 10-7
remote data modules 31-8 to 31-9 buttons 4-3
Object Repository 8-21 to 8-24 menus 6-6
adding items 8-22 OnColEnter event 20-27
database components 26-16 OnColExit event 20-27
sessions 26-17 OnColumnMoved event 20-20, 20-27
specifying shared directory 8-22 OnCommitTransComplete event 23-8, 27-9
using items from 8-22 to 8-23 OnConnect event 39-9
Object Repository dialog 8-21 OnConnectComplete event 27-8
ObjectBroker property 31-24, 31-25, 31-26, 31-27 OnConstrainedResize event 9-5
ObjectContext property, example 46-14 OnDataChange event 20-4
object-oriented programming OnDataRequest event 29-32, 30-3, 30-12
defined 4-1 OnDblClick event 20-27
Delphi 4-1 to 4-21 OnDecisionDrawCell event 22-13
inheritance 4-5 OnDecisionExamineCell event 22-13
objects 4-1 to 4-11 OnDeleteError event 24-20
accessing 4-5 to 4-6 OnDisconnect event 27-8, 39-7, 39-8
creating 4-8, 4-9 OnDragDrop event 7-3, 20-27
customizing 4-5 OnDragOver event 7-2, 20-27
destroying 4-9 OnDrawCell event 10-16
dragging and dropping 7-1 OnDrawColumnCell event 20-26, 20-27
events 4-4 OnDrawDataCell event 20-27
inheritance 3-5 to 3-6, 4-5 OnDrawItem event 7-17
instantiating 4-3 OnEditButtonClick event 20-22, 20-27
multiple instances 4-3 OnEditError event 24-18
properties 4-2 OnEndDrag event 7-3, 20-27
records vs. 4-1 OnEndPage method 44-2
scripting 35-22 OnEnter event 20-27
type declarations 4-7 OnError event 39-9
See also COM objects
Index I-31
design-only option 16-7 DatabaseName 26-3
design-time 16-1, 16-5 to 16-6 directories 26-24
DLLs 16-1, 16-2 local transactions 26-32
duplicate references 16-9 network control files 26-24
dynamically loading 16-4 password protection 26-21 to 26-24
editing 16-8 renaming 26-8
file name extensions 16-1 retrieving indexes 24-27
installing 16-6 parallel processes, threads 13-1
internationalizing 17-10, 17-12 ParamBindMode property 26-12
linker switches 16-13 ParamByName method
naming 16-8 queries 24-47
options 16-8 stored procedures 24-54
referencing 16-4 ParamCheck property 24-45, 28-12
Requires list 16-7, 16-9 parameter collection editor 24-45, 24-52
runtime 16-1, 16-3 to 16-5, 16-7 parameterized queries 24-43, 24-45 to 24-47
source files 16-2 creating
using 8-11 at design time 24-45
using in applications 16-3 to 16-5 at runtime 24-47
weak packaging 16-12 parameters
PacketRecords property 15-27, 26-34, 29-26 binding modes 26-12
page controls 10-14 client datasets 29-27 to 29-29
adding pages 10-14 filtering records 29-29
page dispatchers 35-9, 35-23 dual interfaces 43-16
page mode 36-1 from XML brokers 31-37
page modules 35-2, 35-4 HTML tags 34-14
page producers 34-14 to 34-18, 35-2, 35-4, input 24-51
35-6 to 35-7 input/output 24-51
chaining 34-17 mouse events 12-24, 12-25
Content method 34-15 output 24-51, 29-27
ContentFromStream method 34-15 result 24-51
ContentFromString method 34-15 TXMLTransformClient 32-10
converting templates 34-16 Parameters property 27-21
data-aware 31-39 to 31-42, 34-19 TADOCommand 27-20
event handling 34-16, 34-17, 34-18 TADOQuery 24-45
templates 35-4 TADOStoredProc 24-52
types 35-10 ParamName property 31-40
pages, Component palette 6-7 Params property
PageSize property 10-5 client datasets 29-27, 29-28
paint boxes 10-19 queries 24-45, 24-47
paintboxes 6-7 stored procedures 24-52
PanelHeight property 20-29 TDatabase 26-15
panels TSQLConnection 28-4
adding speed buttons 9-47 XML brokers 31-37
attaching to form tops 9-47 ParamType property 24-46, 24-53
beveled 10-18 ParamValues property 24-47
speed buttons 10-8 ParentColumn property 20-24
Panels property 10-15 ParentConnection property 31-31
PanelWidth property 20-29 ParentShowHint property 10-16
panes 10-6 partial keys
resizing 10-6 searching 24-30
PAnsiString 5-28 setting ranges 24-33
Paradox tables 26-3, 26-5 passthrough SQL 26-31, 26-32
accessing data 26-9
adding records 24-19, 24-20
batch moves 26-53
Index I-33
ProblemCount property 26-53 data constraints 30-13
ProblemTableName property 26-53 error handling 30-11
ProcedureName property 24-50 external 19-11, 29-18, 29-25, 30-1
programming templates 8-3 internal 29-18, 29-25, 30-1
progress bars 10-15 local 29-25, 30-3
project files remote 29-26, 30-3, 31-6
changing 2-2 screening updates 30-11
distributing 2-5 supplying data to XML
Project Manager 9-4 documents 32-9 to 32-11
project options 8-3 using update objects 26-11
default 8-3 XML 32-8 to 32-9
Project Options dialog box 8-3 providing 30-1, 31-4
project templates 8-23 proxy 40-8, 40-9
projects, adding forms 9-1 to 9-4 transactional objects 46-2
properties 3-3, 4-2 PString 5-28
adding to ActiveX controls 45-9 to 45-10 public section 4-6
adding to interfaces 43-10 published section 4-7
COM 40-3, 41-9 PVCS Version Manager 2-5
Write By Reference 41-9 PWideString 5-28
COM interfaces 41-9
HTML tables 34-20 Q
setting 6-2 to 6-3
properties, memo and rich edit controls 10-2 Qt painter 5-31
property editors 6-3 Qt widgets, creating 15-11
Property Page wizard 45-13 qualifiers 4-5 to 4-6
property pages 45-12 to 45-14 queries 24-24, 24-42 to 24-50
ActiveX controls 42-6, 45-3, 45-14 BDE-based 26-2, 26-9 to 26-11
adding controls 45-13 to 45-14 concurrent 26-17
associating with ActiveX control live result sets 26-10 to 26-11
properties 45-13 bi-directional cursors 24-49
creating 45-13 to 45-14 executing 24-49
imported controls 42-4 filtering vs. 24-13
updating 45-13 heterogeneous 26-9 to 26-10
updating ActiveX controls 45-14 HTML tables 34-21
Proportional property 12-3 master/detail relationships 24-47 to 24-48
protected blocks 14-1 optimizing 24-48 to 24-49, 24-50
cleanup code 14-8, 14-9 parameterized 24-43
Delphi 14-2 parameters 24-45 to 24-47
nesting 14-6 to 14-7 binding 24-45
protected section 4-7 from client datasets 29-29
protocols master/detail relationships 24-47 to 24-48
choosing 31-9 to 31-11 named 24-45
connection components 31-9 to 31-11, 31-23 properties 24-46
Internet 33-3, 39-1 setting at design time 24-45
network connections 26-15 setting at runtime 24-47
Provider property 27-4 unnamed 24-45
ProviderFlags property 30-5, 30-10 preparing 24-48 to 24-49
ProviderName property 19-12, 29-25, 30-3, 31-23, result sets 24-49
31-37, 32-9 specifying 24-43 to 24-44, 28-6
providers 30-1 to 30-13, 31-3 specifying the database 24-42
applying updates 30-4, 30-8, 30-11 TSimpleClientDataSet 29-37
associating with datasets 30-2 unidirectional cursors 24-50
associating with XML documents 30-2, 32-8 update objects 26-48
client datasets and 29-24 to 29-32 Web applications 34-21
client-generated events 30-12
Index I-35
registering RemoteHost property 39-7
Active Server Objects 44-8 RemotePort property 39-7
ActiveX controls 45-15 RemoteServer property 29-25, 29-26, 31-22, 31-27,
COM objects 43-17 31-34, 31-37, 32-9
conversion families 5-34 RemoveAllPasswords method 26-22
registering Help objects 8-30 RemovePassword method 26-22
RegisterNonActiveX procedure 45-3 RenameFile function 5-10, 5-11
RegisterPooled procedure 31-9 RepeatableRead 23-10
RegisterTypeLib function 40-18 reports 21-1
RegisterViewer function 8-30 using QuickReport 19-16
RegisterXSClass method 38-5 Repository See Object Repository
RegisterXSInfo method 38-5 Request for Comment (RFC) documents 33-3
Registry 17-8 request headers 34-9
REGSERV32.EXE 18-5 request messages 34-3, 44-4
relational databases 19-1 action items and 34-6
Release 8-27 contents 34-11
_Release method 4-14, 4-18, 4-20 dispatching 34-5
Release method 40-4 header information 34-9 to 34-11
TCriticalSection 13-8 HTTP overview 33-5 to 33-6
release notes 18-16 processing 34-5
releasing mouse buttons 12-26 responding to 34-8 to 34-9, 34-13
relocatable code 15-15 types 34-10
remotable classes 38-4, 38-6 to 38-9 XML brokers 31-38
built-in 38-6 request objects, header information 34-4
example 38-7 to 38-9 RequestLive property 26-10
exceptions 38-18 to 38-19 RequestRecords method 31-37
headers 38-16 requests
lifetime management 38-7 adapters 35-25
registering 38-5 dispatching 35-22
remotable type registry 38-5, 38-18 images 35-26
remote applications, TCP/IP 39-1 Requires list (packages) 16-7, 16-9
remote connections 39-3 ResetEvent method 13-10
multiple 39-5 resizing controls 10-6, 18-13
opening 39-7, 39-8 ResolveToDataSet property 30-4
sending/receiving information 39-10 resolving 30-1, 31-4
terminating 39-8 resource dispensers 46-5
Remote Data Module wizard 31-13 to 31-14 ADO 46-6
remote data modules 8-21, 31-3, 31-6, 31-12, BDE 46-6
31-13 to 31-21 Resource DLLs
child 31-21 dynamic switching 17-11
COM-based 31-21 wizard 17-9
instancing 31-14 resource files 9-45 to 9-46
multiple 31-21, 31-30 to 31-31 loading 9-46
parent 31-21 resource modules 17-8, 17-9, 17-10
pooling 31-8 to 31-9 resource pooling 46-5 to 46-8
stateless 31-8, 31-9, 31-19 to 31-21 resources
threading models 31-14, 31-15 isolating 17-8
Remote Database Management system 19-3 localizing 17-9, 17-10, 17-12
remote database servers 19-2 releasing 14-8
remote servers 26-9, 40-7 strings 17-8
maintaining connections 26-19 resourcestring reserved word 17-8
unauthorized access 23-4 response headers 34-13
Index I-37
SelectAll method 10-3 network servers 39-1
Selecting 35-10 ports and 39-2
Selection property 10-16 requesting 39-6
SelectKeyword 8-30 uninstalling 8-6
selectors, Help 8-30 Session variable 26-3, 26-16
SelEnd property 10-5 SessionName property 26-3, 26-13, 26-29, 34-19
SelLength property 7-9, 10-2 sessions 26-16 to 26-30
SelStart property 7-9, 10-2, 10-5 activating 26-18
SelText property 7-9, 10-2 associated databases 26-20 to 26-21
SendBuf method 39-8 closing 26-18
Sender parameter 6-5 closing connections 26-20
event handlers 4-8 creating 26-28, 26-29
example 12-7 current state 26-18
Sendln method 39-8 databases and 26-13
SendStream method 39-8 datasets and 26-3 to 26-4
separator bars (menus) 9-36 default 26-3, 26-13, 26-16 to 26-17
server applications default connection properties 26-18
architecture 31-5 getting information 26-27
COM 40-5 to 40-9, 43-1 to 43-18 implicit database connections 26-13
interfaces 39-2 managing aliases 26-25
multi-tiered 31-5 to 31-11, 31-12 to 31-17 managing connections 26-19 to 26-21
registering 31-11, 31-22 methods 26-13
services 39-1 multiple 26-13, 26-28, 26-29 to 26-30
Web Services 38-9 to 38-20 multi-threaded applications 26-13,
server connections 39-3 26-29 to 26-30
port numbers 39-5 naming 26-29, 34-19
server sockets 39-7 to 39-8 opening connections 26-19
accepting client requests 39-7, 39-10 passwords 26-21
error messages 39-8 restarting 26-18
event handling 39-9 Web applications 34-18
socket objects 39-7 Sessions property 26-30
specifying 39-6, 39-7 sessions service 35-10, 35-13, 35-14 to 35-15
server types 35-8 Sessions variable 26-17, 26-29
ServerGUID property 31-23 SetAbort method 46-5, 46-8, 46-12
ServerName property 31-23 SetBrushStyle method 12-8
servers SetComplete method 31-17, 46-5, 46-8, 46-12
Internet 33-1 to 33-10 SetData method 25-17
Web application debugger 35-8 SetEvent method 13-10
server-side scripting 35-7, 35-19 to 35-22 SetFields method 24-22
service applications 8-5 to 8-10 SetKey method 24-28
debugging 8-10 EditKey vs. 24-30
example 8-8 SetLength procedure 5-28
example code 8-6, 8-8 SetOptionalParam method 29-15
Service Control Manager 8-5, 8-10 SetPenStyle method 12-7
Service Start name 8-9 SetProvider method 29-25
service threads 8-8 SetRange method 24-33
services 8-5 to 8-10 SetRangeEnd method 24-32
example 8-8 SetRange vs. 24-33
example code 8-6, 8-8 SetRangeStart method 24-31
implementing 39-1 to 39-2, 39-7 SetRange vs. 24-33
installing 8-5 SetSchemaInfo method 28-13
name properties 8-9 Shape property 10-18
Index I-39
speed buttons 10-8 StartTransaction method 23-7
adding to toolbars 9-47 to 9-49 state information
assigning glyphs 9-48 communicating 30-8, 31-19 to 31-21
centering 9-47 managing 46-5
engaging as toggles 9-49 mouse events 12-25
event handlers 12-13 shared properties 46-6
for drawing tools 12-13 transactional objects 46-11
grouping 9-48 to 9-49 State property 10-8
initial state, setting 9-48 datasets 24-3, 25-8
operational modes 9-47 grid columns 20-16
splitters 10-6 grids 20-16, 20-18
SPX/IPX 26-15 stateless objects 46-11
SQL 19-2, 26-9 static binding 31-29
executing commands 23-10 to 23-12 COM 40-17
local 26-9 static text control 10-3
standards 30-13 status bars 10-15
Decision Query editor and 22-7 internationalizing 17-7
SQL Builder 24-44 owner draw 7-13
SQL Explorer 26-55, 31-3 status information 10-15
defining attribute sets 25-14 StatusCode property 34-12
SQL Links, drivers 26-9, 26-15, 26-32 StatusFilter property 15-27, 26-33, 27-13, 29-6,
SQL Monitor 26-55 29-19, 30-8
SQL property 24-44 StdConvs unit 5-33, 5-34, 5-36
changing 24-49 Step property 10-15
SQL queries 24-43 to 24-44 StepBy method 10-15
copying 24-44 StepIt method 10-15
executing 24-49 stored procedures 19-5, 24-24, 24-50 to 24-56
loading from files 24-44 BDE-based 26-2, 26-11 to 26-12
modifying 24-44 parameter binding 26-12
optimizing 24-50 creating 28-12
parameters 24-45 to 24-47, 26-43 dbExpress 28-8
binding 24-45 executing 24-55
master/detail relationships 24-47 to 24-48 listing 23-14
setting at design time 24-45 overloaded 26-12
setting at runtime 24-47 parameters 24-51 to 24-54
preparing 24-48 to 24-49 design time 24-52 to 24-53
result sets 24-49 from client datasets 29-29
update objects 26-47 properties 24-52 to 24-53
SQL servers, logging in 19-4 runtime 24-54
SQL statements preparing 24-55
client-supplied 29-32, 30-6 specifying the database 24-50
decision datasets 22-5, 22-6 StoredProcName property 24-50
executing 28-10 to 28-11 StrByteType 5-24
generating streaming, components 3-8
providers 30-4, 30-10 to 30-11 streams 5-2 to 5-5
TSQLDataSet 28-9 copying data 5-4
parameters 23-11 position 5-4
passthrough SQL 26-32 reading and writing data 5-2
provider-generated 30-12 seeking 5-4
update objects and 26-41 to 26-45 size 5-4
SQLConnection property 28-3, 28-19 storage media 5-4
SQLPASSTHRUMODE 26-32 Stretch property 20-10
standalone mode 36-1 StretchDraw method 12-5
standard components 6-7 to 6-9 string fields, size 25-6
string grids 10-16, 10-17
Index I-41
non-database grids 10-16 TControl 3-5, 3-9
ranges 24-31 to 24-35 defined 3-6
read-only 24-38 TConvType values 5-34
searching 24-28 to 24-30 TConvTypeInfo 5-38
sorting 24-26, 28-7 TCoolBand 10-9
specifying the database 24-25 TCoolBar 9-46
synchronizing 24-42 TCP/IP 26-15, 39-1
TableType property 24-39, 26-5 to 26-6 clients 39-6
tabs, draw-item events 7-17 connecting to application server 31-24
Tabs property 10-14 multi-tiered applications 31-9 to 31-10
tabular display (grids) 10-16 servers 39-7
tabular grids 20-28 TCurrencyField, default formatting 25-15
TAction 9-22 TCustomADODataSet 24-2
TActionClientItem 9-25 TCustomClientDataSet 24-2
TActionList 9-20 TCustomContentProducer 34-14
TActionMainMenuBar 9-18, 9-19, 9-20, 9-21, 9-22, TCustomIniFile 5-13
9-24 TCustomizeDlg 9-24
TActionManager 9-18, 9-20, 9-21 TCustomVariantType 5-40, 5-42 to 5-50
TActionToolBar 9-18, 9-19, 9-20, 9-21, 9-22, 9-24 TDatabase 19-8, 23-1, 26-3, 26-12 to 26-16
TActiveForm 45-3, 45-6 DatabaseName property 26-3
TAdapterDispatcher 35-23 temporary instances 26-20
TAdapterPageProducer 35-21 dropping 26-20
TADOCommand 27-2, 27-7, 27-10, 27-18 to 27-21 TDataSet 24-1
TADOConnection 19-8, 23-1, 27-2, 27-3 to 27-9, descendants 24-2 to 24-3
27-10 TDataSetProvider 30-1, 30-2
connecting to data stores 27-3 to 27-5 TDataSetTableProducer 34-21
TADODataSet 27-2, 27-9, 27-10, 27-16 to 27-17 TDataSource 20-3 to 20-5
TADOQuery 27-2, 27-9, 27-10 TDateField, default formatting 25-15
SQL command 27-18 TDateTimeField, default formatting 25-15
TADOStoredProc 27-2, 27-9, 27-10 TDBChart 19-15
TADOTable 27-2, 27-9, 27-10 TDBCheckBox 20-2, 20-13 to 20-14
Tag property 25-12 TDBComboBox 20-2, 20-10, 20-11 to 20-12
TApplication 8-25, 8-32, 9-1 TDBCtrlGrid 20-2, 20-28 to 20-29
Styles 15-6 properties 20-29
TApplicationEvents 9-2 TDBEdit 20-2, 20-8
TASM code, Linux 15-12 TDBGrid 20-2, 20-15 to 20-27
TASPObject 44-2 events 20-27
TBatchMove 26-49 to 26-53 properties 20-20
error handling 26-52 to 26-53 TDBGridColumns 20-16
TBCDField, default formatting 25-15 TDBImage 20-2, 20-10
TBDEClientDataSet 26-2 TDBListBox 20-2, 20-10, 20-11 to 20-12
TBDEDataSet 24-2 TDBLookupComboBox 20-2, 20-10, 20-12 to 20-13
TBevel 10-18 TDBLookupListBox 20-2, 20-10, 20-12 to 20-13
TBitBtn control 10-7 TDBMemo 20-2, 20-9
TBrush 10-18 TDBNavigator 20-2, 20-29 to 20-32, 24-5, 24-6
tbsCheck constant 9-51 TDBRadioGroup 20-2, 20-14
TByteDynArray 38-4 TDBRichEdit 20-2, 20-9 to 20-10
TCanvas, using 5-22 TDBText 20-2, 20-8
TClientDataSet 29-18 TDCOMConnection 31-24
TClientDataset 8-20 TDecisionCube 22-1, 22-5, 22-7 to 22-9
TClientSocket 39-6 events 22-7
TComObject, aggregation 4-18 TDecisionDrawState 22-13
TComplexVariantType 5-42 TDecisionGraph 22-1, 22-2, 22-13 to 22-18
TComponent 3-5, 3-7, 4-9
defined 3-5
Index I-43
using lists 13-5 TOleContainer 42-16 to 42-17
VCL thread 13-4 Active Documents 40-14
waiting for 13-10 TOleControl 42-6
multiple 13-10 TOleServer 42-6
waiting for events 13-10 tool buttons 9-50
thread-safe objects 13-5 adding images 9-50
threadvar 13-6 disabling 9-50
three-tiered applications See multi-tiered engaging as toggles 9-51
applications getting help with 9-52
THTMLTableAttributes 34-20 grouping/ungrouping 9-51
THTMLTableColumn 34-21 in multiple rows 9-50
THTTPRio 38-21 initial state, setting 9-50
THTTPSoapDispatcher 38-9, 38-11 wrapping 9-50
THTTPSoapPascalInvoker 38-9, 38-11 toolbars 9-46, 10-9
TIBCustomDataSet 24-2 action lists 9-19
TIBDatabase 19-9, 23-1 adding 9-49 to 9-51
TickMarks property 10-5 adding panels as 9-47 to 9-49
TickStyle property 10-5 colormaps 9-23
tiers 31-1 context menus 9-52
TImage, in frames 9-16 creating 9-20
TImage component 10-18 customizing 9-24
TImageList 9-50 default drawing tool 9-48
time, internationalizing 17-8 defined 9-19
time conversion 5-35 designing 9-46 to 9-53
time fields, formatting 25-15 disabling buttons 9-50
timeout events 13-11 hiding 9-53
timers 6-7 inserting buttons 9-47 to 9-49, 9-50
TIniFile 5-11, 5-12 owner-draw 7-13
TInterfacedObject 4-15, 4-19 setting margins 9-48
deriving from 4-15 speed buttons 10-8
implementing IInterface 4-15 styles 9-23
TInvokableClass 38-12 transparent 9-50, 9-52
accessing headers 38-16 tool-tip help 10-16
TInvokeableVariantType 5-51 to 5-52 Top property 9-5, 9-47
Title property, data grids 20-21 TopRow property 10-16
.TLB files 40-18, 41-2, 41-27 TPageControl 10-14
TLCDNumber 10-3 TPageDispatcher 35-23
TLIBIMP command-line tool 40-19, 42-2, 42-5, TPageProducer 34-14
43-15 TPaintBox 10-19
TLocalConnection 29-25, 31-5 TPanel 9-46, 10-13
TMainMenu 9-20 TPersistent, defined 3-5
TMaskEdit 10-1 tpHigher constant 13-3
TMemIniFile 5-11, 5-12, 15-20 tpHighest constant 13-3
TMemo 10-1 tpIdle constant 13-3
TMemoryStream 5-2 tpLower constant 13-3
TModels 38-15 tpLowest constant 13-3
TMTSDataModule 31-6 tpNormal constant 13-3
TMultiReadExclusiveWriteSynchronizer 13-9 TPopupMenu 9-52
TNestedDataSet 24-38 TPrinter 5-32
TObject 3-5, 4-9 TPropertyPage 45-13
defined 3-5 tpTimeCritical constant 13-3
ToCommon 5-38 TPublishableVariantType 5-53
toggles 9-49, 9-51 TQuery 26-2, 26-9 to 26-11
decision datasets and 22-6
TQueryTableProducer 34-21
Index I-45
try blocks 14-2 TWebPageModule 35-2
try...except statements 14-4 TWebResponse 34-3
try...finally statements 14-9 TWidgetControl 3-5, 3-10, 15-5
TScreen 9-1 TWinControl 3-10, 15-5, 17-7
TScrollBox 10-5, 10-13 defined 3-6
TSearchRec 5-9 two-phase commit 31-18
TServerSocket 39-7 two-tiered applications 19-3, 19-9, 19-12, 29-36
TService_object 8-9 TWriter 5-3
TSession 26-16 to 26-30 TWSDLHTMLPublish 38-9, 38-19
adding 26-28, 26-29 TWSDLHTMLPublisher 38-11
TSharedConnection 31-31 TXMLDocument 37-4, 37-9
TSimpleDataSet 28-2, 29-18, 29-26, 29-29, TXMLTransform 32-6 to 32-8
29-35 to 29-37 source documents 32-6
advantages and disadvantages 29-36 TXMLTransformClient 32-9 to 32-11
internal provider 30-1 parameters 32-10
TSoapAttachment 38-7 TXMLTransformProvider 30-1, 30-2, 32-8 to 32-9
TSoapConnection 31-26 type declarations
TSoapDataModule 31-6 classes 4-7
TSOAPHeader 38-16 enumerated types 12-12
TSocketConnection 31-24 type definitions, Type Library editor 41-10
TSQLConnection 19-9, 23-1, 28-2 to 28-5 type information 40-16, 41-1
binding 28-3 to 28-5 dispinterfaces 43-13
monitoring messages 28-19 Help 41-8
TSQLDataSet 28-2, 28-6, 28-7, 28-8 IDispatch interface 43-14
TSQLMonitor 28-19 to 28-20 importing 42-2 to 42-6
TSQLQuery 28-2, 28-6 type libraries 40-11, 40-12, 40-16 to 40-19,
TSQLStoredProc 28-2, 28-8 41-1 to 41-28
TSQLTable 28-2, 28-7 _TLB unit 40-24, 41-2, 41-20, 42-2, 42-5 to 42-6,
TSQLTimeStampField, default formatting 25-15 43-15
TStoredProc 26-2, 26-11 to 26-12 accessing 40-18, 41-20, 42-2 to 42-6
TStream 5-2 Active Server Objects 44-3
TStringList 5-17 to 5-22, 8-28 ActiveX controls 45-3
TStrings 5-17 to 5-22 adding
TTabControl 10-14 methods 41-22 to 41-23
TTable 26-2, 26-5 to 26-8 properties 41-22 to 41-23
decision datasets and 22-6 adding interfaces 41-21
TTextBrowser 10-3 benefits 40-18 to 40-19
TTextViewer 10-3 browsers 40-18
TThread 13-2 browsing 40-19
TThreadList 13-5, 13-8 contents 40-16, 41-1, 42-5 to 42-6
TTimeField, default formatting 25-15 creating 40-17, 41-19 to 41-20
TToolBar 9-20, 9-46, 9-49 deploying 41-27 to 41-28
TToolButton 9-46 exporting as IDL 41-27
TTreeView 10-11 generated by wizards 41-1
TTypedComObject, type library IDL and ODL 40-17
requirement 40-17 importing 42-2 to 42-6
TUpdateSQL 26-40 to 26-48 including as resources 41-27 to 41-28, 45-3
providers and 26-11 interfaces 40-18
TVarData record 5-41 modifying interfaces 41-21 to 41-23
TWebActionItem 34-3 opening 41-20
TWebAppDataModule 35-2 optimizing performance 41-9
TWebAppPageModule 35-2 registering 40-19, 41-27
TWebConnection 31-25 registering objects 40-18
TWebContext 35-23 saving 41-26
TWebDataModule 35-2 tools 40-19
TWebDispatcher 35-23, 35-27
Index I-47
Update SQL editor 26-42 to 26-43 variables
Options page 26-42 declaring 4-7
SQL page 26-42 objects 4-7 to 4-8
UPDATE statements 26-41, 26-44, 30-10 Variant type 5-40
UpdateBatch method 15-27, 27-13, 27-15 variants, custom 5-40 to 5-53
UpdateMode property 30-10 VCL
client datasets 29-22 CLX vs. 3-2
UpdateObject method 45-13, 45-14 defined 3-1
UpdateObject property 26-11, 26-33, 26-41, 26-45 main thread 13-4
UpdatePropertyPage method 45-13 messages 15-12
UpdateRecordTypes property 15-27, 26-33, 29-19 overview 3-1 to 3-3
UpdatesPending property 15-27, 26-33 TComponent branch 3-7
UpdateStatus property 15-27, 26-33, 27-13, 29-19, TControl branch 3-9
30-9 TObject branch 3-6
UpdateTarget method 9-31 TPersistent branch 3-7
URI vs.URL 33-4 TWinControl branch 3-10
URL property 31-25, 31-26, 34-9, 38-22 units 15-8 to 15-11
URLs 33-3 vcl60.bpl 16-1, 16-9, 18-6
host names 39-4 penwin.dll 16-12
IP addresses 39-4 VendorLib property 28-4
javascript libraries 31-34, 31-35 version control 2-5
SOAP connections 31-26 version information
vs. URIs 33-4 ActiveX controls 45-5
Web browsers 33-5 type information 41-8
Web connections 31-25 vertical track bars 10-5
Use Unit command 8-20, 9-4 VertScrollBar 10-5
user interfaces 9-1 video casettes 12-33
databases 19-15 to 19-16 video clips 12-30, 12-32
forms 9-1 to 9-4 viewing scripts 35-21
isolating 19-6 ViewStyle property 10-12
layout 9-5 visibility 4-6 to 4-7
multi-record 20-14 Visible property 3-3
organizing data 20-7 to 20-8, 20-14 to 20-15 cool bars 9-53
single record 20-7 fields 25-12
user list service 35-10, 35-13 menus 9-44
uses clause 4-6, 15-6 toolbars 9-53
adding data modules 8-20 VisibleButtons property 20-30, 20-31
avoiding circular references 9-4 VisibleColCount property 10-16
including packages 16-4 VisibleRowCount property 10-16
UseSOAPAdapter property 31-26 VisualCLX
defined 3-2
V packages 16-9
WinCLX vs. 15-5 to 15-6
$V compiler directive 5-31 visualclx package 16-1
validating data entry 25-16 VisualSpeller Control 18-5
Value property vtables 40-5
aggregates 29-14 COM interface pointer 40-5
fields 25-18 component wrappers 42-6
parameters 24-46, 24-53 creator classes and 42-5, 42-13
ValueChecked property 20-13 dual interfaces 43-13
values, default data 20-10 type libraries and 40-17
Values property vs dispinterfaces 41-9
radio groups 20-14
ValueUnchecked property 20-13, 20-14
VarCmplx unit 5-42
Index I-49
widgets WSDL 38-2
creating 15-11 files 38-19
Windows controls vs. 15-5 importing 38-3, 38-13 to 38-14, 38-20
Width property 9-5, 10-15 publishing 38-18, 38-19 to 38-20
data grid columns 20-16 WSDL administrator 38-20
data grids 20-21 WSDL publisher 38-11
pens 12-5, 12-6
TScreen 18-13 X
WIN32 (cross-platform applications) 15-14
WIN64 (cross-platform applications) 15-14 $X compiler directive 5-31
WinCLX XDR file 37-2
defined 3-2 Xerox Network System (XNS) 39-1
VisualCLX vs. 15-6 .xfm files 4-4, 15-4, 15-17
Windows, Graphics Device Interface (GDI) 12-1 XML 32-1, 37-1
windows, resizing 10-6 database applications 32-1 to 32-11
Windows applications, porting to defining mappings 32-4
Linux 15-2 to 15-19 document type declaration 37-2
Windows services 36-1 mappings 32-2 to 32-4
Windows XP themes 9-54 parsers 37-2
wininet.dll 31-25, 31-27 processing instructions 37-2
wizards 8-21 SOAP and 38-1
Active Server Object 40-21, 44-2 to 44-3 XML brokers 31-34, 31-36 to 31-38
ActiveForm 40-22, 45-6 HTTP messages 31-38
ActiveX controls 40-21, 45-4 to 45-5 XML Data Binding wizard 37-6 to 37-10
ActiveX library 40-22 XML documents 32-1, 37-1 to 37-10
Add New Web Service 38-11 attributes 32-5, 37-5
Automation object 40-21, 43-5 to 43-9 child nodes 37-6
COM 40-19 to 40-24, 43-1 components 37-4, 37-9
COM object 40-21, 41-19, 43-3 to 43-4, converting to data packets 32-6 to 32-8
43-6 to 43-9 generating interfaces for 37-7
COM+ Event object 40-22, 46-21 to 46-22 mapping nodes to fields 32-2
COM+ Event subscriber object 46-22 nodes 37-2, 37-4 to 37-6
Property Page 45-13 properties for nodes 37-7
property page 40-22 publishing database information 32-9
Remote Data Module 31-13 to 31-14 root node 37-4, 37-7, 37-9
Resource DLL 17-9 transformation files 32-1
SOAP Data Module 31-16 XML files 27-15
Transactional Data Module 31-15 XML schemas 37-2
transactional object 40-22, 46-16 to 46-19 XMLBroker property 31-40
Type Library 40-22, 41-19 to 41-20 XMLDataFile property 30-2, 32-8
Web Services 38-10 to 38-14 XMLDataSetField property 31-40
XML Data Binding 37-6 to 37-10 XMLMapper 32-2, 32-4 to 32-6
WM_PAINT messages 12-2 XP themes 9-54
word wrapping 7-7 XSBuiltIns unit 38-5, 38-6
WordWrap property 7-7, 10-3 XSD file 37-2
data-aware memo controls 20-9 XSLPageProducer 35-4
Wrap property 9-50
Wrapable property 9-50 Z
Write By Reference, COM interface properties 41-9
-Z compiler directive 16-13
Write method, TFileStream 5-2
WriteBuffer method, TFileStream 5-2