Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
For Windows
44
46
52
57
60
63
Procedures
Getting Started
Adding Components to a Form .......................................................................................................................
Adding References ..........................................................................................................................................
Adding and Removing Files ............................................................................................................................
Adding Templates to the Object Repository ....................................................................................................
Copying References to a Local Path ...............................................................................................................
Creating a Component Template ....................................................................................................................
Creating a Project ...........................................................................................................................................
Customizing the Form .....................................................................................................................................
Customizing Toolbars .....................................................................................................................................
Customizing the Tool Palette ..........................................................................................................................
Docking Tool Windows ....................................................................................................................................
Finding Items on the Tool Palette ...................................................................................................................
Exploring .NET Assembly Metadata ...............................................................................................................
Exploring Windows Type Libraries ..................................................................................................................
Installing Custom Components .......................................................................................................................
Renaming Files Using the Project Manager ....................................................................................................
3
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
Code Visualization
Adding Shortcuts .............................................................................................................................................
Adding Multiple Elements ................................................................................................................................
Annotating Diagrams .......................................................................................................................................
Using Automated Layout Features ..................................................................................................................
Setting Compartment Controls ........................................................................................................................
Configuring Diagram Options ..........................................................................................................................
Creating Associations .....................................................................................................................................
Creating Class Diagrams ................................................................................................................................
Drawing Links ..................................................................................................................................................
Drawing Links with Bending Points .................................................................................................................
Hiding and Showing Diagram Elements and Links .........................................................................................
Hyperlinking Diagrams ....................................................................................................................................
Exporting Diagram to Image ...........................................................................................................................
Using the Model View .....................................................................................................................................
Moving and Copying Diagram Elements .........................................................................................................
Using the Overview .........................................................................................................................................
Placing Node Elements ...................................................................................................................................
Printing Diagrams ............................................................................................................................................
Resizing Elements ..........................................................................................................................................
Selecting Elements in Diagrams .....................................................................................................................
Synchronizing with the Model View .................................................................................................................
Zooming ..........................................................................................................................................................
140
141
142
143
144
145
146
147
149
150
151
152
154
155
156
157
158
159
160
161
162
163
165
167
168
169
171
134
Debugging Applications
Adding a Watch ...............................................................................................................................................
Attaching to a Running Process ......................................................................................................................
Setting and Modifying Breakpoints .................................................................................................................
Inspecting and Changing the Value of Data Elements ....................................................................................
Resolving Internal Errors .................................................................................................................................
Modifying Variable Expressions ......................................................................................................................
Preparing a Project for Debugging .................................................................................................................
Refactoring Code ............................................................................................................................................
174
175
176
179
181
183
184
185
Deploying Applications
Building Packages ........................................................................................................................................... 165
Linking Delphi Units Into an Application .......................................................................................................... 168
Editing Code
193
194
167
169
198
185
171
203
204
206
208
209
211
Localizing Applications
Adding Languages to a Project .......................................................................................................................
Editing Resource Files in the Translation Manager ........................................................................................
Setting the Active Language for a Project .......................................................................................................
Setting Up the External Translation Manager .................................................................................................
Updating Resource Modules ...........................................................................................................................
Using the External Translation Manager .........................................................................................................
213
215
217
218
220
221
223
224
225
226
227
228
230
232
233
235
237
239
240
243
244
245
246
247
248
250
252
253
254
255
259
260
262
Testing Code
Building Tests .................................................................................................................................................. 264
268
270
275
281
287
291
293
296
299
303
306
308
309
311
312
314
316
323
342
345
348
352
356
358
359
360
366
369
374
383
385
389
Procedures
ASP.NET
Adding Web References in ASP.NET Projects ...............................................................................................
Building an ASP.NET Application ...................................................................................................................
Building an ASP.NET Database Application ...................................................................................................
Building an ASP.NET "Hello World" Application .............................................................................................
Creating a Briefcase Application with DB Web Controls .................................................................................
Building an Application with ...........................................................................................................................
Converting HTML Elements to Server Controls ..............................................................................................
Creating Metadata for a DataSet ....................................................................................................................
Creating an XML File for DB Web Controls ....................................................................................................
Creating a Virtual Directory .............................................................................................................................
Adding Aggregate Values with DBWebAggregateControl ..............................................................................
Debugging Delphi 8 ASP.NET Applications ....................................................................................................
Generating HTTP Messages in ASP.NET ......................................................................................................
Modifying Database Connections ...................................................................................................................
Porting a Delphi for Win32 Web Service Client Application to Delphi for .NET ..............................................
Binding Columns in the DBWebGrid ...............................................................................................................
Setting Permissions for XML File Use .............................................................................................................
Troubleshooting ASP.NET Applications ..........................................................................................................
Using the DB Web Control Wizard ..................................................................................................................
Using the ASP.NET Deployment Manager .....................................................................................................
Using the HTML Tag Editor .............................................................................................................................
Working with ASP.NET User Controls ............................................................................................................
395
399
400
403
405
406
408
410
411
412
413
414
415
416
422
425
426
427
430
431
434
436
Database
Adding a New Connection to the Data Explorer ..............................................................................................
Browsing a Database in the Data Explorer .....................................................................................................
Building an ASP.NET Database Application ...................................................................................................
Creating a Briefcase Application with DB Web Controls .................................................................................
Building a Windows Forms Database Application ...........................................................................................
Building an Application with ...........................................................................................................................
Creating Database Projects from the Data Explorer .......................................................................................
Creating Table Mappings ................................................................................................................................
Creating Metadata for a DataSet ....................................................................................................................
Creating an XML File for DB Web Controls ....................................................................................................
Adding Aggregate Values with DBWebAggregateControl ..............................................................................
Executing SQL in the Data Explorer ...............................................................................................................
Handling Errors in Table Mapping ...................................................................................................................
Migrating Data Between Databases ................................................................................................................
Modifying Connections in the Data Explorer ...................................................................................................
Modifying Database Connections ...................................................................................................................
Building a Database Application that Resolves to Multiple Tables .................................................................
Passing Parameters in a Database Application ..............................................................................................
Binding Columns in the DBWebGrid ...............................................................................................................
Using the Data Adapter Preview ....................................................................................................................
Using the Command Text Editor .....................................................................................................................
Using the Data Adapter Designer ...................................................................................................................
7
438
439
400
405
444
406
449
450
410
411
413
455
456
457
459
416
467
469
425
473
474
475
476
477
481
483
430
488
489
490
491
492
495
497
498
499
500
502
503
504
506
508
511
515
518
520
522
523
524
527
529
Reporting
Adding a Report to Your Project .....................................................................................................................
Selecting Crystal Reports ActiveX Components .............................................................................................
Modifying an Existing Report ..........................................................................................................................
Creating a New Report Object ........................................................................................................................
532
533
534
535
537
538
540
542
543
545
546
547
550
552
554
555
556
557
559
562
395
568
422
Windows Forms
Building a Windows Forms Database Application ...........................................................................................
Building a Windows Forms Application ...........................................................................................................
Building a Windows Forms Hello World Application .......................................................................................
Building Windows Forms Menus .....................................................................................................................
Passing Parameters in a Database Application ..............................................................................................
444
577
578
579
469
Procedures
Database
Accessing Schema Information .......................................................................................................................
Configuring TSQL Connection ........................................................................................................................
Connecting to the Application Server using DataSnap Components ..............................................................
Debugging dbExpress Applications using TSQLMonitor ................................................................................
Executing the Commands using TSQLDataSet ..............................................................................................
Fetching the Data using TSQLDataSet ...........................................................................................................
Specifying the Data to Display using TSQLDataSet .......................................................................................
Specifying the Provider using TLocalConnection or TConnectionBroker .......................................................
Using BDE .......................................................................................................................................................
Using DataSnap ..............................................................................................................................................
Using dbExpress .............................................................................................................................................
Using TBatchMove ..........................................................................................................................................
Connecting to Databases with TDatabase ......................................................................................................
Using TQuery ..................................................................................................................................................
Managing Database Sessions Using TSession ..............................................................................................
Using TSimpleDataSet ....................................................................................................................................
Using TSimpleObjectBroker ............................................................................................................................
Using TSQLQuery ...........................................................................................................................................
Using TSQLStoredProc ...................................................................................................................................
Using TSQLTable ............................................................................................................................................
Using TStoredProc ..........................................................................................................................................
Using TTable ...................................................................................................................................................
Using TUpdateSQL to Update a Dataset ........................................................................................................
618
619
621
622
623
625
626
628
629
630
631
632
633
635
637
638
639
640
641
642
643
644
646
Interoperable Applications
Using COM Wizards ........................................................................................................................................ 648
10
Reporting
Adding Rave Reports to ................................................................................................................................. 650
VCL
Building Application Menus .............................................................................................................................
Building a Windows Application ......................................................................................................................
Building a Windows "Hello World" Application ................................................................................................
Building a VCL Forms Application with Decision Support Components .........................................................
Building VCL Forms Applications With Graphics ............................................................................................
Building a VCL Forms MDI Application Using a Wizard ..................................................................................
Building a VCL Forms MDI Application Without Using a Wizard ....................................................................
Building a VCL Forms SDI Application ............................................................................................................
Creating a New VCL Component ....................................................................................................................
Building a VCL Forms ADO Database Application .........................................................................................
Building a VCL Forms dbExpress Database Application ................................................................................
Building a VCL Forms Application ...................................................................................................................
Creating Actions in a VCL Forms Application .................................................................................................
Building a VCL Forms "Hello world" Application .............................................................................................
Using ActionManager to Create Actions in a VCL Forms Application .............................................................
Building an Application with XML Components ...............................................................................................
Copying Data From One Stream To Another ..................................................................................................
Copying a Complete String List .......................................................................................................................
Creating Strings ..............................................................................................................................................
Creating a VCL Form Instance Using a Local Variable ...................................................................................
Deleting Strings ...............................................................................................................................................
Displaying an Auto-Created VCL Form ...........................................................................................................
Displaying a Bitmap Image in a VCL Forms Application .................................................................................
Displaying a Full View Bitmap Image in a VCL Forms Application .................................................................
Drawing a Polygon in a VCL Forms Application .............................................................................................
Drawing Rectangles and Ellipses in a VCL Forms Application .......................................................................
Drawing a Rounded Rectangle in a VCL Forms Application ...........................................................................
Drawing Straight Lines In a VCL Forms Application .......................................................................................
Dynamically Creating a VCL Modal Form .......................................................................................................
Dynamically Creating a VCL Modeless Form .................................................................................................
Iterating Through Strings in a List ..................................................................................................................
Building a Multithreaded Application ...............................................................................................................
Writing Cleanup Code .....................................................................................................................................
Avoiding Simultaneous Thread Access to the Same Memory ........................................................................
Defining the Thread Object .............................................................................................................................
Handling Exceptions .......................................................................................................................................
Initializing a Thread .........................................................................................................................................
Using the Main VCL Thread ............................................................................................................................
Waiting for Threads .........................................................................................................................................
Writing the Thread Function ............................................................................................................................
Placing A Bitmap Image in a Control in a VCL Forms Application ..................................................................
Renaming Files ...............................................................................................................................................
Reading a String and Writing It To a File ........................................................................................................
Adding and Sorting Strings .............................................................................................................................
Creating a VCL Forms ActiveX Button ............................................................................................................
Creating a VCL Forms ActiveX Active Form ...................................................................................................
Building a VCL Forms Web Browser Application ............................................................................................
652
654
655
656
537
660
661
664
665
667
669
542
543
674
546
547
680
682
684
686
688
690
552
694
696
554
555
556
700
702
704
706
707
708
710
713
714
715
717
719
720
722
723
724
726
728
730
Debugging a WebSnap Application using the Web Application Debugger ..................................................... 736
740
740
741
741
109
111
743
744
745
745
746
747
748
748
749
750
750
751
753
753
754
755
755
756
757
757
759
760
761
761
762
762
763
763
764
765
765
766
766
769
770
771
772
772
772
774
774
774
776
776
776
776
777
778
778
779
779
780
781
781
782
783
784
784
784
704
784
785
682
785
785
786
787
789
790
791
792
792
792
793
793
794
794
795
795
796
796
798
799
802
803
803
804
805
807
808
808
809
810
810
811
132
814
814
815
815
815
815
816
816
816
816
817
817
818
818
820
14
821
821
822
822
823
823
823
823
824
824
824
825
825
826
826
826
827
828
828
829
829
829
830
830
831
831
832
832
832
833
833
834
835
835
835
836
836
836
837
837
838
840
842
843
843
844
844
845
845
845
846
846
846
847
848
848
849
849
850
850
851
851
851
851
852
852
852
852
852
853
853
853
853
854
854
855
855
856
856
857
857
858
858
859
859
859
859
860
861
861
861
862
862
862
863
863
864
864
865
865
866
866
866
867
867
868
870
870
871
871
872
873
873
873
873
875
875
876
876
877
877
878
878
879
879
880
880
881
882
882
883
883
884
884
885
885
886
886
886
887
888
888
889
889
890
890
891
892
893
894
894
894
894
895
895
896
896
897
897
897
898
898
898
899
899
899
899
900
900
900
901
901
901
901
902
902
Types of controls
Text Controls ...................................................................................................................................................
Edit Controls ....................................................................................................................................................
Memo and Rich Edit Controls .........................................................................................................................
Text Viewing Controls .....................................................................................................................................
Labels ..............................................................................................................................................................
Specialized Input Controls ..............................................................................................................................
Scroll Bars .......................................................................................................................................................
Track Bars .......................................................................................................................................................
Up-down Controls (VCL Only) .........................................................................................................................
Hot Key Controls (VCL Only) ..........................................................................................................................
Splitter Controls ...............................................................................................................................................
17
904
904
905
905
906
906
907
907
908
908
908
908
909
909
909
909
910
910
910
910
911
911
912
912
912
913
913
913
913
914
914
914
914
915
915
915
915
915
916
916
916
917
917
917
917
917
918
918
919
919
920
921
921
922
922
922
923
923
923
924
924
924
925
925
925
925
926
926
926
926
926
927
927
928
928
926
929
930
930
930
930
931
931
932
932
932
933
934
934
934
935
936
936
936
937
937
937
938
938
938
938
939
940
940
941
942
944
944
945
719
715
948
948
948
949
949
949
950
950
950
951
951
951
952
953
953
953
953
954
955
Exception handling
Exception Handling .........................................................................................................................................
Defining Protected Blocks ...............................................................................................................................
Writing the Try Block .......................................................................................................................................
Raising an Exception ......................................................................................................................................
Writing Exception Handlers .............................................................................................................................
Exception-handling Statements ......................................................................................................................
Handling Classes of Exceptions ......................................................................................................................
Scope of Exception Handlers ..........................................................................................................................
Reraising Exceptions ......................................................................................................................................
Writing Finally Blocks ......................................................................................................................................
Writing a Finally Block .....................................................................................................................................
Handling Exceptions in VCL Applications .......................................................................................................
VCL Exception Classes ...................................................................................................................................
Default Exception Handling in VCL .................................................................................................................
Silent Exceptions .............................................................................................................................................
Defining Your Own VCL Exceptions ...............................................................................................................
957
957
958
958
959
959
961
961
962
962
963
964
964
965
966
966
20
968
969
969
970
970
971
971
972
972
972
973
973
974
974
975
976
976
977
978
978
978
979
981
981
981
981
982
982
982
982
982
983
984
984
985
985
985
986
986
986
987
987
987
987
988
989
107
Deploying applications
Deploying Applications: Overview ................................................................................................................... 991
Deploying General Applications ...................................................................................................................... 991
Using Installation Programs ............................................................................................................................ 992
Identifying Application Files ............................................................................................................................. 992
Application Files, Listed by File Name Extension ........................................................................................... 992
Package Files .................................................................................................................................................. 992
Merge Modules ............................................................................................................................................... 993
ActiveX Controls .............................................................................................................................................. 994
Helper Applications ......................................................................................................................................... 994
DLL Locations ................................................................................................................................................. 994
Deploying Database Applications ................................................................................................................... 995
Deploying dbExpress Database Applications ................................................................................................. 995
Deploying BDE Applications ........................................................................................................................... 996
Borland Database Engine ............................................................................................................................... 996
Deploying Multi-tiered Database Applications (DataSnap) ............................................................................. 997
Deploying Web Applications ........................................................................................................................... 997
Deploying On Apache Servers ........................................................................................................................ 997
Programming for Varying Host Environments ................................................................................................. 999
Screen Resolutions and Color Depths ............................................................................................................ 999
Considerations When Not Dynamically Resizing ............................................................................................ 999
Considerations When Dynamically Resizing Forms and Controls ................................................................ 1000
Accommodating Varying Color Depths ......................................................................................................... 1000
Fonts ............................................................................................................................................................. 1001
Operating System Versions .......................................................................................................................... 1001
Software License Requirements ................................................................................................................... 1001
21
1017
1017
1018
1019
1019
1019
1020
1020
1021
1021
1022
1022
1022
1022
1023
1023
1023
1024
1024
1024
1025
1026
1027
1027
1028
1028
1029
1030
1031
1031
1032
1032
1033
1034
1034
1034
1036
1037
1037
1037
1038
1040
1040
1041
1041
1043
1043
1044
1044
1047
23
1048
1048
1049
1050
1050
1050
1051
1052
1052
1053
1053
1053
1054
1054
1054
1054
1055
1055
1056
1056
1056
1056
1057
1057
1057
1057
1058
1058
1058
1059
1060
1060
1061
1061
1061
1061
1062
1062
1062
1062
1063
1063
1063
1063
1064
1064
1064
Connecting to databases
Connecting to Databases: Overview .............................................................................................................
Using Implicit Connections ............................................................................................................................
Controlling Connections ................................................................................................................................
Connecting to a Database Server .................................................................................................................
Disconnecting from a Database Server ........................................................................................................
Controlling Server Login ................................................................................................................................
Managing Transactions .................................................................................................................................
Specifying the Transaction Isolation Level ....................................................................................................
Sending Commands to the Server ................................................................................................................
Working with Associated Datasets ................................................................................................................
Obtaining Metadata .......................................................................................................................................
1065
1066
1066
1067
1067
1067
1069
1072
1072
1074
1075
Understanding datasets
Understanding Datasets: Overview ...............................................................................................................
Using TDataSet Descendants .......................................................................................................................
Determining Dataset States ..........................................................................................................................
Opening and Closing Datasets .....................................................................................................................
Navigating Datasets ......................................................................................................................................
Using the First and Last Methods .................................................................................................................
Using the Next and Prior Methods ................................................................................................................
Using the MoveBy Method ............................................................................................................................
Using the Eof and Bof Properties ..................................................................................................................
Marking and Returning to Records ...............................................................................................................
Searching Datasets .......................................................................................................................................
Using Locate .................................................................................................................................................
Using Lookup ................................................................................................................................................
Displaying and Editing a Subset of Data Using Filters ..................................................................................
Enabling and Disabling Filtering ....................................................................................................................
Creating Filters ..............................................................................................................................................
Setting the Filter Property .............................................................................................................................
Writing an OnFilterRecord Event Handler .....................................................................................................
Setting Filter Options .....................................................................................................................................
Navigating Records in a Filtered Dataset ......................................................................................................
Modifying Data ..............................................................................................................................................
Editing Records .............................................................................................................................................
Adding New Records ....................................................................................................................................
Deleting Records ...........................................................................................................................................
Posting Data ..................................................................................................................................................
Canceling Changes .......................................................................................................................................
Modifying Entire Records ..............................................................................................................................
Calculating Fields ..........................................................................................................................................
24
1077
1078
1078
1079
1080
1081
1081
1082
1082
1084
1085
1085
1086
1087
1087
1087
1088
1089
1089
1090
1090
1091
1092
1093
1093
1094
1094
1095
1096
1097
1098
1098
1099
1099
1099
1100
1101
1101
1101
1101
1102
1102
1102
1105
1105
1106
1106
1108
1108
1109
1111
1112
1112
1113
1114
1115
1116
1116
1117
1118
1118
1119
1119
1122
1122
1122
1124
1125
1125
1126
1127
1127
1128
1129
1129
1130
1131
1131
1132
1132
1133
1134
1134
1135
1135
1135
1136
1136
1137
1138
1138
1139
1139
1140
1140
1141
1141
1141
1142
1143
1144
1145
1146
1193
1193
1194
1195
1196
1196
1196
1197
1197
1197
1198
1198
1200
1200
1201
1201
1202
1202
1203
1203
1204
1204
1204
1205
1205
1206
1207
1207
1207
1208
1208
28
1226
1226
1227
1227
1229
1229
1230
1230
1231
1231
1232
1232
1233
1233
1234
1234
1235
1236
1236
1236
1237
1237
1238
1239
1239
1240
1241
1241
1242
1243
1244
1245
1246
1247
1248
1248
1248
1249
1250
1250
1251
1251
1252
1252
1252
1253
1253
1253
1254
1256
1257
1257
1258
1258
1259
1259
1260
1261
1261
1262
1262
1263
1264
1264
1264
1265
29
1266
1267
1268
1268
1269
1270
1271
1271
1272
1272
1272
1273
1273
1273
1275
1275
1276
1276
1277
1278
1278
1279
1280
1281
1281
1282
1283
1283
1284
1284
1285
1285
1286
1286
1286
1288
1288
1289
1290
1290
1290
1291
1292
1292
1294
1294
1295
1296
1298
1298
1298
1300
1302
1307
1307
1308
1309
1309
1310
1310
1310
1311
1311
1313
1313
1314
1315
1315
1316
1316
1317
1317
1317
1318
1318
1318
1319
1319
1319
1320
1320
1321
1321
1322
1322
1322
1322
1323
1323
1323
1323
1324
1324
1324
1324
1324
1325
1325
1325
1325
1326
1326
1327
1327
1328
1329
1329
1330
1330
1330
1330
1331
1331
1331
1331
1332
Using WebSnap
Creating Web Server Applications Using WebSnap .....................................................................................
Fundamental WebSnap Components ...........................................................................................................
Web Modules ................................................................................................................................................
Web Application Module Types .....................................................................................................................
Web Page Modules .......................................................................................................................................
Web Data Modules ........................................................................................................................................
Adapters ........................................................................................................................................................
Page Producers ............................................................................................................................................
Creating Web Server Applications with WebSnap ........................................................................................
Selecting a Server Type ................................................................................................................................
Specifying Application Module Components .................................................................................................
Selecting Web Application Module Options ..................................................................................................
Advanced HTML Design ...............................................................................................................................
Login Support ................................................................................................................................................
Adding Login Support ....................................................................................................................................
Using the Sessions Service ..........................................................................................................................
Login Pages ..................................................................................................................................................
Setting Pages to Require Logins ...................................................................................................................
User Access Rights .......................................................................................................................................
Server-side Scripting in WebSnap ................................................................................................................
Script Objects ................................................................................................................................................
Dispatching Requests and Responses .........................................................................................................
Dispatcher Components ................................................................................................................................
Adapter Dispatcher Operation .......................................................................................................................
Receiving Adapter Requests and Generating Responses ............................................................................
Dispatching Action Items ...............................................................................................................................
Page dispatcher operation ............................................................................................................................
1333
1334
1334
1335
1335
1336
1336
1337
1338
1339
1340
1341
1342
1343
1343
1344
1345
1347
1347
1349
1350
1351
1351
1352
1353
1355
1355
Using IntraWeb
Creating Web Server Applications Using IntraWeb ......................................................................................
Using IntraWeb Components ........................................................................................................................
Getting Started with IntraWeb .......................................................................................................................
Creating a New IntraWeb Application ...........................................................................................................
Editing the Main Form ...................................................................................................................................
Writing an Event Handler for the Button ........................................................................................................
Running the Completed Application ..............................................................................................................
Using IntraWeb with Web Broker and WebSnap ..........................................................................................
32
1357
1357
1358
1359
1360
1361
1362
1363
1365
1366
1367
1367
1367
1369
1370
1371
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1386
1386
1387
1387
1387
1390
1391
1391
1392
1392
1392
1392
1392
1393
1393
1393
1394
1394
1394
1395
1395
1395
1394
1396
1396
1396
1396
1396
1396
1397
1397
1397
1397
1398
1398
1398
1399
1418
1418
1419
1419
1420
1421
1421
1423
1425
1426
1427
1427
1433
1433
1434
1434
1435
1436
1436
1437
1437
1437
1438
1438
1438
1439
1439
1439
1440
1440
1441
1441
1442
1443
1444
1444
1445
1446
1447
1450
1451
1451
1451
1452
1453
1454
1454
1455
1456
1459
1460
1460
1461
1462
1462
1464
1467
1468
1468
1468
1469
1469
1470
1471
1472
1473
1474
1476
1477
1477
1479
1480
1481
1481
1482
1483
1484
1484
1485
1486
1486
1487
1488
1488
1488
1489
1489
1489
1490
1490
1490
1491
36
1492
1492
1493
1494
1494
1494
1495
1495
1496
1497
1497
1498
1499
1499
1500
1500
1501
1501
1501
1502
1502
1503
1503
1504
1505
1506
1509
1510
1511
1511
1514
1514
1515
1515
1516
1516
1516
1517
1517
1517
1517
1518
1519
1519
1519
1520
1521
1522
1522
1523
1523
1524
1525
1525
1526
1533
1533
1534
1534
1534
1535
1535
1535
1535
1535
1536
1536
1537
1537
1537
1538
1539
1539
1540
1540
1540
1541
1541
1541
Creating events
Creating Events: Overview ............................................................................................................................
What Are Events? .........................................................................................................................................
Events Are Method Pointers .........................................................................................................................
Calling the Click-event Handler .....................................................................................................................
Events Are Properties ...................................................................................................................................
Event Types Are Method-pointer Types ........................................................................................................
Event Handler Types Are Procedures ...........................................................................................................
Event Handlers Are Optional .........................................................................................................................
Implementing the Standard Events ...............................................................................................................
Identifying Standard Events ..........................................................................................................................
Making Events Visible ...................................................................................................................................
Changing the Standard Event Handling ........................................................................................................
Defining Your Own Events ............................................................................................................................
Triggering the Event ......................................................................................................................................
Two Kinds of Events .....................................................................................................................................
Defining the Handler Type ............................................................................................................................
Declaring the Event .......................................................................................................................................
Calling the Event ...........................................................................................................................................
Empty Handlers Must Be Valid .....................................................................................................................
Users Can Override Default Handling ...........................................................................................................
1543
1543
1544
1544
1544
1545
1545
1545
1546
1546
1547
1547
1547
1547
1548
1548
1549
1549
1549
1549
Creating methods
Creating Methods: Overview .........................................................................................................................
Avoiding Interdependencies ..........................................................................................................................
Naming Methods ...........................................................................................................................................
Protecting Methods .......................................................................................................................................
Methods That Should Be Public ....................................................................................................................
Methods That Should Be Protected ..............................................................................................................
Abstract Methods ..........................................................................................................................................
Making Methods Virtual .................................................................................................................................
Declaring Methods ........................................................................................................................................
1550
1550
1551
1551
1551
1551
1552
1552
1552
1553
1554
1554
1554
1555
1555
1555
1556
1556
1556
1557
1557
Handling messages
Handling Messages and System Notifications: Overview .............................................................................
Understanding the message-handling system ..............................................................................................
What's in a Windows Message? ...................................................................................................................
Dispatching Messages ..................................................................................................................................
Changing Message Handling ........................................................................................................................
Overriding the Handler Method .....................................................................................................................
Using Message Parameters ..........................................................................................................................
Trapping Messages .......................................................................................................................................
The WndProc Method ...................................................................................................................................
Creating New Message Handlers .................................................................................................................
Defining Your Own Messages .......................................................................................................................
Declaring a Message Identifier ......................................................................................................................
Declaring a Message-structure Type ............................................................................................................
Declaring a New Message-handling Method ................................................................................................
Sending Messages ........................................................................................................................................
Broadcasting a Message to All Controls in a Form .......................................................................................
Calling a Control's Message Handler Directly ...............................................................................................
Sending a Message Using the Windows Message Queue ...........................................................................
Sending a Message That Does Not Execute Immediately ............................................................................
Responding to Signals ..................................................................................................................................
Assigning Custom Signal Handlers ...............................................................................................................
Responding to System Events ......................................................................................................................
Commonly Used Events ................................................................................................................................
Overriding the EventFilter Method ................................................................................................................
Generating Qt Events ....................................................................................................................................
1558
1558
1558
1559
1560
1560
1560
1560
1561
1561
1561
1561
1562
1562
1562
1563
1563
1564
1564
1564
1565
1566
1566
1567
1568
39
1569
1519
1570
1570
1570
1571
1571
1571
1572
1572
1573
1573
1574
1574
1575
1575
1576
1576
1577
1577
1578
1578
1579
1579
1579
1580
1580
1581
1581
1582
1583
1583
1584
1584
1585
1586
1583
1534
1587
1587
1588
1588
1588
1589
1590
1590
1590
1591
1592
1592
1593
Customizing a grid
Customizing a Grid: Overview .......................................................................................................................
Creating and registering the component .......................................................................................................
Publishing Inherited Properties .....................................................................................................................
Changing Initial Values .................................................................................................................................
Resizing the Cells .........................................................................................................................................
Filling in the Cells ..........................................................................................................................................
Tracking the Date ..........................................................................................................................................
Storing the Internal Date ...............................................................................................................................
Accessing the Day, Month, and Year ............................................................................................................
Generating the Day Numbers .......................................................................................................................
Selecting the Current Day .............................................................................................................................
Navigating Months and Years .......................................................................................................................
Navigating Days ............................................................................................................................................
40
1595
1595
1534
1597
1597
1598
1599
1599
1600
1601
1603
1603
1604
1607
1607
1595
1608
1608
1609
1610
1610
1610
1611
1611
1612
1612
1612
1613
1613
1614
1615
1616
1618
1618
1583
1619
1619
1620
1620
1620
41
1622
1622
1623
1624
1624
1625
1626
1626
1627
1627
1628
1628
1629
1629
1630
1630
1633
Concepts
General
42
Getting Started
The Delphi 2005 integrated development environment (IDE) provides many tools and features to help you build
powerful applications quickly. Not all features and tools are available in all editions of Delphi 2005. For a list of
features and tools included in your edition, refer to the feature matrix on http://www.borland.com/delphi.
43
Defining Requirements
Delphi 2005 provides an interface to Borland CaliberRM, a Web-based requirements definition and management
system designed to help control the product development process. Within the IDE, you can access CaliberRM to
collaborate on project requirements and ensure that your applications meets end-user needs.
Modeling Applications
Modeling can help you can improve the performance, effectiveness, and maintainability of your applications by
creating a detailed visual design before you ever write a line of code. Delphi 2005 provides UML-based class
diagramming tools and a framework of Enterprise Core Objects (ECO) to help you create model-powered .NET
applications.
44
45
IDE
The IDE now provides support for Delphi for .NET, Delphi for Win32, and C# application development. An icon
in the IDE toolbars indicates the current development environment: Delphi for .NET
, or C#
Updating and saving files in the IDE now creates multiple backup files in the hidden __history directory of the
current directory. Use the new Create backup files and File backup limit options on the Tools Options
Editor Options page to control the creation of backup files. By default, up to 10 backup files are created.
The new History Manager page lets you see and compare prior versions of a file, including multiple backup
versions, saved local changes, and the buffer of unsaved changes for the active file. You can revert a prior
version to the current version and use synchronized scrolling to navigate two file versions.
The new Structure View shows the hierarchy of source code and HTML displayed in the Code Editor, or
components displayed on the Designer. When displaying the structure of source code or HTML, you can doubleclick an item to jump to its declaration or location in the Code Editor. When displaying components, you can
double-click a component to select it on the form. If you code contains syntax errors, they appear in the
Errors folder of the view.
The Import Component Wizard lets you import existing ActiveX, .NET, or VCL components to a new or existing
package.
A new environment option lets you choose whether VCL forms are displayed on the Design tab or in an
undocked, floating window. Choose Tools
Options
Environment Options
Delphi Options
VCL
Designer and uncheck the Embedded Designer option to enable the floating VCL form.
The Object Inspector now retains form information when the Code Editor is displayed.
The Tool Palette Search text box has been replaced with the filter icon
. To filter the items displayed in
the Tool Palette, click anywhere in the Tool Palette and begin typing the item name you want to find. To remove
the search filter, click the filter icon.
The new Tool Palette and Colors pages in Tools Options control the appearance of the Tool Palette and
replace several of the Tool Palette context menu commands.
Code Editor
Refactoring features allow you to restructure and modify your code in such a way that the intended behavior of
your code stays the same. Refactoring lets you streamline and improve code readability and performance.
Refactoring in Delphi 2005 includes Extract Method, Symbol Rename, Declare Variables and Fields, Find Units
and Namespaces, and more.
The new Sync Edit feature lets you simultaneously edit duplicate identifiers in code. If you select a block of code
that appears
that contains duplicate identifiers, for example, label1, and click the Sync Edit Mode icon
in the left gutter, all of the duplicated identifiers are highlighted and the cursor is positioned to the first identifier.
Code Editor bookmarks are now preserved when you close a file. The Code Editor context menu Clear
Bookmarks command removes all bookmarks from a file.
46
code block is prefixed with //. When using the Visual Studio or Visual Basic key mappings, use CTRL+K+C.
Debugging
The IDE now supports both the Borland .NET Debugger and the Borland Win32 Debugger. The IDE will
automatically use the appropriate debugger based on the current project type. However, when attaching to a
process (using Run Attach to Process) or loading a process (using Run Load Process), you can manually
select either of the debuggers.
A new IDE command line switch, debugger=[borwin32|bordonet], lets you select either the Borland Win32
Debugger or Borland .NET Debugger when debugging from the command line.
Cross-platform (Win32 and .NET) debugging within a project group is supported and, where possible, the
debuggers share a common user interface.
The new Borland.dbkasp.dll provides improved debugging for ASP.NET applications.
The Modules window now displays multiple application domains as separate processes. Clicking a domain
node displays a scope tree view of the namespaces, classes, and methods in the right pane of the window.
The modules pane within the Modules window can now be sorted by module name, base address, or path by
clicking the appropriate column heading.
The Local Variables window now supports viewing local variables from a non-current frame when debugging
Delphi Win32 applications (previously this feature was only available when debugging Delphi .NET, C#, and C
++ applications). Additionally, the variable name and value are now displayed in separate columns for
readability.
The Breakpoint List window has a new check box for enabling and disabling individual breakpoints and a
toolbar for managing breakpoints. Additionally, the Breakpoint List window supports in-place editing of the
condition, pass count, and group by clicking on the current value in those fields.
When debugging Delphi Win32 applications, the Call Stack window now includes information for stack frames
that do not have debug information.
When customizing breakpoints in the Breakpoint Properties dialog box, the new Log Call Stack check box
lets you to display part or all of the call stack in the debug event log when a breakpoint is encountered.
The Debugger Exception Notification dialog box now includes options to break or continue execution, show
the Debug Inspector for the exception object, and show the CPU View.
When debugging managed code, the CPU window now displays Microsoft Intermediate Language (MSIL)
instructions. The CPU window context menu has a new Mixed IL Code command to toggle the display of MSIL
code.
The FPU window now displays Streaming SIMD Extensions (SSE) registers in the SSE pane. You can use the
SSE pane context menu Display As command to display the register content as quad, double-quad, single, or
double words.
All of the debugger windows are now Unicode-enabled, allowing them to display and process international
characters.
47
ECO Framework
The ECO framework is now supported in ASP.NET and ASP.NET web service applications.
The ECO Space designer now contains a tool that allows you to generate code and an object-relational
mapping for an existing (non-ECO) database.
The ECO Space designer contains a tool to upgrade an existing ECO project to the current release. See the
topic Upgrading an ECO framework Project from a Prior Release for more information.
In the ECO Package Selection dialog box, the flyover hint for selected packages now displays all classes in
that package.
Flyover hints for the components on the ECO Space designer show tasks that have been done, and that need
to be done.
You can create an ECO model in a DLL, and then reference that DLL in another project.
The new PersistenceMapperProvider code template generates a class that allows thread-safe pooling of
database connections, and sharing a single PersistenceMapper among multiple ECO Space instances.
HTML
A new DOM-based HTML formatter provides improved HTML formatting.
The HTML Tag Editor now lets you edit the entire tag, not just the inner HTML.
The HTML Tag Editor can now be used to edit most HTML tags, with the exception of body, tbody, tr,
caption, and tfoot.
Syntax highlighting and Code Completion (CTRL+SPACE) are now available for Cascading Style Sheet (CSS)
files.
The new Structure View shows the hierarchy of the HTML displayed in the Code Editor. Double-clicking an
element in the Structure View jumps to its location in the Code Editor.
The product includes the August 2004 version of HTML Tidy.
Template editors are now available on context menus for both the DataList and DataGrid controls.
The Project Manager context menu contains new commands for creating a folder within the project folder
(New Folder), creating additional files in the project (New Other), and showing all of the files in the project
folder (Show All Files).
The new Select URL dialog box provides greater control over URL references. The dialog box is displayed
when you click the ellipsis in the Object Inspector for a URL property, such the ImageURL property of the
Image Web Control. You can specify and preview a user specified, document relative, or root relative URL.
The new Run As Server Control command converts an HTML element to a server control, enabling
programmatic control of those elements. The command adds the id="id" and runat="server" attributes
to .aspx file and declares the element in the code-behind file. Right-click an HTML element on the Designer to
use the command.
You can now select and move multiple controls on a Web Form by pressing SHIFT key and dragging the controls.
Database Development
Many changes have been made to improve support for database application development in Delphi 2005.
BDP.NET Updates
BDP.NET has been updated in several areas to provide added features and improved support for data providers:
Stored Procedure dialogwhen you set the CommandType property for a BdpCommand component to
StoredProcedure, clicking Command Text Editor in the Designer Verb area at the bottom of the Object
Inspector to open the Stored Procedure dialog box. This dialog box lets you specify the stored procedure you
want to use, set input parameters, and execute the stored procedure. If there are output parameters, they are
displayed when the stored procedure is executed.
Sybase 12.5 supportBDP.NET includes the Borland.Data.Sybase namespace to support Sybase 12.5 as a
provider. This gives you the benefits of BDP.NET, such as its rich set of component designers, live data at
designtime, when you develop a database application for Sybase.
Provider enhancementsmultiple updates have been made to improve the reliability and performance for
supported providers.
BdpCopyTablethe BdpCopyTable class lets you programmatically migrate data from one provider to another.
BdpCopyTable maps the data structure, creates the primary key, and copies data. This class does not currently
support creating foreign keys or copying dependent objects.
Multi-table resolvingthe added DataHub and DataSync classes let you connect your database application to
multiple data providers through a collection of DataAdpaters. The DataSync class lets you specify the commit
behavior for resolving transactions across multiple tables.
Data remotingthe DataHub and DataSync classes can be used in conjunction with the added
RemoteConnection and RemoteServer classes to develop a multi-tier, distributed database architecture, where
applications running on different machines or domains can communicate with each other. The DataHub and
RemoteConnection classes are part of the client application, and they connect to the remotely located DataSync
and RemoteServer classes.
Table and column mapping support for the BdpDataAdpater has been added.
49
The Data Explorer supports data migration from one data provider to another. The context menu for tables
includes Migrate Data, Copy Table, and Paste Table commands, which let you copy a table of data from one
provider and paste it as a new table into another provider.
Drag-and-drop stored proceduresstored procedures can be dragged from a provider in the Data Explorer
onto a form in the Designer. This adds and configures a BdpConnection and a BdpCommand component and
automatically populates the acquired stored procedure parameters.
Metadata servicesthe Data Explorer provides a variety of metadata services that allow you to view and modify
your database schema. Using the context menu in the Data Explorer, you can retrieve data from a table for
viewing, add a new table, drop an existing table, or alter the data structure of an existing table. You can retrieve
data associated with a view. You can also view and modify (input only) the parameters for a stored procedure,
and execute the stored procedure.
The compiler now supports function and procedure inlining. See the Delphi Language Guide topic Calling
Procedures and Functions for more information.
The Delphi language has been expanded to include alphabetic and alphanumeric Unicode characters in
identifiers. Note: Unicode characters are not allowed in identifiers in published sections of classes, or in types
used by published members.
The language now supports the aggregation of multiple units within a namespace. See the topic Using
Namespaces with Delphi for more information.
The Delphi for .NET compiler now supports dynamically allocated multi-dimensional arrays. See the Delphi
Language Guide topic Structured Types for more information.
Testing
Delphi 2005 integrates the capabilities of DUnit and NUnit, open source testing frameworks.
DUnit provides the capability to add test cases and test suites to your Delphi projects. DUnit includes a GUI test
runner that you can invoke from within the IDE to interactively run your project tests. Additionally, DUnit provides
a console mode testing capability.
NUnit provides the capability to add test cases and test suites to your C# projects. NUnit includes a GUI test
runner that you can invoke from within the IDE to interactively run your project tests. Additionally, DUnit provides
a console mode testing capability.
You can build DUnit and NUnit test fixtures with the Test Case Wizard.
You can add individual test cases to test suites with the Test Suite Wizard.
Translation Tools
The Translation Tools options have been incorporated into the Tools
The Translation Tools windows are now docked within the IDE.
51
Welcome Page
When you open Delphi 2005, the Welcome Page appears with a number of links to developer resources, such as
product-related articles, training, and online Help. As you develop projects, you can quickly access them from the
list recent projects at the top of the page. If you close the Welcome Page, you can reopen it by choosing View
Welcome Page.
Forms
Typically, a form represents a window or HTML page in a user interface. At design-time, a form is displayed on the
Designer surface. You add components from the Tool Palette to a form to create your user interface.
Delphi 2005 provides several types of forms, as described in the following sections. Select the form that best suits
your application design, whether its a Web application that provides business logic functionality over the Web, or a
Windows application that provides processing and high-performance content display. To switch between the
Designer and Code Editor, click their associated tabs below the IDE.
To access forms, choose File
New
Other.
Windows Forms
Use Windows Forms to build native Windows applications that run in a managed environment. You use the .NET
classes to build Windows clients which presents two major advantagesit allows application clients to use features
unavailable to browser clients, and it leverages the .NET Framework infrastructure. Windows Forms present a
programming model that takes advantage of a unified .NET Framework (for security and dynamic application
updates, for instance) and the richness of GUI Windows clients. You use Windows controls, such as buttons, list
boxes, and text boxes, to build your Windows applications.
52
New
Other
Windows Forms
New
Other
ASP.NET Web
VCL Forms
Use VCL Forms to create applications that use VCL.NET components to run in the .NET Framework. You use the
Borland Visual Component Library for .NET classes to create a VCL Forms application.
VCL Forms are especially useful if you want to port an existing Delphi application containing VCL controls to
the .NET environment, or if you are already familiar with the VCL and prefer to use it.
To access a VCL Forms, choose File
New
Other
HTML Designer
Use the HTML Designer to view and edit ASP.NET Web Forms or HTML pages. This Designer provides a Tag
Editor for editing HTML tags alongside the visual representation of the form or page. You can also use the Object
Inspector to edit properties of the visible items on the HTML page and to display the properties of any current HTML
tag in the Tag Editor. A combo box located above the Tag Editor lets you display and edit SCRIPT tags.
To create a new HTML file, choose File
New
Other
Web Documents
HTML Page.
Designer Surface
The Designer surface, or Designer, is displayed automatically when you are using a form. The appearance and
functionality of the Designer depends on the type of form you are using. For example, if you are using an ASP.NET
Web Form, the Designer will display an HTML tag editor. To access the Designer, click the Design tab at the bottom
of the IDE.
Visual Components
Visual components appear on the form at design-time and are visible to the end user at runtime. They include such
things as buttons, labels, toolbars, and listboxes.
Tool Palette
The Tool Palette contains items to help you develop your application. The items displayed depend on the current
view. For example, if you are viewing a form on the Designer, the Tool Palette displays components that are
53
appropriate for that form. You can double-click a control to add it to your form. If you are viewing code in the Code
Editor, the Tool Palette displays code snippets that you can add to your code.
Customized Components
In addition to the components that are installed with Delphi 2005, you can add customized or third party components
to the Tool Palette and save them in their own category.
Component Templates
You can create templates that are made up of one or more components. After arranging components on a form,
setting their properties, and writing code for them, you can save them as a component template. Later, by selecting
the template from the Tool Palette, you can place the preconfigured components on a form in a single step; all
associated properties and event-handling code are added to your project at the same time. You can reposition the
components independently, reset their properties, and create or modify event handlers for them just as if you had
placed each component in a separate operation.
Object Inspector
The Object Inspector lets you set designtime properties and create event handlers for components. This provides
the connection between your applications visual appearance and the code that makes the application run. The
Object Inspector contains two tabs: Properties and Events.
Use the Properties tab to change physical attributes of your components. Depending on your selection, some
category options let you enter values in a text box while others require you to select values from a drop-down box.
For Boolean operations, you toggle between True or False. After you change your components physical attributes,
you create event handlers that control how the components function.
Use the Events tab to specify the event of a specific object you select. If there is an existing event handler, use the
drop-down box to select it. By default, some options in the Object Inspector are collapsed. To expand the options,
click the plus sign (+) next to the category.
Certain nonvisual components, for example, the Borland Data Providers, allow quick access to editors such as the
Connection Editor and Command Text Editor. You can access these editors in the Designer Verb area at the
bottom of the Object Inspector. To open the editors, point your cursor over the name of the editor until your cursor
changes into a hand and the editor turns into a link. Alternatively, you can right-click the nonvisual component, scroll
down to its associated editor and select it. Note that not all nonvisual components have associated editors. In addition
to editors, this area can also display hyperlinks to show custom component editors, launch a web page and show
dialog boxes.
Object Repository
To simplify development, Delphi 2005 offers predesigned templates, forms, and other items that you can easily
access and use in your application. The Object Repository is accessible by choosing File New Other. A New
Items dialog box appears, displaying the contents of the Object Repository . You can also edit or remove existing
objects from the Object Repository by right-clicking the Object Repository to view your editing options.
54
Project Manager
A project is made up of several application files. The Project Manager lets you view and organize your project files
such as forms, executables, assemblies, objects and library files. Within the Project Manager, you can add, remove,
and rename files. You can also combine related projects to form project group, which you can compile at the same
time.
Add References
You can integrate your legacy COM servers and ActiveX controls into managed applications by adding references
to unmanaged DLLs to your project, and then browse the types just as you would with managed assemblies. Choose
Project
Add Reference to integrate your legacy COM servers or ActiveX controls. Alternatively, right-click the
Reference folder in the Project Manager and click Add Reference. You can add other .NET assemblies, COM/
ActiveX components, or type libraries using the Add Reference feature.
Data Explorer
The Data Explorer lets you browse database server-specific schema objects including tables, fields, stored
procedure definitions, triggers, and indexes. Using the context menus, you can create and manage database
connections. You can also drag and drop data from a data source to most forms to build your database application
quickly.
Structure View
The Structure View shows the hierarchy of source code or HTML displayed in the Code Editor, or components
displayed on the Designer. When displaying the structure of source code or HTML, you can double-click an item to
jump to its declaration or location in the Code Editor. When displaying components, you can double-click a
component to select it on the form.
55
If your code contains syntax errors, they are displayed in the Errors folder in the Structure View. You can doubleclick an error to locate its source in the Code Editor.
You can control the content and appearance of the Structure View by choosing Tools
Options
Explorer and changing the settings.
Options
Environment
History Manager
The History Manager lets you see and compare versions of a file, including multiple backup versions, saved local
changes, and the buffer of unsaved changes for the active file. If the current file is under version control, all types
of revisions are available in the History Manager. The History Manager is displayed to the right of the Code tab
and contains the following tabbed pages:
The Contents page displays current and previous versions of the file.
The Diff page displays differences between selected versions of the file.
The Info page displays all labels and comments for the active file.
You can use the History Manager toolbar to refresh revision information, revert a selected version to the most
current version, and synchronize scrolling between the source viewers in the Contents or Diff pages and the Code
Editor.
Code Editor
The Code Editor provides a convenient way to view and modify your source code. It is a full-featured, customizable,
ANSI editor that provides refactoring, automatic backups, Code Insight, syntax highlighting, multiple undo capability,
context-sensitive Help, and more.
56
Starting a Project
A project is a collection of files which includes, but is not limited to, project files (.bdsproj), assemblies (.dll), program
database files (.pdb), optional resource files (.html, .jpeg, .gif), executables (.exe), and many others that make up
an application. Projects are either created at design time or generated when you compile the project source code.
To assist in the development process, the Object Repository offers many predesigned templates, forms, files, and
other items that you can use to create applications.
To create a project, click New from the Welcome Page and select the type of application you want to create, or
choose File
New
Other. To open an existing project, click Project from the Welcome Page or choose File
Open Project.
This section includes information about:
Types of projects
Working with unmanaged code
Type of Projects
Depending on the edition of Delphi 2005 that you are using, you can create traditional Windows applications, ASP.
NET Web applications, ADO.NET database applications, Web Services applications, and many others. Delphi 2005
also supports assemblies, custom components, multi-threading, and COM. For a list of the features and tools
included in your edition, refer to the feature matrix on either the Borland Delphi web page or the Borland C#Builder
web page.
Windows Applications
You can create Windows applications using Windows Forms to provide processing and high-performance content
display. Windows applications can function as a front end to ADO.NET databases.
In addition to drag and drop components and visual designers, Borland provides an easy way to create application
menus and submenus. The .NET Menu Designers MainMenu and ContextMenu are components that work like
editors to let you visually design menus and quickly code their functionality.
57
VCL.NET Applications
You can use VCL Forms to create a .NET Windows application that uses components from the VCL.NET framework.
Delphi 2005 simplifies the task of building .NET-enabled applications by supporting VCL components that have been
augmented to run on the .NET Framework. This eliminates the need for you to create custom components to provide
standard VCL component capabilities. This makes the process of porting Win32 applications to .NET much simpler
and reliable.
Database Applications
Whether your application uses Windows Forms, Web Forms, or VCL Forms, Delphi 2005 has several tools that make
it easy to connect to a database, browse and edit a database, execute SQL queries, and display live data at design
time.
The ADO.NET framework data providers let you access MS SQL, Oracle, and ODBC and OLE DB-accessible
databases. The Borland Data Providers (BDP.NET) let you access MS SQL, Oracle, DB2, and InterBase databases.
You can connect to any of these data sources, expose their data in datasets, and use SQL commands to manipulate
the data. Using BDP.NET provides the following advantages:
Portable code that's written once and connects to any supported database.
Open architecture that allows you to provide support for additional database systems.
Logical data types that map easily to .NET native types.
Consistent data types that map across databases, where applicable.
Unlike OLE DB, there is no need for a COM/Interop layer.
When using VCL Forms and the VCL.NET framework components, you can extend database support even further
by using the BDE.NET, dbExpress.NET, and Midas Client for .NET connection technologies.
Model-Driven Applications
Modeling is a term used to describe the process of software design. Developing a model of a software system is
roughly equivalent to an architect creating a set of blueprints for a large development project. Like a set of blueprints,
a model not only depicts the system as a whole, but also allows you to focus in on specifics such as structural and
behavioral details. Abstracted away from any particular programming language (and at some levels, even from
specific technology), the model allows all participants in the development cycle to communicate in the same
language.
Borland's Model Driven Architecture (MDA) describes an approach to software engineering where the modeling tools
are completely integrated within the development environment itself. The MDA is designed around Borlands
Enterprise Core Objects (ECO) framework. The ECO framework is a set of interface, classes, and custom attributes
that provide the communication conduit between your application and the modeling-related features of the IDE.
The ECO features include:
Automatic mapping of the model classes, with their attributes and relationships, to a relational schema.
Automatic evolution of schema when the model changes.
Specification of the persistence backend. You can choose to store objects in a relational database or in an XML
file.
Design-time structural validation of the model and its Object Constraint Language (OCL) expressions.
Runtime validation of the OCL expressions.
An event mechanism that allows you to receive notifications whenever objects are added, changed, or removed.
Delphi 2005 IDE leverages the ECO framework to provide an integrated surface on which to develop your application
model. The IDE and its modeling surface features include:
58
Assemblies
An assembly is a logical package, much like a DLL file, that consists of manifests, modules, portable executable
(PE) files, and resources (.html, .jpeg, .gif) and is used for deployment and versioning. An application can have one
or more assemblies that are referenced by one or more applications, depending on whether the assemblies reside
in an application directory or in a global assembly cache (GAC).
Additional Projects
In addition to the project types described above, Delphi 2005 provides templates to create class libraries, control
libraries, console applications, Visual Basic applications, reports, text files, and more. These templates are stored
New
Other.
in the Object Repository and you can access them by choosing File
59
Code Editor
The Code Editor provides a convenient to view and modify your source code. It is a full-featured, customizable,
ANSI editor that provides syntax highlighting, multiple undo capability, and context-sensitive Help for language
elements.
As you design the user interface for your application, Delphi 2005 generates the underlying code. When you modify
object properties, your changes are automatically reflected in the source files.
Because all your programs share common characteristics, Delphi 2005 auto-generates code to get you started. Do
not modify the auto-generated code for the Initialize Components method. Doing so will cause your form to disappear
when you click the Design tab. You can think of the auto-generated code as an outline that you can examine to
create your program.
The Code Editor provides the following features to help you write code:
Refactoring
Code Insight
Sync Edit
Class completion
Code browsing
Code snippets
Code folding
To-Do Lists
Keystroke macros
Bookmarks
Block comments
Refactoring
Refactoring is the process of improving your code without changing its external functionality. For example, you can
turn a selected code fragment into a method by using the extract method refactoring. Delphi 2005 moves the
extracted code outside of the current method, determines the needed parameters, generates local variables if
necessary, determines the return type, and replaces the code fragment with a call to the new method. Several other
refactorings, such as renaming a symbol and declaring a variable, are also available.
Sync Edit
The Sync Edit feature lets you simultaneously edit duplicate identifiers in code. If you select a block of code that
contains duplicate identifiers, for example, label1, and click the Sync Edit Mode icon
that appears in the
left gutter, all of the duplicated identifiers are highlighted and the cursor is positioned to the first identifier. As you
change the first identifier, the same change is performed automatically on the other identifiers.
Code Insight
Code Insight refers to a subset of features embedded in the Code Editor that aid in the code writing process. These
features display context-sensitive pop-up windows and provide the following services:
Help identify common statements you wish to insert into your code.
Assist in the selection of properties and methods.
60
Class Completion
Class completion simplifies the process of defining and implementing new classes by generating skeleton code for
the class members that you declare. By positioning the cursor within a class declaration in the interface section of
a unit and pressing CTRL+SHIFT+C, any unfinished property declarations are completed. For any methods that require
an implementation, empty methods are added to the implementation section.
Code Browsing
While using the Code Editor to edit a VCL Form application, you can hold down the CTRL key while passing the
mouse over the name of any class, variable, property, method, or other identifier. The mouse pointer turns into a
hand and the identifier appears highlighted and underlined; click on it, and the Code Editor jumps to the declaration
of the identifier, opening the source file if necessary. You can do the same thing by right-clicking on an identifier and
choosing Find Declaration.
Code browsing can find and open only units in the project Search path or Source path, or in the product Browsing
or Library path. Directories are searched in the following order:
1 The project Search path (Project
Options
Directories/Conditionals).
2 The project Source path (the directory in which the project was saved).
3 The global Browsing path (Tools
4 The global Library path (Tools
Options
Options
Library).
Library).
The Library path is searched only if there is no project open in the IDE. Code browsing cannot find identifiers declared
in new, unsaved unit files, and it does not work in package projects.
Code Snippets
Code snippets are commonly used programming statements, such as if, while, and for statements, that you can
insert into your code. When the Code Editor is open, you can double-click a code snippet on the Tool Palette to
add it to your code. You can also create your own code snippets by selecting code in the Code Editor, pressing the
ALT key, and dragging the code to the Tool Palette.
Code Folding
Code folding lets you collapse sections of the code to create a hierarchal view of your code and to make it easier to
read and navigate. The collapsed code is not deleted, but hidden from view. To use code folding, click the plus and
minus signs next to the code.
To-Do Lists
A To-Do List records tasks that need to be completed for a project. After you add a task to the To-Do List, you can
edit the task, add it to your code as a comment, indicate that it has been completed, and remove it from the list. You
can filter the list to display only those tasks that interest you.
61
Keystroke Macros
You can record a series of keystrokes as a macro while editing code. After you record a macro, you can play it back
to repeat the keystrokes during the current IDE session. Recording a macro replaces the previously recorded macro.
Bookmarks
Bookmarks provide a convenient way to navigate long files. You can mark a location in your code with a bookmark
and jump to that location from anywhere in the file. You can use up to ten bookmarks, numbered 0 through 9, within
a file. When you set a bookmark, a book icon
Block Comments
You can comment a section of code by selecting the code in the Code Editor and pressing CTRL+/ (slash). Each
line of the selected code is prefixed with // and will be ignored by the compiler. Pressing CTRL+/ will add or remove
the slashes, based on whether the first line of the code is prefixed with //. When using the Visual Studio or Visual
Basic key mappings, use CTRL+K+C to add and remove comment slashes.
62
Help on Help
This section includes information about the:
Delphi 2005 Help
Microsoft .NET Framework SDK Help
Borland Developer Support Services and Web Sites
Delphi 2005 Quick Start Guide
Typographic Conventions Used in the Help
Conceptual Overviews
The conceptual overviews provide information about product architecture, components, and tools that simplify
development. If you are new to a particular area of development, such as modeling or ADO.NET, see the overview
topic at the beginning of each section in the online Help.
At the end of most of the overviews, you will find links to related, more detailed information. Icons are used to indicate
that a link leads to the .NET SDK, partner Help, or to a web site. The icons are explained later in this topic.
How-To Procedures
The how-to procedures provide step-by-step instructions. For development tasks that include several subtasks, there
are core procedures, which include the subtasks required to accomplish a larger task. If you are beginning a
development project and want to know what steps are involved, see the core procedure for the area you are working
on.
In addition to the core procedures, there are several single-task procedures.
All of the procedures are located under Procedures in the Content pane of the Help window. Additionally, most of
the conceptual overviews provide links to the pertinent procedures.
Reference Topics
The reference topics provides detailed information on subjects such as API elements, the Delphi language, and
compiler directives.
All of the reference topics are located under Reference in the Content pane of the Help window. Additionally, most
API references are underlined and link directly to the appropriate reference topic.
C# Language Tutorial
C# is an object-oriented programming language designed for building applications that run on the .NET Framework.
Delphi 2005 includes a comprehensive C# tutorial from Softsteel Solutions. You can access this tutorial by choosing
Help
C# Tutorial.
In addition to the tutorial, the following topics in the Microsoft .NET Framework SDK provide detailed, reference-style
information about C#:
C# Language Specification
C# Programmer's Reference
C# Compiler Options
Used to indicate
Monospace type Source code and text that you must type.
Boldface
Reserved language keywords or compiler options, references to dialog boxes and tools.
Italics
Delphi 2005 identifiers, such as variables or type names. Italicized text is also used for book titles and to
emphasize new terms.
KEYCAPS
64
65
66
Code Visualization
The Code Visualization feature of Delphi 2005 provides the means to document and debug your class designs using
a visual paradigm. As you load your projects and code files, you can use the Model View to get both a hierarchical
graphical view of all of the objects represented in your classes, as well as a UML-like model of your application
objects. This feature can help you visualize the relationships between objects in your application, and can assist you
in developing and implementing.
in the past. Projects with subprojects and multiple source files can be compiled all together, which is called
building, or you can compile each project individually.
The integrated debugger allows you to set watches and breakpoints, and to step through, into, and over individual
lines of code. A set of debugger windows provides details on variables, processes, and threads, and lets you drill
down deeply into your code to find and fix errors.
68
Repository Basics
Source control systems store copies of source files and difference files in some form of database repository. In some
systems, such as CVS or VSS, the repository is a logical structure that consists of a set of flat files and control files.
In other systems, the repositories are instances of a particular database management system (DBMS) such as
InterBase, Microsoft Access, MS SQL Server, IBM DB2, or Oracle.
Repositories are typically stored on a remote server, which allows multiple users to connect, check files in and out,
and perform other management tasks simultaneously. You need to make sure that you establish connectivity not
only with the server, but also with the database instance. Check with your network, system, and database
administrators to make sure your machine is equipped with the necessary drivers and connectivity software, in
addition to the client-side source control software.
69
Some source control systems allow you to create a local repository in which you can maintain a snapshot of your
projects. Over time the local image of your projects differs from the remote repository. You can establish a regular
policy for merging and committing changes from your local repository to the remote repository.
Generally, it is not safe to give each member of your team a separate repository on a shared project. If you are each
working on completely separate projects and you want to keep each project under source control locally, you can
use individual local repositories. You can also create these multiple repositories on a remote server, which provides
centralized support, backup, and maintenance.
70
StarTeam consists of server and client components. On the server side, the StarTeam Server maintains a database
repository that captures a complete snapshot of the source files in your project and incremental changes (deltas or
differences) to those files. The StarTeam client is integrated seamlessly with Delphi 2005. You can place projects
into and pull projects out of your source control repository, and check in, check out, merge, and compare files.
Note: The StarTeam integration for Delphi 2005 supports StarTeam 5.4, 6.0 and 2005 Servers. You must have the
StarTeam Windows Client installed on your system, or some StarTeam features, such as Visual Merge and
Visual Diff, will not be available.
StarTeam Client
The StarTeam integration for Delphi 2005 includes a StarTeam Client for the .NET Framework. You can launch the
full StarTeam Client, or view the client as embedded elements of the IDE. These embedded StarTeam elements
provide access to most of the commands and information available in the client's main window (also called the Project
View Window).
The StarTeam Client provided with Delphi 2005 does not include the Visual Diff and Visual Merge utilities for
comparing and merging revisions of files. These utilities are available with the StarTeam Windows Client, and if you
71
have the StarTeam Windows Client installed, the StarTeam integration can use these utilities. Alternatively, you can
configure the integrated StarTeam Client to use different comparison and merge utilities.
The StarTeam client provided with the Delphi integration can only be started from within Delphi. There are no
provisions for using StarTeam's command-line interface with the StarTeam integration for Delphi.
With the exception of the aforementioned items, the features supported by your StarTeam installation are supported
by the Delphi 2005 integration. For example, if you have StarTeam Standard, which does not support tasks,
requirements, or alternate property editors (APEs), the StarTeam integration for Delphi 2005 will not support tasks,
requirements, or APEs. If you have StarTeam Enterprise, which supports tasks, your StarTeam integration will
support tasks. If you have StarTeam Enterprise Advantage, your StarTeam integration will support tasks,
requirements, and APEs. For more information about StarTeam, including a feature matrix, see the StarTeam
product page on the Borland web site at http://www.borland.com/starteam/index.html.
Advanced Features
The integrated StarTeam Client lets you access advanced StarTeam features without leaving the development
environment. Some of these include:
Create and edit items other than files, such as change requests, requirements, tasks, and topics
Apply labels to a file, an item, a group of files, or a group of items
Establish process items and rules to help you link and track changes to your files
These features will help you assign and track responsibilities for tasks throughout your project. The client that can
be launched from within Delphi 2005 provides even more features and functions for managing your files and projects,
such as the ability to generate reports and charts, and administer user accounts and servers.
working file with revisions of the file in the StarTeam repository. You can also revert the contents of your working
file to any StarTeam revision.
The StarTeam integration supports file renaming and deleting. When you rename or delete a file in your project, the
StarTeam integration will automatically carry out the changes on the repository when you commit the project.
Similarly, if a team member has renamed or deleted files, and committed the changes, the changes are carried out
in your local project when you update the project. This capability prevents loss of revision information when a file is
renamed or moved.
Note: If the file renaming or deletions made in your local project conflict with changes made by another team
member in the StarTeam Client, you must manually resolve the pending renaming or deletion of files. The
Pending Renames/Deletes dialog box (StarTeam
Pending Renames/Deletes) lets you commit any
pending local file renames or deletions to the repository or cancel the pending operations.
When the Structure View displays the folder hierarchy for your StarTeam project, the Structure View includes a
toolbar with the following features:
A drop-down list of paths to the folders you've previously selected. Choose a path from the drop-down list to go
to that StarTeam folder in the current hierarchy. The most recently selected folder sorts to the top.
A Refresh button. Click this button to update the information in the current tree.
A button for selecting which node in the folder hierarchy to show as the root folder, the project or the view. This
button is available when the project and view are not at the same level.
73
How Delphi 2005 Interacts with Source Control Systems using the SCC API
Your source code control system typically consists of server and client components. On the server side, the system
maintains a database repository that captures a complete snapshot of your project files and incremental changes
(deltas or differences) to those files. On your local client, you use the source control client software to manage access
to projects and files stored in the server repository, to maintain an audit trail of changes you make to the projects
and files, and to provide you with conflict management capabilities. In Delphi 2005, the Source Control Manager
(SCM) is an integrated feature that allows you to connect to your source control system on a remote server. You
can place projects into and pull projects out of your source control repository, and check in, check out, merge, and
compare files.
If your source control system implements the Microsoft SCC API, Delphi 2005 can interact with the source control
systems to perform basic source control tasks. Some products, such as StarTeam, provide an SCC integration
application that can be installed separately. Other products, like CVS, do not directly support the SCC API, but a
variety of third-party clients built as front-end applications to CVS do provide the integration.
You must use a system that includes an integration to the SCC API, and the integration must support MSSCCI 1.1.
source control capabilities in Delphi 2005. Delphi 2005 automatically detects supported source control systems that
you have installed and configured.
You can install and use multiple source control systems from within Delphi 2005. When you perform certain tasks
you are required to log into the source control system repository of your choice.
Synchronizing Files
One of the most powerful features of any source control system is its ability to synchronize multiple instances of the
same file, to compare lines of text and mark those that overlap, then to merge the file instances without destroying
conflicting lines of code or text. Most systems also provide the capability to update your local instance of a file, or
even an entire projects files, with the latest source of those files stored in the source control system repository,
without destroying any new lines of code youve added to the local instance of the file. You can perform these
synchronization operations using the Commit Browser from within Delphi 2005.
75
76
77
Associations
In the UML, an association is a navigational link produced when one class holds a reference to another class (for
example, as an attribute or property). Code visualization creates association links when one class contains an
attribute or property that is a non-primitive data type. On the diagram the association link exists between the class
containing the non-primitive member, and the data type of that member.
78
79
Associations
In the UML, an association is a navigational link produced when one class holds a reference to another class (for
example, as an attribute or property). Code visualization creates association links when one class contains an
attribute or property that is a non-primitive data type. On the diagram the association link exists between the class
containing the non-primitive member, and the data type of that member.
80
The class diagram above models a customer order from a retail catalog. The central class is the Order. Associated
with it are the Customer making the purchase and the Payment. There are three types of payments: Cash,
Check, or Credit. The order contains OrderDetails (line items), each with its associated Item.
81
An association has two ends. An end may have a role name to clarify the nature of the association. For example,
an OrderDetail is a line item of each Order.
A navigation arrow on an association shows which direction the association can be traversed or queried. An
OrderDetail can be queried about its Item, but not the other way around. The arrow also lets you know who "owns"
the association's implementation; in this case, OrderDetail has an Item. Associations with no navigation arrows
are bi-directional.
The multiplicity of an association end is the number of possible instances of the class associated with a single
instance of the other end. Multiplicities are single numbers or ranges of numbers. In our example, there can be only
one Customer for each Order, but a Customer can have any number of Orders.
This table lists the most common multiplicities:
Multiplicities Meaning
0..1
0..* or *
1..*
Every class diagram has classes and associations. Navigability, roles, and multiplicities are optional items placed
in a diagram to provide clarity.
82
Element
Namespace
Class
Interface
Structure
Enum
Delegate
Object
Generalization/
Implementation
Association
Dependency
Note
Note Link
83
Compiler Options
You can set many of the compiler options for a project by choosing Project
Options and selecting the
Compiler page. Most of the options on the Compiler page correspond to a compiler option and are described in
the online Help for that page.
On the Compiler page, you can also save compiler options as an option set. This lets you quickly change options
based on your development activity. For example, you can set compiler options specific to debugging your project,
and then change the option set when you are done debugging it.
If you need to specify additional compiler options, you can invoke the compiler from the command line. For a complete
list of the Delphi compiler options and information about running the Delphi compiler from the command line, see
Delphi Language Guide in the Content pane. For a complete list of the C# compiler options and information about
running the C# compiler from the command line, see the .NET Framework SDK online Help.
As you compile your project, you can display the current compiler options in the Messages window. Choose Tools
Options
Environment Options and select the Show command line option. The next time you compile a
project, the command used to compile the project and the response file will displayed in the Messages window. The
response file lists the compiler options and the files to be compiled.
Compiler Errors
As you compile a project, compiler messages are displayed in the Messages window. For an explanation of a
message, select the message and press F1.
84
Refactoring Applications
Refactoring is a technique you can use to restructure and modify your code in such a way that the intended behavior
of your code stays the same. Delphi 2005 provides a number of refactoring features that allow you to streamline,
simplify, and improve both performance and readability of your application code.
85
Refactoring Overview
Refactoring is a technique you can use to restructure and modify your existing code in such a way that the intended
behavior of your code stays the same. Refactoring allows you to streamline, simplify, and improve both performance
and readability of your application code.
Each refactoring operation acts upon one specific type of identifier. By performing a number of successive
refactorings, you build up a large transformation of the code structure, and yet, because each refactoring is limited
to a single type of object or operation, the margin of error is small. You can always back out of a particular refactoring,
if you find that it gives you an unexpected result. Each refactoring operation has its own set of constraints. For
example, you cannot rename symbols that are imported by the compiler. These are described in each of the specific
refactoring topics.
Delphi 2005 includes a refactoring engine that evaluates and executes the refactoring operation. The engine also
displays a preview of what changes will occur in a refactoring pane that appears at the bottom of the Code Editor.
The potential refactoring operations are displayed as tree nodes, which can be expanded to show additional items
that might be affected by the refactoring, if they exist. Warnings and errors also appear in this pane. You can access
the refactoring tools from the Main menu and from context-sensitive drop down menus.
Delphi 2005 provides the following refactoring operations:
Symbol Rename (Delphi, C#)
Extract Method (Delphi)
Declare Variable and Field (Delphi)
Sync Edit Mode (Delphi, C#)
Find References (Delphi, C#)
Extract Resourcestring (Delphi)
Find Unit (Delphi)
Use Namespace (C#)
Undo (Delphi, C#)
86
Rename Method
Renaming a method, type, and other objects is functionally the same as renaming an identifier. If you select a
procedure name in the Code Editor, you can rename it. If the procedure is overloaded, the refactoring engine
renames only the overloaded procedure and only calls to the overloaded procedure. An example of this rule follows:
procedure Foo; overload;
procedure Foo(A:Integer); overload;
Foo();
Foo;
Foo(5);
If you rename the first procedure Foo in the preceding code block, the engine renames the first, third, and fourth items.
If you rename an overridden identifier, the engine renames all of the base declarations and descendent declarations,
which means the original virtual identifier and all overridden symbols that exist. An example of this rule follows:
TFoo = class
procedure Foo; virtual;
end;
TFoo2 = class(TFoo)
procedure Foo; override;
end;
TFoo3 = class(TFoo)
procedure Foo; override;
end;
TFoo4 = class(TFoo3)
procedure Foo; override;
end;
Performing a rename operation on Foo renames all instances of Foo shown in the preceding code sample.
87
88
89
Declare Variable
You can create a variable when you have an undeclared identifier that exists within a procedure block scope. This
feature gives you the capability to select an undeclared identifier and create a new variable declaration with a simple
menu selection or keyboard shortcut. When you invoke the Declare Variable dialog, the dialog contains a suggested
name for the variable, based on the selection itself. If you choose to name the variable something else, the operation
succeeds in creating the variable, however, the undeclared identifier symbol (Error Insight underlining) remains.
Variable names must conform to the language rules for an identifier. In Delphi, the variable name:
Cannot be a keyword.
Cannot contain a space.
Cannot be the same as a reserved word, such as if or begin.
Must begin with a Unicode alphabetic character or an underscore, but can contain Unicode alphanumeric
characters or underscores in the body of the variable name.
In the Delphi language, the type name can also be the keyword string.
Note: The .NET SDK recommends against using leading underscores in identifiers, as this pattern is reserved for
system use.
Note: On the dialog that appears when you choose to declare a variable, you can set or decline to set an initial
value for the variable.
The refactoring engine automatically assumes the new variable myVar should be set to type Integer, provided x is
an Integer.
Often, the refactoring engine can infer the type by evaluating a statement. For instance, the statement If foo
Then... implies that foo is a Boolean. In the example If (foo = 5) Then... the expression result is a Boolean.
Nonetheless, the expression is a comparison of an ordinal (5) and an unknown type (foo). The binary operation
indicates that foo must be an ordinal.
Declare Field
You can declare a field when you have an undeclared identifier that exists within a class scope. Like the Declare
Variable feature, you can refactor a field you create in code and the refactoring engine will create the field declaration
90
for you in the correct location. To perform this operation successfully, the field must exist within the scope of its
parent class. This can be accomplished either by coding the field within the class itself, or by prefixing the field name
with the object name, which provides the context for the field.
The rules for declaring a field are the same as those for declaring a variable:
Cannot be a keyword.
Cannot contain a space.
Cannot be the same as a reserved word, such as if or begin.
Must begin with a Unicode alphabetic character or an underscore, but can contain Unicode alphanumeric
characters or underscores in the body of the field name.
In the Delphi language, the type name can also be the keyword string.
Note: Leading underscores on identifiers are reserved in .NET for system use.
You can select a visibility for the field. When you select a visibility that is not private or strict private, the refactoring
engine performs the following operations:
Searches to find all child classes.
Searches each child class to find the field name.
Displays a red error item if the field name conflicts with a field in a descendant class.
You cannot apply the refactoring if it conflicts with an existing item name.
Sample Refactorings
The following examples show what will happen when declaring variables and fields using the refactoring feature.
Consider the following code:
TFoo = class
private
procedure Foo1;
end;
...
implementation
procedure TFoo.Foo1;
begin
FTestString := 'test';
end;
Assume you apply a Declare Field refactoring. This would be the result:
TFoo = class
private
FTestString: string;
procedure Foo1;
end;
91
procedure TFoo.Foo1;
var
// added by refactor
TestString: string;
// added by refactor
begin
TestString := 'test';
// added by refactor
TestString := 'whatever';
end;
92
Sample Refactoring
The following sample illustrates how the Find References refactoring will proceed:
1 TFoo = class
2
loc_a: Integer;
3
procedure Foo1;
4 end;
5 var
6 loc_a: string;
7 implementation
8 {$R *.nfm}
9 procedure Foo;
10 begin
11 loc_a := 'test';
12 end;
13 procedure TFoo.Foo1;
14 begin
//
15
loc_a:=1;
16 end;
93
94
95
Testing Applications
Unit testing is an integral part of building reliable applications. The following topics discuss unit testing features
included in Delphi 2005.
96
DUnit
DUnit gets installed automatically by the Delphi 2005 installer. You can find many DUnit resources in the \source
\DUnit directory, under your primary installation directory. These resources include documentation and test
examples.
When using DUnit, at a minimum you usually include at least one test case and one or more text fixtures. Test cases
typically include one or more assertion statements to verify the functionality of the class being tested.
DUnit is licensed under the Mozilla Public License 1.0 (MPL).
NUnit
During the install process, you will be prompted to install NUnit. You can change the default location of the installation,
or you can accept the default, which installs NUnit into C:\Program Files\NUnit V2.x, where x is a point release
number.. The installation directory includes a number of resources including documentation and example tests.
NUnit is the name of the .NET testing framework and can be used with both Delphi for .NET and C# projects. There
are some subtle but important differences between the way NUnit and DUnit work. For example, NUnit does not link
in .dcu files, as DUnit does.
When using NUnit, at a minimum, you usually include at least one test case and one or more test fixtures. Test cases
typically include one or more assertion statements to verify the functionality of the class being tested.
Test Projects
A test project encapsulates one or more test cases and is represented by a node in the IDE Project Manager. You
can create a test project before creating test cases. Once you have a test project that is associated with a code
project, you can add test cases to the test project. Delphi 2005 provides a Test Project Wizard to help you build a
test project.
97
Test Cases
Every class that you want to test must have a corresponding test class. You define a test case as a class in order
to instantiate test objects, which makes the tests easier to work with. You implement each test as a method that
corresponds to one of the methods in your application. More than one test can be included in a test case. The ability
to group and combine tests into test cases and test cases into test projects is what sets a test case apart from simple
forms of testing, such as using print statements or evaluating debugger expressions. Each test case and test project
is reusable and rerunnable, and can be automated through the use of shell scripts or console commands.
Generally, you should create your tests in a separate project from the source file project. That way, you do not have
to go through the process of removing your tests from your production application. Delphi 2005 provides a Test
Case Wizard to help you build test cases. You can add test cases directly into the same project as your source file,
however, doing so increases the size of your project. You can also conditionally compile your test cases out of
production code by using IFDEF statements around the test case code.
Test Fixtures
The term test fixture refers to the combination of multiple test cases, which test logically related functionality. You
define test fixtures in your test case. Typically, you will instantiate your objects, initialize variables, set up database
connection, and perform maintenance tasks in the SetUp and TearDown sections. As long as your tests all act upon
the same objects, you can include a number of tests in any given test fixture.
98
DUnit Overview
DUnit is an open-source unit test framework based on the JUnit test framework. The DUnit framework allows you
to build and execute tests against Delphi Win32 applications. The Delphi 2005 integration of DUnit allows you to test
both Delphi for .NET and C# applications.
Each testing framework provides its own set of methods for testing conditions. The methods represent common
assertions. You can also create your own custom assertions. You will be able to use the provided methods to test
a large number of conditions.
This topic includes information about:
Building DUnit Tests.
DUnit Functions.
DUnit Test Runners.
The following example shows the test case skeleton file that you need to modify to test the two functions, Add and
Sub, in the preceding code.
unit TestCalcUnit;
99
interface
uses
TestFramework, CalcUnit;
type
// Test methods for class TCalc
TestTCalc = class(TTestCase)
strict private
aTCalc: TCalc;
public
procedure SetUp; override;
procedure TearDown; override;
published
procedure TestAdd;
procedure TestSub;
end;
implementation
procedure TestTCalc.SetUp;
begin
aTCalc := TCalc.Create;
end;
procedure TestTCalc.TearDown;
begin
aTCalc := nil;
end;
procedure TestTCalc.TestAdd;
var
_result: System.Integer;
y: System.Integer;
x: System.Integer;
begin
_result := aTCalc.Add(x, y);
// TODO: Add testcode here
end;
procedure TestTCalc.TestSub;
var
_result: System.Integer;
y: System.Integer;
x: System.Integer;
begin
_result := aTCalc.Sub(x, y);
// TODO: Add testcode here
end;
initialization
// Register any test cases with the test runner
RegisterTest(TestTCalc.Suite);
end.
DUnit Functions
DUnit provides a number of functions that you can use in your tests.
100
Function
Description
Check
CheckEquals
CheckNotEquals
CheckNotNull
CheckNull
CheckSame
EqualsErrorMessage
Checks to see that an error message emitted by the application matches a specified error message.
Fail
FailEquals
FailNotEquals
Checks to see that a failure condition does not equal a specified failure condition.
FailNotSame
Checks to see that two failure conditions are not the same.
NotEqualsErrorMessage Checks to see that two error messages are not the same.
NotSameErrorMessage
Checks that one error message does not match a specified error message.
For more information on the syntax and usage of these and other DUnit functions, see the DUnit help files in \source
\dunit\doc.
101
NUnit Overview
NUnit is an open-source unit test framework based on the JUnit test framework. The NUnit framework allows you
to build and execute tests against .NET Framework applications. The Delphi 2005 integration of NUnit allows you
to test both Delphi for .NET and C# applications.
This topic includes information about:
Building NUnit Tests.
NUnit Asserts.
NUnit Test Runners.
The following example shows the test case skeleton file that you need to modify to test the two methods, Add and
Sub, in the preceding code.
102
namespace
{
using
using
using
using
using
using
TestCalc
System;
System.Collections;
System.ComponentModel;
System.Data;
NUnit.Framework;
CSharpCalcLib;
Note: Each test method is automatically decorated with the [Test] attribute in C# projects. In addition, in C# the test
methods are defined as functions returning void.
The following example shows a small Delphi for .NET program that performs simple addition and subtraction:
103
unit CalcUnit;
// .Net Version
interface
type
{ TCalc }
TCalc = class
public
function Add(x, y: Integer): Integer;
function Sub(x, y: Integer): Integer;
end;
implementation
{ TCalc }
function TCalc.Add(x, y: Integer): Integer;
begin
Result := x + y;
end;
function TCalc.Sub(X, Y: Integer): Integer;
begin
Result := x + y;
end;
end.
The following example shows the test case skeleton file that you need to modify to test the two functions, Add and
Sub, in the preceding code.
unit TestCalcUnit;
interface
uses
NUnit.Framework, CalcUnit;
type
// Test methods for class TCalc
[TestFixture]
TestTCalc = class
strict private
FCalc: TCalc;
public
[SetUp]
procedure SetUp;
[TearDown]
procedure TearDown;
published
[Test]
procedure TestAdd;
[Test]
procedure TestSub;
end;
104
implementation
procedure TestTCalc.SetUp;
begin
FCalc := TCalc.Create;
end;
procedure TestTCalc.TearDown;
begin
FCalc := nil;
end;
procedure TestTCalc.TestAdd;
var
ReturnValue: Integer;
y: Integer;
x: Integer;
begin
// TODO: Setup call parameters
ReturnValue := FCalc.Add(x, y);
// TODO: Validate return value
end;
procedure TestTCalc.TestSub;
var
ReturnValue: Integer;
y: Integer;
x: Integer;
begin
// TODO: Setup call parameters
ReturnValue := FCalc.Sub(x, y);
// TODO: Validate return value
end;
end.
Note: In Delphi for .NET the test methods are defined as procedures.
Each test method must:
be public
be a procedure for Delphi for .NET or a function with a void return type for C#
take no arguments
Setup
Use the SetUp procedure to initialize variables or otherwise prepare your tests prior to running. For example, this is
where you would set up a database connection, if needed by the test.
TearDown
The TearDown method can be used to clean up variable assignments, clear memory, or perform other maintenance
tasks on your tests. For example, this is where you would close a database connection.
105
NUnit Asserts
NUnit provides a number of asserts that you can use in your tests.
Function Description
Syntax
IsNull
AreSame Checks to see that two items are the same. Assert.AreSame(expected, actual [, string
message])
IsTrue
IsFalse
Fail
Assert.Fail([string message])
You can use multiple asserts in any test method. This collection of asserts should test the common functionality of
a given method. If an assert fails, the entire test method fails and any other assertions in the method are ignored.
Once you fix the failing test and rerun your tests, the other assertions will be executed, unless one of them fails.
Note: You may need to set the path to your host application in the Project Options dialog. Set the Host Application
property to the location of the test runner you want to use.
106
Localizing Applications
Delphi 2005 includes a suite of Translation Tools to facilitate localization and development of .NET and Win32
applications for different locales. The Translation Tools include the following:
Satellite Assembly Wizard (for .NET)
Resource DLL Wizard (for Win32)
Translation Manager
Translation Repository
The Translation Tools are available for Delphi VCL Forms applications (both .NET and Win32), and Win32 console
applications, packages, and DLLs. You can access the Translation Tools configuration options by choosing Tools
Options
Translation Tools Options.
The Wizards
Before you can use the Translation Manager or Translation Repository, you must add languages to your project by
running either the Satellite Assembly Wizard for .NET projects or the Resource DLL Wizard for Win32 projects. The
Satellite Assembly Wizard creates a .NET satellite assembly for each language you add. The Resource DLL Wizard
creates a Win32 resource DLL for each language. For simplicity, this documentation uses the term resource
module to refer to either a satellite assembly or a resource DLL.
While running either wizard, you can include extra files, such as .resx or .rc files, that are not normally part of a
project. You can add new resource modules to a project at any time. If you have multiple projects open in the IDE,
you can process several at once.
You can also use the wizards to remove languages from a project and restoring languages to a project.
Translation Manager
After resource modules have been added to your project, you can use the Translation Manager to view and edit VCL
forms and resource strings. After modifying your translations, you can update all of your applications resource
modules.
The External Translation Manager (ETM) is a version of the Translation Manager that you can set up and use without
the IDE. ETM has the same functionality as the Translation Manager, with some additional menus and toolbars.
Translation Repository
The Translation Repository provides a database for translations that can be shared across projects, by different
developers. While working in the Translation Manager, you can store translated strings in the Repository and retrieve
translated strings from the Repository.
By default, each time your assemblies are updated, they will be populated with translations for any matching strings
that exist in the Repository. You can also access the Repository directly, through its own interface, to find, edit, or
delete strings.
The Translation Repository stores data in XML format. By default, the file is named default.tmx and is located in the
Delphi 2005\bin directory.
107
.nfn (.NET)
.dfn (Win32)
The Translation Tools maintain a separate file for each form in your application and each target language.
These files contain the data (including translated strings) that you see in the Translation Manager.
.resx (.NET)
The Satellite Assembly Wizard uses the compiler-generated .drcil file to create an .resx file for each target
language. These .resx files contain special comments that are used by the Translation Tools.
.rc (Win32)
The Resource DLL Wizard uses the compiler-generated .drc file to create an .resx file for each target language.
These .resx files contain special comments that are used by the Translation Tools.
.tmx
The Translation Repository stores data in an .tmx file. You can maintain more than one repository by saving
multiple .tmx files.
.bdsproj
The External Translation Manager lists the assemblies (languages) and resources to be translated into
a .bdsproj project file. When third-party translators add and remove languages from a project, they can save
these changes in an .bdsproj file, which they return to the developer.
108
Debugging Applications
Delphi 2005 includes both the Borland .NET Debugger and Borland Win32 Debugger. The IDE automatically uses
the appropriate debugger based on the active project type. Cross-platform debugging within a project group is
supported and, where possible, the debuggers share a common user interface.
The integrated debuggers let you find and fix both runtime errors and logic errors in your Delphi 2005 application.
Using the debuggers, you can step through code, set breakpoints and watches, and inspect and modify program
values. As you debug your application, the debug windows are available to help you manage the debug session and
provide information about the state of your application.
Evaluate/Modify
The Evaluate/Modify functionality allows you to evaluate an expression for which you've set a breakpoint. You can
also pass expression values to the currently running process. For instance, you can modify a value for a variable
and insert that value into the variable, which you might want to do if that value is to be passed to another method at
some point during the execution of the application. This allows you to provide values in-process, which you might
otherwise not be able to do. The Evaluate/Modify functionality is customized to whichever implementation language
you are using and that is supported by the product.
Breakpoints
Breakpoints pause program execution at a certain point in the program or when a particular condition occurs. You
can then use the debugger to view the state of your program, or step over or trace into your code one line or machine
instruction at a time. The debugger supports two types of breakpoints. Source breakpoints pause execution at a
specified location in your source code. Address breakpoints pause execution at a specified memory address.
Watches
Watches lets you track the values of program variables or expressions as you step over or trace into your code. As
you step through your program, the value of the watch expression changes if your program updates any of the
variables contained in the watch expression.
Debug Windows
The following debug windows are available to help you debug your program. By default, most of the windows are
displayed automatically when you start a debugging session. You can also view the windows individually by using
the View
Debug Windows sub-menu.
Each window provides one or more right-click context menus. The F1 Help for each window provides detailed
information about the window and the context menus.
109
Debug Window
Description
Breakpoint List Displays all of the breakpoints currently set in the Code Editor or CPU window.
Call Stack
Watch List
Displays the current value of watch expressions based on the scope of the execution point.
Local Variables Displays the current functions local variables, enabling you to to monitor how your program updates the
values of variables as the program runs.
Modules
Displays processes under control of the debugger and the modules currently loaded by each process. It
also provides a hierarchical view of the namespaces, classes, and methods used in the application.
Threads Status Displays the status of all processes and threads of execution that are executing in each application being
debugged. This is helpful when debugging multi-threaded applications.
Event Log
Displays messages that pertain to process control, breakpoints, output, threads, and module.
CPU
Displays the low-level state of your program, including the assembly instructions for each line of source
code and the contents of certain registers.
FPU
Displays the contents of the Floating-point Unit and SSE registers in the CPU.
110
Deploying Applications
After you have written, tested, and debugged your application, you can make it available to others by deploying it.
Depending on the size and complexity of the application, you can package it as one or more assemblies, as
compressed cabinet (.cab) files, or in an installer program format (such as .msi). After the application is packaged,
you can distribute it by using XCOPY, FTP, as a download, or with an installer program.
This sections includes the following general topics:
Deploying .NET Applications
Deploying Win32 Applications
Using Installation Programs
Redistributing Delphi 2005 Files
Redistributing Third Party Software
For additional information about deploying specific types of applications, refer to the list of links at the end of this topic.
installer called dotnetfx.exe, which contains the common language runtime and .NET Framework components
required to run .NET applications. For more information about dotnetfx.exe, see the .NET Framework SDK online
Help.
Description
Deploy.htm
License.txt
Readme.htm Contains last minute information about Delphi 2005, possibly including information that could affect the
redistribution rights for Delphi 2005 files.
112
Procedures
113
Getting Started
114
If you add a nonvisual component to the form, the component tray appears at the bottom of the Designer surface.
3 Repeat steps 1 and 2 to add additional components.
4 Use the dotted grid on the form to align your components.
115
Adding References
You can integrate your legacy COM servers and ActiveX controls into managed applications by adding references
to unmanaged DLLs to your project, and then browse the types just as you would with managed assemblies.
To add references
1 From the main menu, choose Project
Add References.
Tip:
You can also right-click the Reference folder in the Project Manager, and choose Add
Reference.
116
Add to Project.
The file appears below the Project.exe node of the Project Manager.
117
Add to Repository.
3 Enter the project name, description, and author information in the dialog box.
4 Click Browse to select an icon to represent the project you saved.
5 Click OK.
118
Note:
The IDE maintains the Copy Local setting until you change it.
119
Select All.
Your new template appears immediately on the Tool Palette, in the category that you specified.
The components in the component template are added to the form, along with their preconfigured properties and
events. You can reposition the components independently, reset their properties, and create or modify event
handlers for them, just as if you had placed each component in a separate operation.
120
Creating a Project
To add a new project
1 Choose Project
121
Options.
Tip: The changes will affect only forms created after these options are changed. To change the settings for existing
forms, set the GridSize, DrawGrid, and SnapToGrid properties of the form.
122
Customizing Toolbars
To arrange your toolbars
1 Click the grab bar on the left side of any toolbar.
2 Drag the toolbar to another location or onto your desktop.
Toolbars
Customize.
2 From the toolbar, not the Customize dialog box, drag the tool from the toolbar until its icon displays an X and
Toolbars
Customize.
123
124
The tool window is replaced by one or more tabs at the outer edge of the IDE window.
2 To display the tool window, position the cursor over the tab.
125
The Tool Palette is filtered to display only those item names that match what you are typing. The characters that
you have typed appear bold-faced in the item names.
2 Double-click an item to perform the default action for that item. For example, double-clicking a component adds
To remove the search filter from the Tool Palette, click the filter icon
126
Open.
2 In the Open dialog box, from the Files of type drop-down list, select Assembly Metadata.
3 Navigate to the folder where the .NET assembly is located. Select the assembly and click Open.
You can open multiple .NET assemblies in the metadata explorer. Each open assembly is displayed in the tree in
the left-pane; the top-level node for a .NET assembly is denoted by the
To close a particular .NET assembly, right-click on the top-level
icon.
The top half of the Call Graph tab shows you a list of methods that call the method you selected in the left pane.
The bottom half of the Call Graph tab shows you the methods called by the method you selected in the left pane.
Methods that exist in the same assembly as the currently selected method will appear as clickable links, and are
displayed in blue underlined text. Clicking on a link will cause that method to become selected in the tree in the
left-hand pane.
Tip:
You can use the Browser buttons on the toolbar to navigate backwards and forwards to previously
selected items in the left pane.
127
Open.
2 In the Open dialog box, from the Files of type drop-down list, select Type Library.
This sets the file filter to display files with extensions of .TLB, .OLB, .OCX, .DLL, and .EXE.
3 Navigate to the folder where the type library is located.
4 Select the file and click Open.
You can open multiple type libraries in the explorer. Each open type library is displayed in the tree in the left pane;
the top-level node for a type library is denoted by the
icon.
128
Alternatively, you can enter the name of the full path to the assembly in the File Name field.
4 Select the assembly.
5 Click Open.
The Installed .NET Components dialog box displays the components from the assembly.
6 Verify that the components you want to install on the Tool Palette are checked.
7 Click OK.
129
To rename a file
1 In the Project Manager, right-click the file that you want to rename.
If the file has associated files that appear as child nodes in the Project Manager tree, those files are automatically
renamed.
130
Desktops
Save Desktop.
2 Enter the name of the desktop in the Save Desktop dialog box.
3 Click OK.
Desktops
131
132
3 Click the ellipsis (...) button next to (Advanced) to display the Dynamic Properties dialog box.
This dialog lists all of the properties that can be stored in the configuration file.
4 Select the properties you want to store in the configuration file.
5 Optionally, you can override the default key name listed in the Key mapping field.
6 Click OK.
The dynamic properties are marked with an icon in the Object Inspector.
Delphi 2005 creates an XML file named app.config (for a Windows application) or Web.config (for a Web
application) in the project directory. This file lists the dynamic properties and their current values.
7 Compile the application.
Delphi 2005 creates a file named <projectname>.exe.config (for a Windows application) or <projectname>.
dll.config (for a Web application) in the same directory as the application's executable or DLL file.
The next time the application runs, the changed property value will be in effect.
133
Options.
Options.
Options.
134
down list, or click the ellipsis (...) next to the text box to use the associated property editor, depending on which
update technique is available for the property.
to Code view.
4 Type the code you want to execute when the event occurs.
135
Options.
2 Review the options in each tool category and customize the settings to suit your needs.
3 Click OK.
136
To-Do List.
The item is added as a comment to your code, beginning with the word TODO.
To-Do List.
2 In the To-Do List dialog box, check the check box next to the item to indicate completion.
The item remains in the list, but the text is crossed out. If the item was added as a comment to code, the comment
is updated to indicate DONE instead of TODO.
To-Do List.
The to-do list is redisplayed, with the filtered items hidden. The status bar at the bottom of the To-Do List dialog
box indicates how many items are hidden due to filtering.
To-Do List.
The item is removed from the to-do list. If the item was added as a comment to code, the comment is also
removed.
137
To choose another event for the component, click the Events tab in the Object Inspector, locate the event, and
double-click its text box.
The Code Editor appears.
3 Type the code that will execute when the event occurs at runtime.
138
Code Visualization
139
Adding Shortcuts
Shortcuts facilitate re-use of elements, make it possible to display library classes on diagrams, and demonstrate
relationships between the diagrams within the model. You can place relationship references to the selected model
elements on the diagram background, using the three methods: Add Shortcuts dialog, Copy-Paste Shortcut from
the Model View, Add Shortcuts from Model View right-click menu.
Shortcut elements are indicated on a diagram with the shortcut icon
Tip:
3 In the Add Shortcuts dialog, choose the required element from the tree view of available contents.
4 Click Add to place the selected element to the list of the Existing or ready to add elements.
5 When the list of ready to add elements is complete, click OK.
Tip:
You can also copy an element from one diagram and paste it in another diagram as a shortcut.
140
deselect the element after closing the in-place editor of the last inserted element.
Tip:
After making a selection on the Tool Palette or doing the first of a multi-draw or multi-placement
operation, you can cancel the operation by clicking the Pointer button on the Tool Palette or by
pressing the ESC key.
141
Annotating Diagrams
The Tool Palette for UML diagram elements displays note and note link buttons for all ECO class diagrams. Use
these elements to place notes and note links on the diagram.
Notes can be free floating, or you can draw a note link to some other element to show that a note pertains specifically
to it. In the diagram view, you can:
Use Hyperlinks to hyperlink the note to another diagram or element.
Edit the text when its in-place editor is active.
Edit a note's properties using the Object Inspector.
Add an existing note from one diagram to another diagram using a shortcut. (Choose Add
any diagram context menu.)
button.
142
Shortcuts from
143
144
Options.
The following options are available to tailor the diagrams to best fit your purposes:
Option
Description
Show compartments as line When True, the compartments in the class icons of class/package diagrams are displayed as
solid lines. Otherwise, compartments are displayed as expandable nodes.
Font in diagrams
Show grid
If this option is True, a design grid is visible in the background behind diagrams. You can cause
diagram elements to "snap" to the nearest grid coordinate by selecting True for the Snap to grid
option.
Snap to grid
If this option is True, diagram elements "snap" to the nearest coordinate of the diagram
background design grid. The snap function works whether the grid is visible or not.
3D look
If this option is True, a shadow appears under each diagram element to create a threedimensional effect.
Sort elements alphabetically When setting this option to True, fields, methods, subclasses, and properties are sorted
alphabetically within compartments.
Sort elements by visibility
When this option is True, fields, methods, subclasses, and properties are sorted by visibility
within compartments.
In the Diagram View, you can optionally show or hide the name of the base class or interface in the top-right corner
of a classifier. You can hide these references to simplify the visual presentation of the project.
Options.
145
Creating Associations
There are two types of relationships you can create on an ECO framework class diagram: An association link, and
a generalization link.
Use the association link button
on the Tool Palette to draw association links between diagram elements. The
Object Inspector enables you to set properties on the association, such as the cardinality of the client and supplier.
Use the generalization link button
You can select the association link on the class diagram, and set its properties in the Object Inspector.
You can select the association link on the class diagram, and set its properties in the Object Inspector.
146
Other Diagram on the right-click menu. Alternatively, you can use the shortcut CTRL+SHIFT+D.
The new diagram opens in a new tab in the Editor Window. You can use the Object Inspector to view and edit
the diagram properties.
To delete a diagram
1 In the Model view select the diagram to be deleted.
2 On the right-click menu, choose Delete.
147
148
Drawing Links
Use the Tool Palette to draw relationship links on your diagrams. To open the Tool Palette, select View
Palette, and click on the corresponding Tool Palette item to view the available diagram elements.
Tool
Note: Drawing links and creating associations on class diagrams is only supported in ECO framework class
diagrams and ECO framework projects.
149
on a link lie between two blue bullets (see the figure below). The bullets display whenever you select the link on
the diagram.
4 Click on the destination element to terminate the link.
Tip: Once you have created a link, you can add bending points to it. Select the link on the diagram, and then drag
the link to the desired postion. The figure below demonstrates this technique.
150
151
Hyperlinking Diagrams
You can create hyperlinks from diagrams or diagram elements to other system artifacts, and browse directly to them.
You can create a hyperlink to an existing diagram or diagram element anywhere in a project. You can also create
hyperlinks to a document or file on a local or remote storage device. Finally, you can create hyperlinks to a URL on
your company intranet or to the Internet.
Hyperlinks exist within the context of projects, so open a project to create or browse them. To create, view, remove,
and browse hyperlinks choose Hyperlinks from the diagram context menu.
You may also create hyperlinks from your diagrams to external documents such as files or URLs. For most users,
such hyperlinking will probably take the form of documents on a LAN or document server or URLs on the company
intranet. However, you can also easily link to online information such as newsgroups or discussion forums. If it is
available online, you can link to it. Hyperlinks are used to:
Link diagrams that are generalities or overviews to specifics and details.
Create browse sequences leading through different but related views in a specific order; create hierarchical
browse sequences.
Link descendant classes to ancestors; browse hierarchies.
Link diagrams or elements to standards or reference documents or generated documentation.
Facilitate collaboration among team members.
Do not select the actual namespace in the Model View to create a hyperlink. Rather, expand
the namespace node, and select the class diagram.
4 Select the Model Elements tab to view the pane containing a tree view of the available project contents in the
project. Select the desired diagram or element from the list, and click Add.
5 For element selection, expand diagram nodes in the Model Elements tab. To remove an element from the
4 Select the External Documents tab to view the Recently used Documents list which contains a list of previously
152
Note:
Items added to the Recently used Documents list are not specific to a single project or
solution.
To view hyperlinks to a diagram, element or external document, right click on the diagram background or element,
and choose Hyperlinks from the context menu. All hyperlinks created appear under the Hyperlinks submenu. On
a diagram, all names of diagram elements that are hyperlinked are displayed in blue font. When you select a link
from the submenu, the respective element appears selected in the Diagram View.
Once you have defined hyperlinks for a selected diagram or element, use the context menus to browse to the linked
resources. Note that browsing to a linked diagram opens it in the Diagram View, or makes it the current diagram if
already open. Browsing to a linked element causes the parent diagram to open or become current, and the diagram
scrolls to the linked element and selects it.
To remove a hyperlink
1 Open the diagram that displays the link you want to remove.
2 Choose Hyperlinks
Edit from the diagram or element context menu. The Hyperlinks dialog appears.
3 In the selected list on the right of the dialog, click on the hyperlink that you wish to remove.
4 Click Remove.
5 Click OK to close the dialog.
Note: To remove a hyperlink from a specific element, be sure to select the element first. Then choose Hyperlinks
Edit from the context menu.
153
154
Model View.
By default, the Model View displays expandable diagram nodes with the elements contained therein. You can hide
expandable diagram nodes to further simplify the visual presentation of the project.
Options.
By default, diagram nodes are sorted by metaclass; however, you sort elements in the Model View by metaclass,
alphabetically, or none.
Options.
155
To move an element
1 Select the element (or elements) to be moved.
2 Drag and drop the selection to the target location.
Tip: Use Cut/Paste right-click menu commands, or, the keyboard shortcuts for Cut (CTRL+X), Copy (CTRL+C), and
Paste (CTRL+V).
156
diagram.
2 Use the mouse to click on the shaded area and drag it. This is a convenient way to scroll around the diagram.
3 Alter the size of the Overview pane by clicking on the upper left corner of the pane and dragging to resize it.
4 Close the Overview pane by clicking on the diagram.
157
Tool Palette.
2 To view design elements on the Tool Palette, click the UML Class Diagram category.
3 On the Tool Palette, click the button for the element you want to place on the diagram. (Icons are identified with
labels.)
4 Click on the diagram background in the place where you want to create the new element. This creates the new
Alternatively, you can right-click the diagram background and choose Add from the right-click
menu. The submenu displays all of the basic elements that can be added to the diagram, and
the Shortcuts command.
158
Printing Diagrams
You can print diagrams separately or as a group, or print all diagrams in the project.
To print diagrams
1 With the diagram in focus in the Diagram View, choose File
Click the Print combobox, and choose Print dialog, to select the target printer.
Use the Options dialog box to set up the paper size, orientation, and margins. (Tools
Options
Together)
Tip: You can click Preview to open the preview pane. Use the Preview zoom slider, or Auto Preview zoom check
box, as required.
159
Resizing Elements
Diagram elements can be resized automatically or manually. When new items are added to an element that has
never been manually resized, the element automatically grows to enclose the new items.
When the element contents change, for example, when when members are added or deleted, and the element size
is too small to display all members, scrollbars are displayed to the right of compartments.
160
161
To navigate to the corresponding node in the Model View from the Diagram View
1 Right-click on the selected element or diagram background in the Diagram View.
2 On the right-click menu choose Synchronize with Model View.
To navigate to the corresponding node in the Model View from the Editor
1 Right-click on the selected code element in the Editor.
2 On the right-click menu choose Synchronize Model View.
Tip: Alternatively, you can right click on an element in the Model View, and choose either the Select on
Diagram or Go to Definition (for source-generating elements) commands. The Go to Definition command
is also available for source-generating elements in the Diagram View. Using the Select on Diagram command
opens the appropriate diagram with the element highlighted. For source-generating elements, using the Go to
Definition command opens the corresponding source file with the code element highlighted.
162
Zooming
Use the diagram context menu to obtain the required magnification in the Diagram View.
The table below lists the Zoom submenu commands and their corresponding shortcut keys. Press NUMLOCK on your
keyboard to activate the shortcut keys.
Zoom commands and their shortcut keys
Command
Shortcut
Zoom In
Zoom Out
To Actual Size /
Fit in Window
163
164
Building Packages
You can create packages easily in Delphi 2005 and include them in your projects.
New
Other
This creates a new, empty package and makes an entry for it in the Project Manager, along with two folders:
one marked Contains and one marked Requires.
Note:
If you want to add required files to the package, you must add compiled packages (.dcpil, .dll)
to the Required folder. Add uncompiled code files (.pas) to the Contains folder.
New
Other
6 Click OK.
166
Finding References
The Find References refactoring feature helps you locate any connections between a file containing a symbol you
intend to rename and other files where that symbol also appears. A preview allows you to decide how you want the
refactoring to operate on specific targets or on the group of references as a whole.
Note:
Find References.
You can also invoke Find References with the keyboard shortcut Shift+Ctrl+Enter.
Note:
If you continue to perform Find References operations without clearing the results, the new
results are appended in chronological order to the existing results in the window.
Note:
2
No matter which you select, you get the same results. The entire node will be cleared.
at the top of the Find References window, to delete the selected item and
Note: Deleting items from the Find References window does not delete them from your actual code files or your
project.
167
Add Reference.
2 In the Add Reference dialog box, select a Delphi-produced assembly DLL from the list of .NET assemblies and
168
Rename Symbol.
This displays a hierarchical list of the potentially refactored items, in chronological order as they were found. You
can jump to each item in the Code Editor.
Note:
If you want to remove an item from the refactoring operation, select the item and click the Delete
Refactoring icon in the toolbar.
Warning:
If you change an item in the Code Editor, the refactoring operation is prevented. You need
to reapply the refactoring after making changes to any files during the process, while the
Message Pane contains refactoring targets.
To apply refactorings
1 Open a project.
2 Locate a symbol name in the Code Editor.
3 Select the symbol name.
4 Right-click to display the context menu.
169
5 Select Refactoring
Rename Symbol.
As long as the View references before refactoring check box is not selected, the refactoring occurs
immediately.
Warning:
If the refactoring engine encounters errors, the refactoring is not applied. The errors are
displayed in the Message Pane.
170
Renaming a Symbol
You can rename symbols if the original declaration symbol is in your project, or if a project depended upon by your
project contains the symbol and is in the same open project group. You can also rename error symbols.
To rename a symbol
1 Select the symbol name in the Code Editor.
2 Right-click to display the drop-down context menu.
3 Select Refactoring
Rename Symbol ' symbol name' where symbol name is the actual name of the selected
symbol.
This displays the Rename Symbol dialog.
4 Enter the new name in the New Name text box.
5 If you want to preview the changes to your project files, select the View References Before Refactoring check
box.
Note:
The menu commands are context-sensitive. If you select a method, the command will read
Rename Method method name where method name is the actual name of the method you
have selected. This context-sensitivity holds true for all other object types, as well.
171
Options.
Options.
Options.
172
Debugging Applications
173
Adding a Watch
Add a watch to track the values of program variables or expressions as you step over or trace into code. Each time
program execution pauses, the debugger evaluates all the items listed in the Watch List window and updates their
displayed values.
You can organize watches into groups. When you add a watch group, a new tab is added to the Watch List window
and all watches associated with that group are shown on that tab. When a group tab is displayed, only the watches
in that group are evaluated during debugging. By grouping watches, you can also prevent out-of-scope expressions
from slowing down stepping.
To add a watch
1 Choose Run
An expression consists of constants, variables, and values contained in data structures, combined with language
operators. Almost anything you can use as the right side of an assignment operator can be used as a debugging
expression, except for variables not accessible from the current execution point.
3 Optionally, enter a name in the Group Name field to create the watch in a new group, or select a group name
For example, you can request the debugger to evaluate the watch, even if doing so causes function calls, by
selecting the Allow Function Calls option.
5 Click OK.
174
2 Select either Borland .NET Debugger or Borland Win32 Debugger from the Debugger drop-down list,
175
To set a breakpoint
1 Click the left gutter of the Code Editor next to the line of code where you want to pause execution.
2 Choose Run
Tip:
Add Breakpoint
Options
Editor Options
Display and
The following icons are used to represent breakpoints in the Code Editor gutter.
Icon Description
To modify a breakpoint
1 Right-click the breakpoint icon and choose Breakpoint Properties.
2 Set the options in the Source Breakpoint Properties dialog box to modify the breakpoint.
For example, you can set a condition, create a breakpoint group, or determine what action occurs when execution
reaches the breakpoint.
3 Click Help for more information about the options on the dialog box.
4 Click OK.
existing group.
3 Click OK.
176
Add Breakpoint
2 In the Line number field, enter the line in the Code Editor where you want set the breakpoint.
Tip:
To pre-fill the Line number field, click a line in the Code Editor prior to opening the Add Source
Breakpoint dialog box.
3 In the Condition field, enter a conditional expression to be evaluated each time this breakpoint is encountered
Conditional breakpoints are useful when you want to see how your program behaves when a variable falls into a
certain range or what happens when a particular flag is set.
If the conditional expression evaluates to true (or not zero), the debugger pauses the program at the breakpoint
location. If the expression evaluates to false (or zero), the debugger does not stop at the breakpoint location.
Tip:
Add Breakpoint
You can also right-click the breakpoint icon and choose Breakpoint Properties to display the
Source Breakpoint Properties dialog box.
2 Click Advanced to display additional options at the bottom the dialog box.
3 Check the actions that you want to occur when the breakpoint is encountered.
For example, you can specify an expression to be evaluated and write the result of the evaluation to the Event
Log.
4 Click OK.
To change the color of the text at the execution point and breakpoints
1 Choose Tools
Options
Editor Options
Color.
For example, to change the breakpoint color for Delphi 2005 code, select the Delphi 2005 tab.
3 Scroll the code sample window to display the execution and breakpoint icons in the left gutter of the window.
4 Click anywhere on the execution point or breakpoint line that you want to change.
177
5 Use the Foreground Color and Background Color drop-down lists to change the colors associated with the
178
2 In the Inspect dialog box, type the expression you want to inspect.
3 Click OK.
and owner.
Tip:
If you want to see the hexadecimal representation of a string, double-click the string value in the
Debug Inspector.
3 Click the Methods tab to view all of the methods that have executed up to this point in the code.
179
Tip:
If you want to see the return type for any method, select the method and look at the status bar
of the Debug Inspector, where the syntax line for the method, including the return type is
displayed.
4 Click the Properties tab to view all of the properties for the active object.
5 Click any property name to see its type displayed in the status bar of the Debug Inspector.
6 Click the question mark (?) icon to see the actual value for that property at this point of the execution of the
application.
180
the programming construct that you introduced exposed a problem with the compiler. If so, follow the procedure
below, on reviewing code.
Close All.
This will clear the unit cache maintained in the IDE. Alternatively, you can close the IDE and restart.
4 Another option is to try and recompile your application using the Project Build option so that the compiler will
compiler (dccil.exe) from a command prompt. This will remove the unit caching of the IDE from the picture and
could help to resolve the problem.
Typically, most internal errors can be reproduced with only a few lines of code and frequently the code involves
syntax or constructs that are rather unusual or unexpected. If this is the case, try modifying the code to do the
same thing in a different way. For example, if you are typecasting a value, try declaring a variable of the cast
type and do an assignment first.
begin
if Integer(b) = 100 then...
end;
var
a: Integer;
begin
a := b;
if a = 100 then...
end;
Here is an example of unexpected code that you can correct to resolve the error:
181
var
A : Integer;
begin
{ Below the second cast of A to Int64 is unnecessary; removing it can avoid the Internal
Error. }
if Int64(Int64(A))=0 then
end;
2 In this case, the second cast of A to an Int64 is unnecessary and removing it corrects the error. If the problem
seems to be a while...do loop, try using a for...do loop instead. Although this does not actually solve the
problem, it may help you to continue work on your application.
If this resolves the problem, it does not mean that either while loops or for loops are broken but more likely it
means that the manner in which you wrote your code was unexpected.
3 Once you have identified the problem, we ask that you create the smallest possible test case that still reproduces
versa.
2 If it uses a nested function or procedure (a procedure/function contained within a procedure/function) try
unnesting them.
3 If it occurs on a typecast look for alternatives to typecasting like using a local variable of the type you need.
4 If the problem occurs within a with statement try removing the with statement altogether.
5 Try turning off compiler optimizations under Project Options
Compiler.
error by changing the code. While this may not be the best solution, it may help you to continue to work on your
application. If this resolves the problem, it does not mean that either while loops or for loops are broken but
perhaps that the manner in which you have written your code was unexpected and therefore resulted in an error.
2 If you've tried your code on the latest release of the compiler and it is still reproducible, create the smallest possible
test case that will still reproduce the error and submit it to Borland. If it is not reproducible on the latest version,
it is likely that the problem has already been fixed.
For example, create a directory called C:\DCPIL and under Tools Environment Options select the Library tab
and set the DCPIL output directory to C:\DCPIL. This setting will help ensure that the .dcpil files the compiler
generates are always up-to-date. This is useful when you move a package from one directory to another. You
can create a .dcuil directory on a per-project basis using Project Options
Directories/Conditionals
Unit output directory.
2 The key is to use the most up-to-date versions of your .dcuil and .dcpil files. Otherwise, you may encounter
182
Evaluate/Modify.
To modify a component property, specify the property name, for example, this.button1.Height or
Self.button1.Height.
3 Enter a value in the New Value edit box.
The expression must evaluate to a result that is assignment-compatible with the variable you want to assign it
to. Typically, if the assignment would cause a compile or runtime error, it is not a legal modification value.
4 Choose Modify.
183
Options
Debugger Options.
Options.
2 Review the debugging options on the various pages of the Project Options dialog box.
In particular, review the following pages: Compiler, Linker, Directories/Conditionals, Version Info, and
Debugger. Note that not all pages are available for all project types. For example, the Version Info page is only
displayed for Delphi Win32 projects.
3 Click OK.
184
Refactoring Code
Refactoring refers to the capability to make structural changes to your code without changing the functionality of the
code. Code can often be made more compact, more readable, and more efficient through selective refactoring
operations. Delphi 2005 provides a set of refactoring operations that can help you re-architect your code in the most
effective and efficient manner possible.
Refactoring operations are available for both Delphi and C#. However, the refactorings for C# are limited in number.
You can access the refactoring commands from the Refactoring menu or from a right-click context menu while in
the Code Editor.
The Undo capability is available for all refactoring operations. Some operations can be undone using the
standard Undo (Ctrl-z) menu command, while the rename refactorings provide a specific Undo feature.
To rename a symbol
1 In the Code Editor, click the identifier to be renamed.
The identifier can be a method, variable, field, class, record, struct, interface, type, or parameter name.
2 From either the main menu or the Code Editor context menu, choose Refactor
Rename.
3 In the Rename dialog box, enter the new identifier in the New Name field.
4 Leave View references before refactoring checked. If this option is unchecked, the refactoring is applied
The Refactorings dialog box displays every occurrence of the identifier to be changed.
6 Review the proposed changes in the Refactorings dialog box and use the Refactor button at the top of the
dialog box to perform all of the refactorings listed. Use the Remove Refactoring button to remove the selected
refactoring from the dialog box.
To declare a variable
1 In the Code Editor, click anywhere in a variable name that has not yet been declared.
Note:
Any undeclared variable will be highlighted with a red wavy underline by Error Insight.
2 From either the main menu or the Code Editor context menu, choose Refactor
Declare Variable.
If the variable has already been declared in the same scope, the command is not available.
3 Fill in the Declare New Variable dialog box as needed.
4 Click OK.
The variable declaration is added to the procedure, based on the values you entered in the Declare New
Variable dialog box.
To declare a field
1 In the Code Editor, click click anywhere in a field name that has not yet been declared.
2 From either the main menu or the Code Editor context menu, choose Refactor
3 Fill in the Declare New Field dialog box as needed.
4 Click OK.
185
Declare Field.
The new field declaration is added to the type section of your code, based on the values you entered in the Declare
New Field dialog box.
Note: If the new field conflicts with an existing field in the same scope, the Refactorings dialog box is displayed,
prompting you to correct the conflict before continuing.
Extract Method.
Delphi 2005 moves the extracted code outside of the current method, determines the needed parameters, generates
local variables if necessary, determines the return type, and replaces the original code fragment with a call to the
new method.
Note:
The resourcestring keyword and the resource string are added to the implementation section of your code, and
the original string is replaced with the new resource string name.
resourcestring
strHelloWorld = 'Hello World';
procedure foo;
begin
writeLn(StrHelloWorld);
end.
2 From either the main menu or the Code Editor context menu, choose Refactor
Find Unit.
The Find Unit dialog box displays a selection list of applicable Delphi units.
Note:
If you are coding in C#, the dialog box is called the Use Namespace dialog box.
3 Select the unit or namespace that you want to add to the uses or using clause in the current scope.
implementation section.
Note:
This choice is not relevant for C# and so the selection is not available when refactoring C# code.
5 Click OK.
The uses or using clause is updated with the selected units or namespaces.
187
Deploying Applications
188
Building Packages
You can create packages easily in Delphi 2005 and include them in your projects.
New
Other
This creates a new, empty package and makes an entry for it in the Project Manager, along with two folders:
one marked Contains and one marked Requires.
Note:
If you want to add required files to the package, you must add compiled packages (.dcpil, .dll)
to the Required folder. Add uncompiled code files (.pas) to the Contains folder.
New
Other
6 Click OK.
190
Add Reference.
2 In the Add Reference dialog box, select a Delphi-produced assembly DLL from the list of .NET assemblies and
191
Editing Code
192
Tip: To turn off code folding for the current edit session, press and hold Ctrl+Shift, and then K, and then O. To collapse
the nearest code block, press and hold Ctrl+Shift, and then K, and E. To expand the nearest code block, press
and hold Ctrl+Shift, and then K, and U. To expand all code, press and hold Ctrl+Shift and then press K, and A.
193
Options.
194
Finding References
The Find References refactoring feature helps you locate any connections between a file containing a symbol you
intend to rename and other files where that symbol also appears. A preview allows you to decide how you want the
refactoring to operate on specific targets or on the group of references as a whole.
Note:
Find References.
You can also invoke Find References with the keyboard shortcut Shift+Ctrl+Enter.
Note:
If you continue to perform Find References operations without clearing the results, the new
results are appended in chronological order to the existing results in the window.
Note:
2
No matter which you select, you get the same results. The entire node will be cleared.
at the top of the Find References window, to delete the selected item and
Note: Deleting items from the Find References window does not delete them from your actual code files or your
project.
195
Rename Symbol.
This displays a hierarchical list of the potentially refactored items, in chronological order as they were found. You
can jump to each item in the Code Editor.
Note:
If you want to remove an item from the refactoring operation, select the item and click the Delete
Refactoring icon in the toolbar.
Warning:
If you change an item in the Code Editor, the refactoring operation is prevented. You need
to reapply the refactoring after making changes to any files during the process, while the
Message Pane contains refactoring targets.
To apply refactorings
1 Open a project.
2 Locate a symbol name in the Code Editor.
3 Select the symbol name.
4 Right-click to display the context menu.
196
5 Select Refactoring
Rename Symbol.
As long as the View references before refactoring check box is not selected, the refactoring occurs
immediately.
Warning:
If the refactoring engine encounters errors, the refactoring is not applied. The errors are
displayed in the Message Pane.
197
To record a macro
1 In the Code Editor, click the record macro button
Note:
The macro is now available to use during the current IDE session.
To run a macro
1 In the Code Editor, position the cursor in the code where you want to run the macro.
2 Click the macro playback button
198
Refactoring Code
Refactoring refers to the capability to make structural changes to your code without changing the functionality of the
code. Code can often be made more compact, more readable, and more efficient through selective refactoring
operations. Delphi 2005 provides a set of refactoring operations that can help you re-architect your code in the most
effective and efficient manner possible.
Refactoring operations are available for both Delphi and C#. However, the refactorings for C# are limited in number.
You can access the refactoring commands from the Refactoring menu or from a right-click context menu while in
the Code Editor.
The Undo capability is available for all refactoring operations. Some operations can be undone using the
standard Undo (Ctrl-z) menu command, while the rename refactorings provide a specific Undo feature.
To rename a symbol
1 In the Code Editor, click the identifier to be renamed.
The identifier can be a method, variable, field, class, record, struct, interface, type, or parameter name.
2 From either the main menu or the Code Editor context menu, choose Refactor
Rename.
3 In the Rename dialog box, enter the new identifier in the New Name field.
4 Leave View references before refactoring checked. If this option is unchecked, the refactoring is applied
The Refactorings dialog box displays every occurrence of the identifier to be changed.
6 Review the proposed changes in the Refactorings dialog box and use the Refactor button at the top of the
dialog box to perform all of the refactorings listed. Use the Remove Refactoring button to remove the selected
refactoring from the dialog box.
To declare a variable
1 In the Code Editor, click anywhere in a variable name that has not yet been declared.
Note:
Any undeclared variable will be highlighted with a red wavy underline by Error Insight.
2 From either the main menu or the Code Editor context menu, choose Refactor
Declare Variable.
If the variable has already been declared in the same scope, the command is not available.
3 Fill in the Declare New Variable dialog box as needed.
4 Click OK.
The variable declaration is added to the procedure, based on the values you entered in the Declare New
Variable dialog box.
To declare a field
1 In the Code Editor, click click anywhere in a field name that has not yet been declared.
2 From either the main menu or the Code Editor context menu, choose Refactor
3 Fill in the Declare New Field dialog box as needed.
4 Click OK.
199
Declare Field.
The new field declaration is added to the type section of your code, based on the values you entered in the Declare
New Field dialog box.
Note: If the new field conflicts with an existing field in the same scope, the Refactorings dialog box is displayed,
prompting you to correct the conflict before continuing.
Extract Method.
Delphi 2005 moves the extracted code outside of the current method, determines the needed parameters, generates
local variables if necessary, determines the return type, and replaces the original code fragment with a call to the
new method.
Note:
The resourcestring keyword and the resource string are added to the implementation section of your code, and
the original string is replaced with the new resource string name.
resourcestring
strHelloWorld = 'Hello World';
procedure foo;
begin
writeLn(StrHelloWorld);
end.
2 From either the main menu or the Code Editor context menu, choose Refactor
Find Unit.
The Find Unit dialog box displays a selection list of applicable Delphi units.
Note:
If you are coding in C#, the dialog box is called the Use Namespace dialog box.
3 Select the unit or namespace that you want to add to the uses or using clause in the current scope.
implementation section.
Note:
This choice is not relevant for C# and so the selection is not available when refactoring C# code.
5 Click OK.
The uses or using clause is updated with the selected units or namespaces.
201
Renaming a Symbol
You can rename symbols if the original declaration symbol is in your project, or if a project depended upon by your
project contains the symbol and is in the same open project group. You can also rename error symbols.
To rename a symbol
1 Select the symbol name in the Code Editor.
2 Right-click to display the drop-down context menu.
3 Select Refactoring
Rename Symbol ' symbol name' where symbol name is the actual name of the selected
symbol.
This displays the Rename Symbol dialog.
4 Enter the new name in the New Name text box.
5 If you want to preview the changes to your project files, select the View References Before Refactoring check
box.
Note:
The menu commands are context-sensitive. If you select a method, the command will read
Rename Method method name where method name is the actual name of the method you
have selected. This context-sensitivity holds true for all other object types, as well.
202
Using Bookmarks
You can mark a location in your code with a bookmark and jump directly to it from anywhere in the file. You can set
up to ten bookmarks. Bookmarks are preserved when you save the file and available when you reopen the file in
the Code Editor.
To set a bookmark
1 In the Code Editor, right-click the line of code where you want to set a bookmark.
A bookmark icon
Tip: To set a bookmark using the shortcut keys, press CTRL+K and a number from 0 to 9.
To jump to a bookmark
1 In the Code Editor, right-click to display the context menu.
2 Choose GoTo Bookmarks
Tip: To jump to bookmark using the shortcut keys, press CTRL and the number of the bookmark, for example, CTRL
+1.
To remove a bookmark
1 In the Code Editor, right-click to display the context menu.
2 Choose Toggle Bookmarks
The bookmark icon is removed from the left gutter of the Code Editor.
Tip: To remove all bookmarks from a file, choose Clear Bookmarks.
203
If your declarations and implementations are sorted alphabetically, class completion maintains their sorted order.
Otherwise, new routines are placed at the end of the implementation section of the unit and new declarations are
placed in private sections at the beginning of the class declaration.
204
205
Explorer page
Options.
Options.
Options.
Options.
debugging.
Options.
206
3 On the Code Editor, point to any identifier to display its declaration while editing your code.
207
code.
208
Options
New
Other
Editor Options page and verify that the Create Backup Files option is checked.
Other Files
Text and click OK to display a blank text file in the Code Editor.
3 On line one of the file, type First line of text and save the file using any name and location.
4 On line two, type Second line of text and save the file.
5 On line three, type Third line of text and save the file.
There are now three versions of the file stored in the current directory in a hidden directory named __history.
6 Click the History tab, which is next to the Code tab.
The revision list at the top of the Contents tab displays three versions of the file. The first version is named ~1~,
the second is named ~2~, and the current version is named File. The source viewer at the bottom of the tab
displays the source for the selected version.
7 Select the different versions to display their source in the source viewer.
8 Click the Code tab to return to the Code Editor and on line four of the file, type Fourth line of text but
Refresh revision info updates the revision list to include unsaved changes to the file.
Revert to previous revision makes the selected version the current version and is available on the Contents and
Info pages.
Reverting a prior version deletes any unsaved changes in the editor buffer.
Synchronize scrolling synchronizes scrolling in the the Contents and Diff pages and the Code Editor. It matches
the line of text that contains the cursor with the nearest matching line of text in the other view. If there is no matching
text in that region of the file, it matches line numbers.
Go to next difference repositions the source on the Diff page to the next block of different code.
Go to previous difference repositions the source on the Diff page to the previous block of different code.
209
Follow text movement locates the same line in the source viewer when switching between views.
Tip: The toolbar button functions are also available of the right-click context menus of the History Manager pages.
The following icons are used to represent file versions in the revision lists.
Icon Description
The Differences From and To panes at the top of the page shows the file versions that you can compare. At
the bottom of the page, source lines that were deleted are highlighted and marked with a minus sign (). Lines
that were added are highlighted and marked with a plus sign (+). The highlighting colors depend on the Code
Editor colors.
3 Select the different file versions in both the Differences From pane and the To pane to see the results in source
viewer.
Right-click the ~2~ version of the file and select Revert, or click the
toolbar button.
The Confirm dialog box indicates that reverting the file will lose any unsaved changes in the buffer.
3 Click Yes on Confirm dialog box.
210
The first duplicate identifier is highlighted and the others are outlined. The cursor is positioned to the first identifier.
If the code contains multiple sets of duplicate identifiers, you can use the TAB key to move to the first identifier
in each set.
2
3 Begin editing the first identifier. As you change the identifier, the same change is performed automatically on the
other identifiers.
By default, the identifier is replaced. To change the identifier without replacing it, use the arrow keys before you
begin typing.
4 When you have finished changing the identifiers, you can exit Sync Edit mode by clicking anywhere in the Code
211
Localizing Applications
212
Languages
Add.
Tip:
To change the path, click the path, and then click the ellipsis (...) button to browse to a different
directory.
When you are satisfied with the path information, click Next.
6 If no satellite assembly for the language exists yet, Create New appears in the Update Mode column. Click Next.
If a resource module exists for the language in the directory you have specified, click in the Update Mode column
to select Update or Overwrite. Choose Update to keep and modify the existing satellite assembly project.
Choose Overwrite to create a new, empty project and to delete the old project and any translations it contains.
Click Next.
7 Review the summary of what the wizard will do and click Finish to create or update the resource modules for
The generated projects contain untranslated copies of the resource strings in your original project. By default, the
Translation Manager is displayed, enabling you to begin translating the resource files.
Languages
Remove.
3 Check the languages that you want to remove and then click Next.
4 Click Finish.
The wizard removes the selected resource module from your project file, but does not delete the assemblies, the
source of the assemblies, or the directories in which they reside.
213
Languages
2 Specify the directory path of the old resource module in the appropriate dialog.
3 In the Update Mode column, select Update.
If a resource module already exists for the language (in the directory you have specified), click in the Update
Mode column to select Update or Overwrite. Choose Update to keep and modify the existing assembly project.
Choose Overwrite to create a new, empty project and to delete the old project and any translations it contains.
4 Click Finish.
214
Translation Manager
Translation Editor.
3 Expand the project tree view to display the resource files that you want to edit.
Tip:
Use the expand and collapse icons on the toolbar above the tree view.
4 Click the resource file you want to edit. The resource strings in the file are displayed in a grid in the right pane.
5 Click the field that you want to edit and type the new text directly in the grid, right-click the field and choose
Edit to edit the string in a dialog box, or click the Multi-line editor icon on the toolbar above the grid.
6 Optionally, enter a comment in the Comment field.
7 Optionally, set the translation status for the string by using the drop-down list in the Status field.
8 Click the Save Translation icon on the toolbar above the grid to update the resource file.
Tip: To display the original form or translated form, click the Show original form and Show translated form icons
in the toolbar above the grid.
Translation Repository.
2 Choose Repository
The resource string is added to the Translation Repository and can be viewed by closing the Translation Manager
and choosing Tools
Translation Repository.
The resource strings in the file are displayed in a grid in the right pane.
4 Right-click the field that you want to update and choose Repository
If the Translation Repository contains only one translation that matches the selected source string, it copies that
translation into the target language column. If the Repository contains more than one match for the selected
resource, its default behavior is to retrieve the first matching translation it finds.
215
Tip:
To change this behavior, close the Transaction Manager and choose Tools Translation Tools
Options, click the Repository tab, and change the Multiple Find Action setting.
Tip:
To change the text editor used by the Translation Manager, choose Tools Translation Tools
Options and change executable file specified in the External Editor field.
216
Languages
Set Active.
The Set Active Language wizard displays a list of the languages in the project. The base language appears in
angle brackets at the top of the language list, for example, <English (United States)>.
3 Select a language from the list and click Finish.
217
By default these files are in either the Program Files\Borland\BDS\3.0\Bin or the Windows\system32 directory
on the developer's computer.
Note:
If the developer chose to install only the Delphi for Win32 personality of Delphi 2005, the files
marked with an asterisk (*) will not available on the developer's computer.
Borland.Delphi.dll *
Borland.Globalization.dll *
Borland.ITE.dll *
Borland.ITE.FormDesigner.dll *
Borland.SCI.dll *
Borland.Vcl.dll *
Borland.VclRtl.dll *
Borland.VclX.dll *
designide90.bpl
dfm90.bpl
DotnetCoreAssemblies90.bpl *
etm.exe
IDECtrls90.bpl
itecore90.bpl
itedotnet90.bpl *
rc90.bpl
ResX90.bpl *
rtl90.bpl
vcl90.bpl
vclactnband90.bpl
vclide90.bpl
vclx90.bpl
xmlrtl90.bpl
nfmrtl90.bpl *
2 Create a directory, such as C:\ETM.
3 Copy the ETM files from the developer into the directory.
4 Open ETM.
From Windows Explorer, double-click etm.exe. From the command line, enter etm.exe.
5 Choose Tools
Options
Packages.
Make sure that the Files of type filter is set to Designtime packages (dcl*.bpl).
8 Select all of the designtime packages in the directory and click OK.
The designtime packages are registered and you can now begin using ETM.
218
following:
a satellite assembly or resource DLL for each language to be translated
the .bdsproj project file generated by using File
219
Languages
Run Updaters (or press F9) or click the Files tab and then click the Run
3 After updating in the internal Translation Manager, rebuild each resource module project by opening the project
Compile.
To simplify this process, you can maintain all the projects, along with the application itself, in a
single project group that can be compiled from the Project Manager by choosing Project
Compile All.
220
where [files] is the optional project group file or the project files.
2 To run the ETM from Windows Explorer, double-click etm.exe
Tip:
Use the expand and collapse icons on the toolbar above the tree view.
4 Click the unit file that you want to edit. The resource strings in the file are displayed in a grid in the right pane.
5 Click the field that you want to edit and do one of the following:
After you have finished the translations, you can send the translated files back to the developer to add to the project.
ETM removes the selected assemblies or DLLs from your projec, but it does not delete them, the source of them,
or the directories they reside in.
221
222
Team
Add Files.
This displays the Add Files dialog box, with a list of the files included in your project that can be added to the
repository.
3 Select the checkboxes next to the files you want to add.
4 Click OK.
all check box. If you leave this unchecked, the Comments dialog displays once for each file you are adding.
6 Click OK.
Tip: You can select or deselect all of the listed files at once by clicking the Check All or Uncheck All buttons
223
Team
Check In Files.
all check box. If you leave this unchecked, the Comments dialog displays once for each file you are checking in.
6 Click OK.
Tip: You can select or deselect all of your files at once by clicking the Check All or Uncheck All buttons,
respectively.
224
Team
system and those that you are checking out, resolve the conflicts that appear in the Synchronization dialog box.
5 Click OK.
all check box. If you leave this unchecked, the Comments dialog displays once for each file you are checking out.
7 Click OK.
Tip: You can select or deselect all of your files at the same time, by clicking the Check All or Uncheck All buttons,
respectively.
225
Options.
226
To connect to a repository
1 Choose Tools
Team
If you are not already connected to the source control system, Delphi 2005 displays the connection dialog box.
2 Enter your user ID in the Username textbox.
3 Enter your password in the Password textbox.
4 Enter the repository name in the Database textbox, or click the Browse... button to locate the repository on your
network.
5 Click OK to connect.
227
Team
2 On the first page of the wizard, choose the source control system from which you want to pull the project. If you
If you have not already logged in to your source control system, this displays the connection dialog for your
particular source control system. For more information on how to log on, see the subtask To select a file if you
are not yet logged on to the source control system.
2 When you have located the project and it appears in the text box of this wizard page, click Place.
This pulls the project and places it into your target directory. The results of the operation are displayed in the
Place project Wizard: Status page dialog.
To select a project if you are not yet logged on to the source control system
1 In the Open Existing Project dialog, select a server from the Server description: drop-down list box.
2 Click Log On.
If you enter the correct log on information, the Open Existing Project dialog is displayed with the available
projects displayed.
5 Select a project from the list of those displayed in the Project: list box.
6 Select a module or view from the View: drop-down list box.
Note:
Your system may call this a view, a module, a project, or may use some other terminology to
refer to the basic project unit.
Note:
Your system may call this a view, a module, a project, or may use some other terminology to
refer to the basic project unit.
4 Click OK.
228
Note:
If your system supports the notion of nested branches or folders, you might be presented with
another dialog box, from which you can select the target branch.
229
To pull a project
1 Choose Tools
Team
2 On the first page of the wizard, choose the source control system from which you want to pull the project. If you
ellipsis(...) button to display the file browser and locate a valid target directory.
If you enter a new directory name into the field, the wizard creates the new directory for you.
2 Click OK to close the file browser window.
3 Click Next.
4 Click Pull to pull the project from the repository into your target directory.
The wizard displays the status of the operation in a separate Pull project Wizard: Status page dialog box.
5 Click Close to close the Pull project Wizard: Status page.
If you have not already logged in to your source control system, this displays the connection dialog for your
particular source control system. For more information on how to log on, see the subtask To select a file if you
are not yet logged on to the source control system.
2 When you have located the project and it appears in the text box of this wizard page, click Pull.
This pulls the project and places it into your target directory. The results of the operation are displayed in the
Pull project Wizard: Status page dialog.
To select a project if you are not yet logged on to the source control system
1 In the Open Existing Project dialog, select a server from the Server description: drop-down list box.
2 Click Log On.
If you enter the correct log on information, the Open Existing Project dialog is displayed with the available
projects displayed.
5 Select a project from the list of those displayed in the Project: list box.
6 Select a module or view from the View: drop-down list box.
230
Note:
Your system may call this a view, a module, a project, or may use some other terminology to
refer to the basic project unit.
Note:
Your system may call this a view, a module, a project, or may use some other terminology to
refer to the basic project unit.
4 Click OK.
Note:
If your system supports the notion of nested branches or folders, you might be presented with
another dialog box, from which you can select the target branch.
231
Team
Remove Files.
Delphi 2005 prompts you to confirm that you want to remove the files.
4 Click OK to confirm the removal.
Tip:
You can check or uncheck the entire list of files by clicking the Check All or Uncheck All buttons,
respectively.
232
Note:
The StarTeam Add command is available in the StarTeam menu for the active file.
Alternatively, you can use the Add command on the StarTeam context menu in the Project
Manager to add any new file in the project.
2 Choose StarTeam
Add.
If the file has not been saved, the Save File As dialog box opens. When the file has been saved, the Add
Files dialog box opens.
3 Optionally (but recommended), fill in the information in the Add Files dialog box.
Dialog Box Element
Description
File Description
Lock Status
Specifies the type of file locking to use after files are checked in. Your lock choice lets
other team members know whether or not you are working on the files. An exclusive
lock means you intend to change the files.
Unlockedindicates you do not intend to make changes.
Exclusiveindicates you intend to make changes to these files, and prevents others
from checking the files in. Unless another user breaks your lock, no one else can create
a new revision of a locked file in the repository until you release your lock.
Non-Exclusiveindicates you are working on the files, and may possibly make
changes, but other users can alter and check in the files.
Specifies whether or not to delete the local files after the project is checked into the
repository. Either check the Delete Working Files check box to delete the files from
your workstation, storing them only in the server configurations repository, or uncheck
the check box to keep the working files in the working folder, as well as the repository.
Check the Link And Pin Process Item check box if you want to link a process item to
your files. If process rules are enforced for this project, this option is required. If the
use of process items is required, the Link And Pin Process Item check box is selected
by default. If an active process item has been set, it's pre-selected as the process item
to link. To select a process item, click the Select button to open the Select Process
Item dialog box.
If this process item is now fixed, finished, or complete as a result of placing the project
into StarTeam, then check the Mark Selected Process Item As Fixed/Finished/
Complete check box.
Revision Label
Specifies a label to assign to the checked in files. Select a label from the Revision
Label combo box or create a new revision label by typing its name. Existing labels are
listed in reverse chronological order based on the time they were created. A label is
233
useful if you plan to retrieve these files as a group later or if you will need this specific
revision of any of the files.
4 Optionally, click the Advanced button to specify advanced options.
The new file is checked into the StarTeam repository. The status of the StarTeam operation is displayed in the
StarTeam Messages window.
234
Commit Project) to
Check In.
Description
Comment
This does not apply when you are checking in a single file.
Compare
Lock Status
Force Check-in
Check the Link And Pin Process Item check box if you want
to link a process item to your file. If process rules are
enforced for this project, this option is required. If the use
235
The file is checked into the StarTeam repository. The status of the StarTeam operation is displayed in the
StarTeam Messages window.
Note:
For some types of files, Delphi 2005 automatically generates an associated file in the same
module to store resources. For example, when you create a VCL Forms application for
the .NET Framework, Delphi 2005 generates a unit file (for example, Unit1.pas) and the
associated form (Unit1.nfm) file in the same module. The associated form file is maintained by
Delphi 2005 as you make changes to the unit file. The StarTeam integration treats these paired
files specially. If you check in the unit file, StarTeam checks to see if the associated form file
has been modified. If the form file has been modified, StarTeam will automatically check in both
files.
236
Check Out.
If you are only interested in checking out the tip revision of the file, click OK now, and you are
done.
Description
Force Checkout
Select Force Check-out to overwrite the local working file, even if the file in the
repository is less recent than the file in the working folder.
When this checkbox is checked, StarTeam opens a Checkout Statistics dialog box
when you check out the file. This dialog box lists information about the checkout, such
as the total elapsed time, the quantity of information transferred, and the transfer rate.
Reference By
Select an option and specify settings for the file you are checking out. By default, a file's
current configuration is checked out.
Selecting Current Configurationchecks out the most current (or tip) file revision.
Selecting Labeled Configurationchecks out a revision of the file associated with a
specific view or revision label. When you select Labeled Configuration, you can select
the label or view from the drop-down list. The existing view and revision labels are listed
in reverse chronological order based on the time for which they were created. The view
labels precede the revision labels in the list. If the selected file does not have the label,
no revision is checked out for that file.
Promotion State Configurationchecks out the revision of the selected file that has a
specific promotion state. Actually, this is the revision that has the view label currently
assigned to the selected promotion state.
As Ofchecks out the revision that was the tip revision at the specified date and time.
Lock Status
Specifies the type of file locking to use after the file is checked out. Your lock choice
lets other team members know whether or not you are working on the files. An exclusive
lock means you intend to change the file.
Unlockedindicates you do not intend to make changes.
Exclusiveindicates you intend to make changes to this file, and prevents others from
checking the file in. Unless another user breaks your lock, no one else can create a
new revision of a locked file in the repository until you release your lock.
237
Non-Exclusiveindicates you are working on the file, and may possibly make changes,
but other users can alter and check in the files.
Keep currentindicates that the checkout will keep the file's current lock status. This
is the default selection.
3 Optionally, click the Advanced button to specify advanced options.
system and those that you are checking out, resolve the conflicts that appear in the Synchronization dialog box.
7 Click OK.
all check box. If you leave this unchecked, the Comments dialog displays once for each file you are checking out.
9 Click OK.
238
To compare the active working file with the latest revision in the repository
1 Choose StarTeam
Difference.
The file comparison application (typically, Visual Diff) opens, displaying the tip revision of the file on the left and
the working version on the right. Differences between the files appear in a different color. Under some
circumstances, the latest checked in version may contain changes made by other team members.
2 To return to the IDE, choose File
Tip: The StarTeam file revisions show up in the History Manager. The History Manager lets you compare any
two revisions a file, including any revision of the file in the StarTeam repository, locally saved revisions, and
the current content of the file in the editor buffer. File comparison in the History Manager does not rely on any
external file comparison tools.
1 Choose StarTeam
The file comparison application (typically, Visual Diff) opens, displaying the tip revision of the file on the left and
the working version on the right. Differences between the files appear in a different color. Under some
circumstances, the latest checked in version may contain changes made by other team members.
4 To return to the IDE or the StarTeam Client, choose File
239
Manage Associations.
Description
StarTeam Server
Specifies the StarTeam Server where the project is stored. Select a server from the
StarTeam Server drop-down list. If the StarTeam Server you want to use does not appear
on the list, click the Servers button to add a new server or change the properties of an
existing server. If you have not previously logged on, the Log On dialog box requests a
user name and password when you select a server.
Project Name
The name of the StarTeam Project in the repository. Select the StarTeam project that
contains your Delphi 2005 project from the Project Name drop-down list.
View Path
Each StarTeam project has at least one view, and may have multiple views. A view defines
the files and folders that can be accessed for a given project. Select an existing view from
the View Path drop-down list.
Folder Path
This is the root folder path for your Delphi 2005 project. By default, the folder path is set
to the root folder of the StarTeam view. To choose a different folder, click the ellipsis ()
button, and select the folder. Child folders can be created if needed. If your project contains
files in a non-relative path, your the root folder path for your project must be a child of the
root folder of the StarTeam view.
This is the user name used to log on to the selected StarTeam Server. This field is not
editable.
This is the path to the local directory containing your project. This field should not require
editing.
4 After you have made your selections, click OK to close the StarTeam Associations dialog box.
240
The StarTeam Associations dialog box will list the StarTeam associations (server, project, view, and folder
path) and indicate that your project is associated.
Manage Associations.
This opens the Manage Non-relative Working Paths dialog box, which lets you map the non-relative local
working path to a folder in the StarTeam repository.
Note:
The Manage Non-relative Working Paths dialog box opens automatically when you attempt
to check in a file with a non-relative path.
3 Click Add, browse to and select the local (non-relative) folder that you want to map, and click OK.
The Select A StarTeam Folder dialog box appears. This dialog box lets you select a folder to which you can
map. If necessary, you can create child folders in the dialog box.
4 Select a StarTeam folder for storing files from a given non-relative path, and click OK.
Note:
The folder for files in non-relative paths must be outside the root folder path for the Delphi 2005
project. For example, if your local working path for your Delphi 2005 project is C:\Borland Studio
Projects\Project1 and it maps to the folder path BDS\Project1 in View1 in the repository, then
any files in non-relative paths cannot be mapped to View1\BDS\Project1 or its child folders.
Therefore, if you add a file, logo.bmp that is stored locally in C:\images, you cannot map the
working path to BDS\Project1\images or any other folder beneath BDS\Project1, but you can
map it to BDS\images.
5 Click Close to close the Manage Non-relative Working Paths dialog box.
6 Click Close to close the StarTeam Associations dialog box.
Once you have mapped the non-relative path, files that are part of your project and located in this local working
path can be checked in to StarTeam.
Personal Options.
The Personal Options dialog box appears. The StarTeam Personal Options dialog box contains the following
pages:
Workspace: lets you specify confirmation requirements for version control operations, display options, and other
parameters that apply to the behavior and appearance of StarTeam item in the workspace.
StarTeamMPX Server: lets you enable support for StarTeamMPX Server for the active project and subsequently
opened projects. When StarTeamMPX Server is enabled, information about changed objects in a StarTeam
server configuration is broadcast in encrypted format through a publish/subscribe channel to the StarTeam
client. The caching modules in the StarTeam client automatically display the new information to the user. By
reducing demand on the StarTeam Server, the caching modules also increase the number of active sessions
that can be effectively managed in a single server configuration.
File: lets you set checkout options, locking options, merging options, end-of-line options, default file status
repository settings, and alternate applications for editing, merging, or comparing files.
Change Request: lets you set system tray notification parameters and locking options for change requests.
241
Requirement: lets you set system tray notification parameters and locking options for requirements.
Task: lets you set system tray notification parameters and locking options for tasks.
Topic: lets you set system tray notification parameters and locking options for topics.
The StarTeam Personal Options dialog box can also be opened in the StarTeam client. Please refer to the
StarTeam User's Guide for additional information on setting personal options.
Note:
3 After you have set your personal options, click OK to close the dialog box.
242
The steps for setting the active process item are the same for the embedded client and the standalone client.
2 In the upper pane of the project view window, select the process item (change request, requirement, or task) you
Note: Setting a second active process item clears the first. There is also a Clear Active Process Item command on
the Change Request, Requirement, and Task menus, but you will probably never use it. You do not have to
use the active process item while adding files or checking them in. The active process item becomes the
default selection for a process item, but you can select another appropriate item.
2 Alternatively, locate and double-click the active process item in either the embedded client or the standalone
client.
Note: Depending on how your team has set up StarTeam, you may see a different dialog box called an alternate
property editor (APE). APEs are created with StarTeam Extensions. Refer to the StarTeam Extensions User's
Guide for more information about APEs and workflow processes.
243
Find.
This opens the embedded StarTeam Client, and highlights the file that is active in the Code Editor.
2 Alternatively, right-click a file in the Project Manager, and choose StarTeam
This opens the embedded StarTeam Client, and highlights the selected file.
244
Find.
Launch Client.
The client opens the StarTeam project associated with the active Delphi 2005 project, and selects the project's
root folder.
2 Perform source code control operations or administrative tasks as needed.
The StarTeam Client can be used even after the IDE has been closed.
245
Lock/Unlock.
246
Check Out.
If a merge is required, StarTeam displays a dialog box asking if you want to merge the file now.
3 click Yes to start the merge.
Visual Merge lets you quickly search for and resolve conflicts and differences between the two file revisions.
5 When you have resolved all conflicts, choose File
StarTeam tells you whether you have resolved all conflicts and asks if you wish to save the file.
6 click Yes to replace your working file with the merged file.
The file status will change from Merge to Modified. The file is now ready to check in to StarTeam.
247
Manage Associations.
This opens the Manage Associations dialog box, which lets you associate your Delphi 2005 project with a
StarTeam Server, project, and folder.
3 Click the Edit button to re-establish a connection to the StarTeam Server.
Description
StarTeam Server
Specifies the StarTeam Server where the project is stored. Select a server from the
StarTeam Server drop-down list. If the StarTeam Server you want to use does not appear
on the list, click the Servers button to add a new server or change the properties of an
existing server. If you have not previously logged on, the Log On dialog box requests a
user name and password when you select a server.
Project Name
The name of the StarTeam Project in the repository. Select the StarTeam project that
contains your Delphi 2005 project from the Project Name drop-down list.
View Path
Each StarTeam project has at least one view, and may have multiple views. A view defines
the files and folders that can be accessed for a given project. Select an existing view from
the View Path drop-down list.
Folder Path
By default, the folder path is set to the project's root folder. To choose a different folder,
click the ellipsis () button, and select the directory.
This is the user name used to log on to the selected StarTeam Server. This field is not
editable.
This is the path to the local directory containing your project. This field should not require
editing.
5 After you have made your selections, click OK to close the StarTeam Associations dialog box.
The Manage Associations dialog box will list the StarTeam associations (server, project, view, and folder path)
and indicate that your project is associated.
6 Click Close to close the Manage Associations dialog box.
248
The project is automatically committed. StarTeam changes the file extension for the StarTeam SCC interface
configuration file from <projectfilename>.cdp to <projectfilename>.cdp.saved to disassociate the project from the
SCC interface.
The project is now associated with the StarTeam integration.
249
Place Group.
If you have not saved your project or project group, you are required to save it before continuing. When your
project or project group is saved locally, the the StarTeam Association dialog box opens. This dialog box lets
you specify the details for placing your project or project group into StarTeam.
Note:
2 In the StarTeam Association dialog box, fill in the following fields:
Field
Description
StarTeam Server
Specifies the StarTeam Server where the project will be stored. Select a server from the
StarTeam Server drop-down list. If the StarTeam Server you want to use does not appear
on the list, click the Servers button to add a new server or change the properties of an
existing server. When you select a server, the Log On dialog box requests a user name
and password. See your StarTeam administrator for your server logon name.
Project Name
Specifies the name of the StarTeam Project in the repository. Select a StarTeam project
from the Project Name drop-down list, or click the New button to create a new StarTeam
project.
When you click New, the New Project dialog box opens. Use this dialog box to specify a
project name and the default working folder. The StarTeam project name must be unique.
The directory specified in the Default Working Folder field is used as the default target
directory when the project is pulled from StarTeam.
View Path
Each StarTeam project has at least one view, and may have multiple views. A view defines
the files and folders that can be accessed for a given project. Select an existing view from
the View Path drop-down list. If you created a new StarTeam project, there is only one
view, and it has the same name as the project. You can create additional views after the
project has been placed into StarTeam.
Folder Path
By default, the folder path is set to the project's root folder. To choose a different folder,
click the ellipsis () button, and select the directory.
This is the user name used to log on to the selected StarTeam Server. This field is not
editable.
By default, this is the local directory containing the the Delphi 2005 project to be placed
into StarTeam. You can click the Browse button and change the path if you expect files
that are not in the directory hierarchy of the given Delphi 2005 project to be added.
Description
File Description
Comment associated with the file revision. Type a generic description for all files in
the File Description text box, or select the Prompt For Description For Each File check
box to be prompted for separate descriptions for each file.
250
Lock Status
Specifies the type of file locking to use after files are checked in. Your lock choice lets
other team members know whether or not you are working on the files. An exclusive
lock means you intend to change the files.
Unlockedindicates you do not intend to make changes.
Exclusiveindicates you intend to make changes to these files, and prevents others
from checking the files in. Unless another user breaks your lock, no one else can create
a new revision of a locked file in the repository until you release your lock.
Non-Exclusiveindicates you are working on the files, and may possibly make
changes, but other users can alter and check in the files.
Specifies whether or not to delete the local files after the project is checked into the
repository. Either check the Delete Working Files check box to delete the files from
your workstation, storing them only in the repository, or uncheck the check box to keep
the working files in the working folder, as well as the repository.
Check the Link And Pin Process Item check box if you want to link a process item to
your files. If process rules are enforced for this project, this option is required. If the
use of process items is required, the Link And Pin Process Item check box is selected
by default. If an active process item has been set, it's pre-selected as the process item
to link. To select a process item, click the Select button to open the Select Process
Item dialog box.
If this process item is now fixed, finished, or complete as a result of placing the project
into StarTeam, then check the Mark Selected Process Item As Fixed/Finished/
Complete check box.
Revision Label
Specifies a label to assign to the checked in files. Select a label from the Revision
Label combo box or create a new revision label by typing its name. Existing labels are
listed in reverse chronological order based on the time they were created. A label is
useful if you plan to retrieve these files as a group later or if you will need this specific
revision of any of the files.
The project source files are checked into the StarTeam repository. The status of the StarTeam operation is
displayed in the StarTeam Messages window.
251
Pull.
This opens the Pull Group Or Project From StarTeam dialog box.
Note:
If none of the StarTeam Servers in your server list match server address (IP address or domain
name) of the server configuration used to check in the project or project group, you are asked
if you want to indicate a specific server to use for this server address.
2 In the Pull Group Or Project From StarTeam dialog box, fill in the following fields:
Field
Description
StarTeam Server
Specifies the StarTeam Server where the project is stored. Select a server from the
StarTeam Server drop-down list. If the StarTeam Server you want to use does not appear
on the list, click the Servers button to add a new server or change the properties of an
existing server. When you select a server, the Log On dialog box requests a user name
and password. See your StarTeam administrator for your server logon name.
Project Name
Specifies the name of the StarTeam Project in the repository. Select a StarTeam project
from the Project Name drop-down list.
View Path
Each StarTeam project has at least one view, and may have multiple views. A view defines
the files and folders that can be accessed for a given project. Select an existing view from
the View Path drop-down list.
Folder Path
By default, the folder path is set to the project's root folder. To choose a different folder,
click the ellipsis () button, and select the directory.
This is the user name used to log on to the selected StarTeam Server. This field is not
editable.
Type in a path to an empty local directory (new or existing) to store the project, or click the
ellipsis () button to browse to a directory. This directory will become the local workspace
for the project. The default value is based on the default working folder specified by the
team member who placed the project into StarTeam.
The Delphi 2005 project file (.bdsproj) or project group file (.bdsgroup) you want to pull.
The status of the StarTeam operation is displayed in the StarTeam Messages window.
252
5 Choose StarTeam
This does not delete files from the local working path.
253
Revert.
This discards any changes in the editor buffer for the active file, and reverts it back to the most recent revision
of the file in the repository.
2 Alternatively, you can right-click a file in the Project Manager and choose StarTeam
Revert.
Note: The StarTeam file revisions show up in the History Manager. The History Manager lets you revert a file
back to any revision of the file in the StarTeam repository. You can also revert a file back to a locally saved
revision of the file.
254
To update a project
1 Choose StarTeam
Update Project.
StarTeam updates your project source files and the project file with the latest revisions from the repository. If files
have been added to or removed from the project, the local project is updated to reflect these changes. If any files
are in a merge state, StarTeam asks if you want to merge the files.
2 If you have a file in a merge state, click Yes to merge the changes or click No to leave the working file unchanged.
3 StarTeam tells you whether you have resolved all conflicts and asks if you wish to save the file. click Yes to
To commit a project
1 Choose StarTeam
Commit Project.
If files have been added to the project, the StarTeam Add Files dialog box appears. If you have not added new
files, the Check In dialog box opens. If this is the case, proceed to Step 6.
2 Optionally (but recommended), fill in the information in the Add Files dialog box.
Dialog Box Element
Description
File Description
Comment associated with the file revision. Type a generic description for all files in
the File Description text box, or select the Prompt For Description For Each File check
box to be prompted for separate descriptions for each file.
Lock Status
Specifies the type of file locking to use after files are checked in. Your lock choice lets
other team members know whether or not you are working on the files. An exclusive
lock means you intend to change the files.
Unlockedindicates you do not intend to make changes.
255
Exclusiveindicates you intend to make changes to these files, and prevents others
from checking the files in. Unless another user breaks your lock, no one else can create
a new revision of a locked file in the repository until you release your lock.
Non-Exclusiveindicates you are working on the files, and may possibly make
changes, but other users can alter and check in the files.
Delete Working Files
Specifies whether or not to delete the local files after the project is checked into the
repository. Either check the Delete Working Files check box to delete the files from
your workstation, storing them only in the repository, or uncheck the check box to keep
the working files in the working folder, as well as the repository.
Check the Link And Pin Process Item check box if you want to link a process item to
your files. If process rules are enforced for this project, this option is required. If the
use of process items is required, the Link And Pin Process Item check box is selected
by default. If an active process item has been set, it's pre-selected as the process item
to link. To select a process item, click the Select button to open the Select Process
Item dialog box.
If this process item is now fixed, finished, or complete as a result of placing the project
into StarTeam, then check the Mark Selected Process Item As Fixed/Finished/
Complete check box.
Revision Label
Specifies a label to assign to the checked in files. Select a label from the Revision
Label combo box or create a new revision label by typing its name. Existing labels are
listed in reverse chronological order based on the time they were created. A label is
useful if you plan to retrieve these files as a group later or if you will need this specific
revision of any of the files.
Description
Comment
Compare
Lock Status
256
Check the Link And Pin Process Item check box if you want
to link a process item to your files. If process rules are
enforced for this project, this option is required. If the use
of process items is required, the Link And Pin Process Item
check box is selected by default. If an active process item
has been set, it's pre-selected as the process item to link.
To select a process item, click the Select button to open
the Select Process Item dialog box.
If this process item is now fixed, finished, or complete as
a result of placing the project into StarTeam, then check
the Mark Selected Process Item As Fixed/Finished/
Complete check box.
Revision Label
257
Change requestsclick the Show Change Requests button to display any change requests linked to the files
being checked in. If the changes in the file address any change requests, select the fixed change requests, and
check the Mark Selected Change Requests As Fixed checkbox.
9 Click OK to close the Check In dialog box.
The any new files are added and any modified project source files are checked into the StarTeam repository.
The status of the StarTeam operation is displayed in the StarTeam Messages window.
258
Team
This displays the Undo Check Out dialog, which lists the files that you checked out.
2 Select the check boxes next to the files for which you want to undo the check out.
Tip:
You can check or uncheck all of the files at once by clicking the Check All or Uncheck All buttons,
respectively.
3 Click OK.
Note:
If you have left any check boxes unchecked, this does not check in the corresponding files. It
just means that you won't undo the check out of those files and that you intend to retain any
changes to those files. You must still perform a check in on them at some point.
259
To commit a file
1 Choose the Commits tab to display a list of all potential commit candidate files.
2 Check the Trim File Path check box to limit the displayed name to the file name only.
3 For each file listed, select the Commit action from the Action drop-down list box.
When you select an action for a file, the Individual Comment tab is activated.
4 Add an individual comment for the current file.
Note:
If you want to add one comment to be applied to all of the files, use the Summary Comment,
instead.
Team
Commit Browser.
Note:
If you do not want a file to be affected by any actions, choose the No activity action.
3 Click Commit.
default, Delphi 2005 inserts your summary comment in front of any individual comment already existing for the file.
260
To view history
1 Click the Diff. and History tab in the lower pane of the Commit Browser.
2 Click Show History... to display a report of the history of changes made to the files. This report contains
To view conflicts
1 Click the Diff. and History tab in the lower pane of the Commit Browser.
2 Click Show Difference... to display a report that lists all conflicts between the selected local and remote sources.
3 Review the conflicts that are displayed in the pane. They might be colored differently, or they might be marked
with a conflict tag, depending on the source control system you are currently using.
Note:
If there are no conflicts, the system displays a confirmation alert to that effect.
4 When you have resolved the conflicts in your files, initiate the Commit Browser again and recommit the files.
261
Team
This initiates a session of your source control system, assuming it supports SCC API.
2 Perform source code control operations in the session instance.
3 Exit the session instance prior to shutting down Delphi 2005.
262
Testing Code
263
Building Tests
The structure of a unit test is largely dependent on the functionality of the class and method you are testing. The
Unit Test Wizards can help you by providing a template of the test project, setup and teardown methods, and basic
tests. You will need to add the specific test logic to test a particular method. The following procedures describe how
to build your test projects and test cases. Follow these procedures in order. The test project must be built prior to
the test cases.
New
Other.
By default, the personality is set to the same personality as the active project.
7 If you do not want the test project added to your project group, uncheck the Add to Project Group check box.
8 Click Next.
9 Choose the GUI or Console test runner, then click Finish.
The Test Project Wizard adds the necessary references to your project.
New
Other.
the class and method names, in the Available classes and methods list.
Note:
You can deselect individual methods in the list. The wizard will build test templates for the
checked methods only. If you deselect a class, the wizard will not create test templates for any
of the methods in that class.
7 Click Next.
264
The wizard creates a test case file and creates a name for the file by prefixing the name of the active code file
with the word Test. For example, if your code file is named MyProgram, the test case file will be named
TestMyProgram.
Run.
The GUI Test Runner starts up immediately on execution of your application. The list of tests appears in the left
pane of the GUI Test Runner.
3 Select one or more tests.
4 Click the Run button.
The test results appear in the Test Results window. Any test highlighted with a green bar passed successfully.
Any test highlighted in red failed. Any test highlighted in yellow was skipped.
5 Review the test results.
6 Fix the bugs and rerun the tests.
265
Concepts
.NET
266
267
an unfiltered view by design, Code Visualization will naturally expose some implementation details behind the ECO
framework.
A notable example is the fact that ECO UML packages are actually implemented as classes, as opposed to .NET
namespaces as one might expect. On a Code Visualization class diagram you will see ECO UML packages
represented as classes within your project's namespace. On an ECO class diagram however, you will see the true,
logical representation of the UML package.
In the Model View , all of your ECO UML packages and classes will be grouped under a top-level root package in
the project tree. The default name of the root ECO UML package is CoreClasses. The root ECO UML package
node (and all ECO UML packages underneath it) is distinguished from a .NET namespace node by its icon. The
icon represents a .NET namespace discovered by Code Visualization. The
package.
269
ECO Spaces
An ECO Space is a container of both a model, and of objects. At designtime, you use the Code Visualization
diagramming tools to create the ECO packages, ECO classes, and associations that exist in the model. At runtime,
the ECO Space contains all of the metadata of the model, plus the instances of the classes in your model. It is helpful
to think of the ECO Space as an instance of a model, much like an object is an instance of a class. The objects
contained in the ECO Space retain the domain properties (attributes and operations) and relationships defined in
the model.
270
As a container of objects, an ECO Space is both a cache, and a transactional context. Within the ECO Space, a
component called a persistence mapper is used as the conduit between the real persistence layer (either an RDBMS
or XML file), and the instances of the classes defined in the model. When you configure an ECO Space in the IDE,
you will select the ECO UML packages that you wish to persist from your model.
When you work with the ECO wizards, Delphi 2005 automatically generates code to create an ECO space type for
your application (a subclass of the DefaultEcoSpace class), and to create an instance of that type at runtime. Typically
you will have only one ECO space type in your application. You might, however, instantiate multiple instances of the
same ECO space. This is true when using the ECO framework with ASP.NET, but it is also possible in an ordinary
ECO Windows Forms application. In the ASP.NET case, the ECO framework transparently handles pooling, reuse,
and synchronization of multiple ECO spaces through a component called the EcoSpaceStrategyHandler.
The EcoSpaceStrategyHandler transparently manages pooling of ECO spaces. Another component called a
PersistenceMapperProvider manages database connection pooling. This is of primary concern in ECO ASP.NET
applications, but it can be put to use in Windows Forms applications as well. The PersistenceMapperProvider takes
over database connection and configuration from the ECO space; multiple instances of an ECO space can share a
PersistenceMapperProvider component.
Borland.Eco.Services
The Borland.Eco.Services namespace is the primary interface to the runtime capabilities of the ECO
framework. Please refer to the link at the end of this topic for more information on the ECO Service interfaces.
Borland.Eco.Handles
The Borland.Eco.Handles namespace contains the components used for defining and interacting with ECO
Spaces. The components in this namespace fall into three categories:
The DefaultEcoSpace class
The OclVariables class
The ElementHandle abstract class
You cannot instantiate a DefaultEcoSpace object directly; instead the IDE will generate a subclass for you when you
use either the ECO Application wizard, or the ECO Space wizard.
The ElementHandle class is the abstract superclass of all model elements. ElementHandle objects represent values.
The handle might represent a single value, a collection of values, or its value might be calculated with OCL.
Subclasses of ElementHandle implement the IListSource interface, making them suitable for use as data sources
in the .NET databinding architecture.
The OclVariables class is a container of named handles. You can construct OCL expressions that reference these
handles by their name (hence, the class name, OclVariables). The value of the handle will be retrieved when the
OCL expression is evaluated.
Please refer to the link at the end of this topic for more information on ECO handles and OclVariables.
Borland.Eco.ObjectRepresentation
There are two ways to access the objects in your application's ECO Space. The most direct way is to simply access
the class' properties and methods through the source code generated from your class diagrams. The
271
Borland.Eco.ObjectRepresentation namespace provides a second, more generic way that does not involve
direct use of the types defined in the generated source code.
Instead, access to the objects is accomplished through a set of interfaces that all ECO classes implement indirectly
(i.e. they are implemented for you by automatic code generation tools). As the name ObjectRepresentation
implies, these interfaces expose the model the way it is represented internally within the ECO Space. One reason
for accessing objects through the ObjectRepresentation interfaces is if you are creating user interface controls
that are designed to work with objects in an arbitrary ECO Space.
The second, more common use of the ObjectRepresentation interfaces is when you work with ECO services
such as persistence, Undo/Redo, and OCL evaluation; these services are defined in the Borland.
Eco.Services namespace, and are available through your application's ECO Space object. The Services APIs
take the ObjectRepresentation interfaces as parameters, and return references to them, which you can then
use to call methods on the interface, or to cast to a type defined in your model. The following diagram shows the
interfaces defined in the ObjectRepresentation namespace.
The root interface, IElement, contains a property called ContentType that you can examine to determine how to cast
the interface reference. IElement represents all runtime elements, including the objects themselves, their attributes,
the associations between classes in your model, and primitive types such as strings and collections. The key to
linking your model as it exists on the class diagrams with its representation within the ECO framework, is to think of
a single class as a generic container of elements. These elements might be primitive types, or objects, or they might
be collections themselves, which in turn contain more elements.
For example, suppose you had a Person class in your model. You can think of this class as a container for a set
of properties, such as personName, home, and ownedBuildings. The property personName is a string (a
primitive type), the property home is an object of another class (which has its own set of properties), and
ownedBuildings is a collection of objects. The following diagram shows how the mapping is made from the source
code declaration to the interfaces implemented by the object's representation within the ECO Space.
272
Please refer to the link below, Working with ECO Handles, for example code that demonstrates how to cast between
ECO types and model types.
Borland.Eco.Subscription
The Borland.Eco.Subscription namespace contains interfaces and classes allowing you to work directly with
the framework's subscription mechanism. Please refer to the topic Working with ECO Subscriptions for more
information.
Borland.Eco.Persistence
The Borland.Eco.Persistence namespace contains classes related to saving objects in an ECO Space out to
disk. The ECO framework supports persistence to either a relational database, or an XML file. There are three
components of primary interest in this namespace:
PersistenceMapperBdp. This component is used for saving objects to a relational database using the Borland
Data Provider classes for database connectivity.
PersistenceMapperSqlServer. This component uses the SqlConnection component for database connectivity.
PersistenceMapperXML. This component is used to store objects in an XML file.
These components are used by dropping them onto the ECO Space designer, (or the Persistence Mapper
Provider designer) and then connecting them to the PersistenceMapper property of the application's ECO Space.
The namespaces nested within Borland.Eco.Persistence contain the classes that implement the default
attribute mappings for the supported databases.
Borland.Eco.UmlRt
The Borland.Eco.UmlRt namespace implements of a subset of the foundation package in the UML metamodel
version 1.4. The interfaces in this namespace are used to access the UML elements that comprise the model's type
system. The type system is comprised of model metadata, and as stated above, is contained within the ECO space.
Data pertaining to the types within the type system are accessed through the interfaces in the UmlRt namespace.
For example, this namespace contains the interfaces IClassifier, IClass, and IAttribute. These interfaces are used
to access the classes and attributes of the model.
The top-level interface is IEcoTypeSystem. There are two ways to access the type system:
The TypeSystem property of the EcoSpace class.
Through the GetEcoService method, available on classes that implement the IEcoServiceProvider interface.
273
Once you have the IEcoTypeSystem, you can examine its properties, such as AllClasses, which returns a collection
of metadata on all of the UML classes defined in the model.
274
Each ECO service is declared in the Borland.Eco.Services namespace. Individual services are listed in the
table below.
TProject10EcoSpace = class(Borland.Eco.Handles.DefaultEcoSpace)
private
procedure InitializeComponent;
class var fTypeSystemProvider: ITypeSystemService;
class var fTypeSystemProviderLock: Tobject;
strict protected
function GetTypeSystemProvider: ITypeSystemService; override;
public
275
constructor Create;
class constructor Create;
class function GetTypeSystemService: ITypeSystemService; static;
procedure UpdateDatabase;
function get_PersistenceService: IPersistenceService;
property PersistenceService: IPersistenceService read get_PersistenceService;
function get_DirtyListService: IDirtyListService;
property DirtyListService: IDirtyListService read get_DirtyListService;
function get_UndoService: IUndoService;
property UndoService: IUndoService read get_UndoService;
function get_TypeSystemService: ITypeSystemService;
property TypeSystemService: ITypeSystemService read get_TypeSystemService;
function get_OclService: IOclService;
property OclService: IOclService read get_OclService;
function get_ObjectFactoryService: IObjectFactoryService;
property ObjectFactoryService: IObjectFactoryService read get_ObjectFactoryService;
function get_VariableFactoryService: IVariableFactoryService;
property VariableFactoryService: IVariableFactoryService read
get_VariableFactoryService;
end;
276
if (typeSystemProvider == null)
typeSystemProvider = MakeTypeService(typeof(Project11EcoSpace));
}
return typeSystemProvider;
}
protected override ITypeSystemService GetTypeSystemProvider()
{
return Project10EcoSpace.GetTypeSystemService();
}
//
// Services
//
public IPersistenceService PersistenceService
{
get { return (IPersistenceService)GetEcoService(typeof(IPersistenceService)); }
}
public IDirtyListService DirtyListService
{
get { return (IDirtyListService)GetEcoService(typeof(IDirtyListService)); }
}
public IUndoService UndoService
{
get { return (IUndoService)GetEcoService(typeof(IUndoService)); }
}
public ITypeSystemService TypeSystemService
{
get { return (ITypeSystemService)GetEcoService(typeof(ITypeSystemService)); }
}
public IOclService OclService
{
get { return (IOclService)GetEcoService(typeof(IOclService)); }
}
public IObjectFactoryService ObjectFactoryService
{
get { return (IObjectFactoryService)GetEcoService(typeof
(IObjectFactoryService)); }
}
public IVariableFactoryService VariableFactoryService
{
get { return (IVariableFactoryService)GetEcoService(typeof
(IVariableFactoryService)); }
}
//
// Misc helper functions
//
public void UpdateDatabase()
{
if ((PersistenceService != null) && (DirtyListService != null))
{
PersistenceService.UpdateDatabaseWithList(DirtyListService.AllDirtyObjects());
}
}
}
277
When you add more ECO-enabled forms to your application using the ECO Enabled Windows Form wizard, the
IDE will generate a new form class with a constructor that takes an instance of an ECO space as a parameter. In
addition, and similar to the main form, each subsequent ECO enabled windows form you create with the wizard will
have its own EcoSpace property. The constructor initializes this property with the ECO space parameter. An ECO
application only has one instance of an ECO space, so the typical usage scenario is to pass the ECO space instance
from the main form to secondary forms when they are created. The following example creates a new ECO enabled
form in response to a button click on the main form:
278
Other service interfaces and their methods can be called using a similar technique. Each ECO service interface and
its purpose is shown in the following table.
Interface
Description
IStateService
Allows you to discover whether a particular object or property in the ECO space has been
modified.
IPersistenceService
Provides a consistent API for you to update objects in the ECO space, without regard to
the persistence mechanism.
IDirtyListService
Allows you to retrieve a list of all modified objects, and to query the ECO space to discover
whether any objects have been modified. An object is considered modified if it does not
have the same state in memory as in persistent storage.
IExtentService
Allows you to query the ECO space for all instances of a certain class.
IObjectFactoryService
Provides methods for you to create new instances of the classes in your model. The
IObjectFactoryService interface methods create new objects using their type information.
279
This approach is more generic than directly creating a new object by calling the C# new
method, or the Delphi Create method.
IVariableFactoryService
ITypeSystemService
Allows you to get the type system of the model, and to validate the model programmatically.
IVersionService
For domain classes that have been marked as versioned, this interface allows you to get
a specific version of an object from persistent storage.
IOclService and IOclTypeService These interfaces allow you to evaluate expressions in Object Query Language (OCL).
IOclService is a descendent of IOclTypeService. Only the IOclService interface is exposed
through the ECO space.
IUndoService
IExternalIdService
Returns a globally unique ID for an ECO object, regardless of whether the object has been
saved in persistent storage. This ID is only valid within the ECO space where the ID
originated. This service is intended primarily for use in ASP.NET applications.
280
The diagram shows the relationships between the various kinds of ECO handles.
281
Rooted handle: Evaluation of a rooted handle begins in the context established by the previous handle. The
previous handle can either be a root handle, or another rooted handle.
Handles represent objects and values, therefore, they are also the link between the ECO space and your application's
user interface. All ECO handles can be used as .NET data sources for GUI components. The ECO framework uses
standard .NET data binding mechanisms. Once you bind a GUI component to a handle, you can work with the
component the same way as you would if it were bound to any other kind of data source.
Root Handles
If rooted handles are the individual links in the chain, then a root handle is the spike that is hammered into the ground
to anchor the chain. The ground is your application's ECO space.
There are two important designtime properties of root handles that must be set to establish the initial context: the
EcoSpaceType property, and the StaticValueTypeName property.
The EcoSpaceType property points to your application's ECO space. The EcoSpaceType property gives the root
handle the type system of the model, and a link to the runtime world where objects live.
The StaticValueTypeName property determines the type of object to which the root handle will refer. This property
is used by the IDE during designtime to establish a context for the OCL Expression Editor. At runtime, the framework
will throw an exception if the root handle is ever set to reference an object that does not match the type set in the
StaticValueTypeName property.
At runtime, you can set the Element property of a root handle to refer to a specific object in the ECO space. Root
handles are the only handles that have a writable Element property. Evaluation of the rooted handles in the chain
begins with the object referenced by the root handle.
Rooted Handles
Rooted handles have a property called Expression. The Expression property is an OCL expression that, when
evaluated, produces an object, a set of objects, or an atomic element such as a specific attribute or a calculated
value. When we talk about evaluating rooted handles within a certain context, we are actually talking about the
context for the handle's OCL expression. The context begins at the root handle, and evolves through the chain of
rooted handles.
ReferenceHandle
The ReferenceHandle is a concrete descendent of the RootHandle class. The EcoSpaceType property must be
configured at designtime to refer to your application's ECO space. The handle's StaticValueTypeName property
should also be configured, as this will provide additional designtime assistance in the OCL Expression Editor, as
well as runtime type checking on the handle's Element property.
Every form that needs access to the objects in the ECO space must have at least one instance of a ReferenceHandle.
The ECO Application wizard automatically generates a ReferenceHandle for the main form. The default name of
this ReferenceHandle is rhRoot. For secondary forms, the ECO Enabled Windows Form wizard generates a
ReferenceHandle, also having the default name rhRoot.
VariableHandle
Unlike a ReferenceHandle, a VariableHandle holds a value that does not exist in the ECO space. You configure a
VariableHandle with an ECO space and a StaticValueTypeName, however, a VariableHandle is typically not used
282
to reference objects of classes defined in the model. Instead, a VariableHandle holds values of atomic data types
such as the .NET type System.Int32. This is because a VariableHandle holds an indirect reference to the object,
unlike a ReferenceHandle, which holds the object directly.
A VariableHandle can be used as a data source for GUI components; they are typically used in conjunction with
OclVariables objects to create parameters for use in OCL expressions.
ExpressionHandle
An ExpressionHandle references an object or a list of objects through the evaluation of its OCL expression.
You must link the RootHandle property of an ExpressionHandle with either a root handle (an instance of a
ReferenceHandle or VariableHandle class), or another ExpressionHandle.
You configure the Expression property of the ExpressionHandle using the OCL Expression Editor. When you open
the OCL Expression Editor, the context of the expression (the type of the OCL keyword self) is determined by the
type of the result returned by the previous handle in the chain. If the previous handle is a root handle, the type is
determined from the StaticValueTypeName property. If it is another ExpressionHandle, the type is determined from
the Expression property of that handle.
OclPSHandle
Unlike an ExpressionHandle, an OclPSHandle is always executed against persistent storage, rather than data in
memory (i.e. in the ECO space). Therefore, the result of executing an OclPSHandle is a static snapshot of the
contents of persistent storage.
An OclPSHandle has a method called Execute. The handle's OCL expression is not evaluated until the Execute
method is called. Usually, you will call the Execute method in response to some event on a form, such as a button
click.
An OclPSHandle is typically used when the OCL expression has an intermediary part that results in a large number
of objects, and a subsequent part that filters the set down to a smaller number. For example, a call to
allInstances followed by a select statement.
The OCL expression is first mapped to a SQL query, which is then evaluated by the database. A select statement
in an OclPSHandle will therefore be able to take advantage of any indices defined within the database. With an
ExpressionHandle, the entire set of objects would be created and then processed in memory.
Since the OCL expression of an OclPSHandle is first mapped to SQL, there are some restrictions on OCL constructs
that you can use. The following operations and constructs are supported:
Navigation: You can freely access attributes and roles defined in the model. However, derived and nonpersistent attributes and roles cannot be used in the expression, since the database has no knowledge of them.
List operations: select, reject, allInstances, size, orderBy, minValue, maxvalue, average, sum,
exists, forall, notEmpty, isEmpty, and union are supported.
Boolean operators: =, <, >, <=, >=, <>, and, or, not, xor, sqlLike, sqlCaseInsensitiveLike are
supported.
Arithmetic operators: +, *, /, -, div, mod are supported.
Enum: Enumerated constants are supported.
Type operations: oclIsKindOf, oclIsTypeOf, oclAsType are supported.
283
var
E : Borland.Eco.ObjectRepresntation.IElement;
O : System.Object;
P : Person;
begin
E := rhPerson.Element;
O := E.AsObject;
P := O as Person;
P.DoSomething;
// Now you can call methods and access attributes of the Person class.
// This code could be abbreviated...
P := (rhPerson.Element.AsObject) as Person;
284
P.DoSomething;
// Abbreviating even more...
(rhPerson.Element.AsObject as Person).DoSomething;
end;
Borland.Eco.ObjectRepresentation.IElement E;
System.Object O;
Person
P;
E = rhPerson.Element;
O = E.AsObject();
P = O as Person;
P.DoSomething(); // Now you can call methods and access attributes of the Person class.
// This code could be abbreviated...
P = (rhPerson.Element.AsObject) as Person;
P.DoSomething();
// Abbreviating even more...
(rhPerson.Element.AsObject as Person).DoSomething();
When the element referenced by the handle is a collection, you must first cast the Element property to the ECO
interface IObjectList. In the following code, the variable ehAllPersons is an ExpressionHandle. It is also assumed
the list returned by this expression contains at least three elements. The Expression property has been set to retrieve
all instances of the Person class from the ECO space.
var
L : Borland.Eco.ObjectRepresentation.IObjectList;
O : System.Object;
P : Person;
begin
L := ehAllPersons.Element as IObjectList; //
O := L[2].AsObject;
//
cast it to a System.Object
P := O as Person;
//
P.DoSomething;
//
class.
Borland.Eco.ObjectRepresentation.IObjectList L;
System.Object O;
Person P;
L = ehAllPersons.Element as IObjectList;
285
O = L[2].AsObject;
it to a System.Object
P = O as Person;
P.DoSomething();
286
These two parameters correspond to the two different kinds of subscriptions you can place: Reevaluate
subscriptions, and ReSubscribe subscriptions. The difference between them has to do with the impact any change
in the ECO space has on existing subscriptions. All changes will always cause a reevaluation to occur, so that
subscribers will be informed when they must reevaluate a particular data value. In addition to the reevaluation of
data, some changes in the ECO space also require additional subscriptions to be created. The difference between
the two kinds of subscriptions is illustrated in the following example.
287
You have a model that contains a Person class and a Building class. You have drawn an association between
these two classes such that a person can own zero or many buildings. In addition, you have an association between
a Building and a Person, such that a building can have zero or many residents (i.e. instances of the Person
class). These relationships are shown below.
At some point while your application is running, the ECO space contains one person object, and this person owns
two buildings. You have built the following OCL expression to retrieve all the residents in all the buildings owned by
a person:
self.ownedBuildings.residents
If a new Person is created and added to the list of residents for building B1, the result would be as shown:
Notice in the diagram, that adding a new person as a resident in an existing building changed the result set of our
OCL expression, but it did not impact the set of subscriptions itself. This kind of change would trigger only a reevaluate
subscription. But what would happen to the subscriptions if you added a new building, with its own set of residents?
The result is shown in the next diagram.
288
In this diagram you can see that the change not only affected the result set, but it caused a new subscription to be
added as well. This kind of change triggers both a reevaluate and a resubscribe subscription.
The rule of thumb is that if a change occurs in the last element of an OCL navigation (in this example, in the residents
relation) only the value needs to be reevaluated (i.e. a reevaluation is required). If a change occurs anywhere else
in the navigation (in this example, in the ownedBuildings relation), both the value and the subscriptions must be
reevaluated (i.e. a reevaluation and a resubscription are required).
Having two different subscribers allows you to take different actions when these two types of subscriptions occur.
When working with the EvaluateAndSubscribe method, you can pass a null value for either subscriber parameter if
you are not interested in that kind of subscription. You can also pass the same subscriber to both parameters; this
will cause a minor impact in performance, as a resubscription will be performed in those cases where only a
reevaluation is required.
You must replace attributeName with the name of the attribute you are deriving. For example, in our Person
class, if we wanted to derive the attribute called fullName in source code, we would implement the method
Please refer to the procedure Deriving an Attribute in Source Code for an example of computing a value and placing
subscriptions in source.
289
290
const
MODE: EcoSpaceStrategyHandler.SessionStateMode =
EcoSpaceStrategyHandler.SessionStateMode.Always;
The following table shows the possible values and their meaning.
Value
Meaning
Never
Never cache the ECO space in the session state. Instead, return it to the pool, or (if pooling is not used), release
it. Any unsaved changes to the ECO space will be lost.
Always
Keep a private ECO space in the session state for the duration of the session. When the session ends, the ECO
space will be returned to the pool, or (if pooling is not used), discarded.
This has the advantage that the ECO space will always contain the objects used by the session, which may be
more efficient than getting one from the pool that could have different contents.
The disadvantage of this mode is that it ties up resources much longer.
291
IfDirty Keep the ECO space in the session state if it contains dirty objects. This mode allows applications to keep state
over multiple requests.
Keeping ECO spaces in the session state ties up resources on the server, so it is most efficient to write applications
so that they are stateless. Stateless applications can set SessionStateMode to Never.
Note: The code generated by the template for an ECO Web Service application does not use the session state
mode property. Instead, ECO web service applications use the methods
EcoSpaceProvider.GetSessionFreeEcoSpace and
EcoSpaceProvider.ReturnSessionFreeEcoSpace. These methods ensure that the web service is
always stateless.
292
293
Meaning
Ignore
No action will be performed; the change will be removed from the list. This action should only be taken if you
have handled the conflict in some other manner. For example, by directly changing the object.
Discard
The value in the ECO space will be marked as invalid, which will lead to the value being reread from the database
next time it is accessed. If value was dirty, the new value will be discarded. This option is semantically identical
to Reread.
Reread
The value is reread from the database. If value was dirty, the modified value will be discarded.
Keep
The ECO space will update its notion of the old value to the one currently in the database, but will keep any
modified value. If the value is not modified Keep is identical to Reread.
Verify
Verify that the potential conflict is in fact a conflict by reading the value from the database. This can happen if
another user has modified only parts of an object that are not loaded in this ECO space. It will also happen if
the ECO space has lost and reestablished contact with the persistence server. In this case all loaded objects
will be marked as potential conflicts.
Undecided No action is performed, and the change is left in the list. This is the action set on any new changes discovered.
The Sync method of the IPersistenceService interface will resolve all changes where it is safe, for example, where
the element in question has not been modified. The example code below shows how a method like Sync could be
implemented.
294
Change: IChange;
EcoSpace.PersistenceService.RetrieveChanges();
Changes = EcoSpace.PersistenceService.GetChanges();
foreach Change in Changes {
if(!Change.IsDirectConflict) {
if(ReadNewValues)
Change.Action = ChangeActionKind.Reread;
else
Change.Action = ChangeActionKind.Discard;
}
}
EcoSpace.PersistenceService.ApplyAll();
}
295
296
Legend:
[ ] indicates that an attribute is optional
+ means that a node must occur at least once
* means that a node can occur zero or more times
? means that a node is optional
1 means that a node must occur exactly once
Description
<Globals> Element
<Classes> Element
A top level node that groups all the classes defined in the OR mapping file.
<ClassDef> Element
The storage characteristics of each class are contained within a <ClassDef> element and
its subnodes.
<AliasDef> Element
For each class, you must specify a reference to the table(s) that will store instances of the
class. These table references are contained in <AliasDef> elements. Note that multiple
aliases can refer to the same table, if the same table stores instances of multiple classes.
<KeyDef> Element
<KeyColumn> Element
The keys that exist to identify each instance of the class are specified in <KeyImpl>,
<KeyColumn> and <KeyDef> nodes. Each <ClassDef> can contain multiple keys, but
exactly one of them must specify a key to be the ID. The ID key is the unique identity of an
object in the object layer. It is normally also the primary key of the object in the database.
<ConstantColumn> Element
A constant column is used if a column should always have the same value.
<DiscriminatorDef> Element
<KeyImpl> Element
<DiscriminatorImpl> Element
<DiscriminatorValue> Element
<DiscriminatorColumn> Element
<AttributeDef> Element
<SingleLinkDef> Element
The <SingleLinkDef> element must be specified when the model requires that a class has
a singlelink relationship to another class.
The <AliasDef>, <KeyDef>, <DiscriminatorDef>, <AttributeDef>, and <SingleLinkDef> elements are all inherited by
subclasses in the custom OR mapping file. If a <ClassDef> element specifies a superclass, that <ClassDef> will
inherit all of the elements in its superclass, all the way up the inheritance chain.
297
298
using Borland.Eco.Windows.Forms;
// ...
[STAThreadAttribute]
public static void Main()
{
// Add this line
WinFormDequeuer.Active = true;
System.Windows.Forms.Application.Run(new MainForm());
}
uses
// ...
Borland.Eco.Windows.Forms;
// ...
[STAThreadAttribute]
begin
// Add this line
WinFormDequeuer.Active := True;
Application.Run(TWinForm.Create);
end.
If you do not make this change, none of the GUI components will be updated when you perform operations on the
ECO space (for example, creating new objects and changing attribute values on existing objects).
299
Referenced Assemblies
The classes and components that are dependent on System.Windows.Forms have moved to a separate assembly
called Borland.Eco.Windows.Forms. You need to add this assembly to the list of required assemblies in order
for your application to compile. You might also need to add new namespaces to your uses/using clauses.
Removed Properties
In Delphi 2005, a large number of ECO properties have been assigned default values so that they will no longer be
generated in the WinForm Initialization procedure. Some properties have also been removed. Most of these
properties still exist as a "write only" property for backwards compatibility, so the next time a form with such a property
is loaded, the property is consumed and then not generated back again. Here is a list of removed properties:
PersistenceMapperDb.Active: Persistence mappers are activated automatically when the ECO space
needs a database connection.
EcoExtenders.EcoSpace: Extenders now get their ECO space (or service provider) by connecting to a
handle instead
PersistenceMapperDb.ClockLogGranularity: This property has changed name to VersionGranularity
and type to DateTime.
PersistenceMapperDb.EvolutionSupport: This property has been removed.
PersistenceMapperDb.SqlDatabaseConfig.UseXFiles: This property has been removed.
Updates to EcoListActionExtender
The EcoListActionExtender was formerly connected to a CurrencyManagerHandle. This is no longer the case.
Instead, you need to connect the components (e.g. a button) that use the extender to a BindingContext. This would
normally be a data grid or list box containing the list that the EcoListActionExtender should act upon.
So, code such as the following
300
Self.EcoListActions.SetCurrencyManagerHandle(Self.Button2, Self.cmAllPersons);
this.EcoListActions.SetCurrencyManagerHandle(this.Button2, this.cmAllPersons);
Self.EcoListActions.SetBindingContext(Self.Button2, Self.lbxAllPersons);
this.EcoListActions.SetBindingContext(this.Button2, this.lbxAllPersons);
CreateDataBaseSchema(GetTypeSystemService(), DefaultCleanPsConfig.Create(true));
CreateDataBaseSchema(GetTypeSystemService(), FormBasedConfigureCleanPS.Create());
301
302
ASP.NET Overview
ASP.NET is the .NET programming environment for building applications in HTML that run on the Web. This topic
provides introductory information about the major components of the ASP.NET architecture and explains how ASP.
NET integrates with other programming models in the .NET framework. This topic introduces:
ASP.NET Architecture
Web Forms
Data Access
Web Services
Designtime Features
Supported Web Servers
Sample Applications
ASP.NET Architecture
The major components of the ASP.NET architecture are Web Forms, ASP.NET server controls, code-behind logic
files, and compiled DLL files. Web Form pages contain HTML elements, text, and server controls. Code-behind files
contain application logic for the Forms page. Compiled DLL files render dynamic HTML on the web server.
Borland provides tools to simplify ASP.NET development. If you are familiar with rapid application development
(RAD) and object oriented programming (OOP) using properties, methods, and events, you will find the ASP.NET
model for building Web applications familiar.
303
HTML elements are static, client-side controls; they are not, by default, programmatically accessible. However, they
are well suited for static text and images on a Web Form.
Data Access
Web Forms access data through ADO.NET. You can connect an ASP.NET application to an ADO.NET data source
by using the data components included in the .NET Framework or the Borland Data Provider (BDP.NET) components
included with Delphi 2005. BDP.NET components connect to a several industry standard databases.
The Borland DB Web Controls are data-aware components that simplify database development tasks. They work
with both the BDP.NET and .NET Framework data components. The DB Web Controls provide advanced
functionality and include grid, navigator, calendar, combobox, sound, and video components.
Web Services
Web Services provide application components to many distributed systems using XML-based messaging. A web
service can be as simple as an XML message updating values in a remote application or can be an integral part of
a sophisticated ASP.NET or ADO.NET application. Web Services and ASP.NET share a common .NET infrastructure
that allows for seamless integration.
Designtime Features
Delphi 2005 provides several designtime features to help you accelerate the development of Web Forms, HTML,
and CSS files.
you can set it as the default formatter, instead of the internal formatter. You can also define tags that HTML Tidy
would otherwise detect as invalid, such as those prefixed with asp:. To access the HTML Tidy options, choose
Tools
Options
HTML Tidy Options.
The Structure View displays a hierarchical tree view of the HTML tags in the active HTML page and is useful for
navigating large files. Double-clicking a node in the tree view positions the HTML file to the corresponding tag.
on the HTML
To permanently change the layout for new files created with Delphi 2005, you can edit the page.aspx template file
located at, by default, \BDS\3.0\Objrepos\DelphiDotNet.
Sample Applications
Delphi 2005 includes several ASP.NET sample applications in the Demos directory (located, by default, at C:
\Program Files\Borland\BDS\3.0\Demos). Many of the sample applications include a readme file that explains the
application and lists any prerequisites. Before you attempt to open a sample application in the IDE:
Check for a readme file in the application's directory and follow any set up instructions.
Create a virtual directory for the sample application to avoid resource cannot be found errors in the browser at
runtime (see the procedure listed at the end of this topic).
305
306
The DBWebNavigator control provides navigation capabilities for grids, multiple text controls, and can be
extended to standard web controls.
The DBWebDataGrid provides built-in capabilities for paging with numbers and icons, for adding Edit and Delete
columns, and other advanced capabilities. In other words, you no longer need to code these features into your
grid control.
Description
DBWebDataSource
Acts as a bridge between the data source and the DBWeb controls.
DBWebAggregateControl
Text box control that displays aggregate values from a specified column.
DBWebCalendar
A calendar control.
DBWebCheckBox
DBWebDropDownList
DBWebGrid
A data grid.
DBWebImage
An image control.
DBWebLabel
A label.
DBWebLabeledTextBox
DBWebListBox
DBWebMemo
DBWebNavigationExtender A non-visual component that allows you to define standard web control buttons as navigation
controls.
DBWebNavigator
A navigation bar.
DBWebRadioButtonList
DBWebSound
A sound control, which uses the default media player on your system.
DBWebTextBox
A text box.
DBWebVideo
A video control, which uses the default media player on your system.
308
Runtime Properties
At designtime, when a DBWeb control points to a DataView, the control is automatically updated whenever there is
a change to any DataView property that controls the rows to be displayed. To change the DataView properties at
runtime, you must make sure that the change is in place prior to the rendering of any of the DB Web Controls.
For example, if you use a listbox to set the filter, you would also:
Set the listbox AutoPostback property to True.
Add code in the Page_Load event to handle setting the RowFilter.
Add code in the Page_Load event to call the ClearSessionChanges method after the RowFilter has been
changed.
Assume you have two tables on a form. You bind an ASP.NET listbox to one table that contains lookup values.
These values serve as a filter for the second table, whose values display in a DBWebGrid. Set the AutoPostback
property in the listbox to True, handle the RowFilter setting in Page_Load, and call ClearSessionChanges after
changing the RowFilter.
Tip: If you set the AutoRefresh property to False, which is the default, you might end up using cached data. Review
the WorldTravel demo in \Demos\DBWeb to see an example of how this is handled.
Master-Detail Relationships
You can make a DataView the master table in a master-detail relationship by adding a row filter. Set up a masterdetail relationship with two or more DataTables within a single DataSet, then connect the DataView to the master
DataTable. When the DBWebDataSource connects to the DataView, the DB Web Controls will let you select either
the parent table, which is the DataView, or the detail table.
ClearSessionChanges Method
The ClearSessionChanges method notifies the DBWebDataSource that the DataSet has changed and that existing
row, column, and changed data information is no longer valid. All pending changes are removed. If you try to call
this method from a DBWebNavigator button click event, the DBWebNavigator button will not work.
DataView Limitations
There are some limitations with the DataView:
Inserted rows in a DataView behave differently than inserted rows in a DataTable.
A DataView does not allow multiple inserts of null records. This means that you must add data to an inserted
row before adding a new inserted row.
309
If an inserted row is deleted, that row is removed from the DataView and you cannot use Undo to recall it.
If an inserted row contains a single non-null value, and that value is set to null, the row can be deleted in some
cases and cannot be recalled.
DBWeb controls do not provide full support for the DataViewSort property. If a sort field is encountered, the
values for the fields contained in the Sort property cannot be changed, and the insert key will be disabled on
the DBWebNavigator.
310
IDBWebDataLink
All DB Web Controls implement this interface. The interface defines a data source and a data table, allowing you to
connect to and access data from a variety of data sources, including databases, text files, arrays, and collections.
If your control only needs to access data at the table level, you implement this interface.
IDBWebColumnLink:IDBWebDataLink
This interface is implemented by DBWeb column controls, such as DBWebImage,DBWebTextBox, and
DBWebCalendar, among others. The interface defines a column name to which a column control is linked. In
combination with the IDBWebDataLink interface, this interface provides access to standard table and column data.
IDBWebLookupColumnLink:IDBWebColumnLink
This interface is implemented by DBWeb lookup controls, such as DBWebListBox,DBWebRadioGroup, and
DBWebDropDownList. The interface defines a TableName within a DataSet, a ColumnName representing a table
that contains the data to be displayed in the lookup, and the column containing the values which, when a value is
selected, are to be placed into the ColumnName field linked to the control. By default, the ColumnName field is the
same as DataTextField. Lookup controls contain not only a text property, usually the item that is displayed in the
control, such as a listbox, but also a value property. The value property might be identical to the text property, or it
might contain a completely different piece of data, such as an identification number. For example, you might choose
to display product names in a listbox or a drop down listbox, but set the values for each displayed item to their
respective product IDs. When a user selects a product name, the product ID is passed to the application, rather than
the name of the product itself. One benefit of this approach is to eliminate processing confusion between products
with similar names.
311
Cascading Deletes
In a master-detail application, the application typically uses an OnApplyChanges event to send the DataSet changes
to the server. It is necessary for the master data adapter's update method (in BDP.NET, the AutoUpdate event) to
be called prior to the detail data adapter's update method. Otherwise, insertion of detail rows fails if the master row
has not yet been inserted. If the master row is deleted prior to the detail row, the server might return an error.
The property CascadingDeletes has been added to the DBWebDataSource control. The CascadingDeletes property
specifies how the server deletes rows in master-detail applications. The CascadingDeletes property provides the
following three options:
NoMasterDelete (Default)
ServerCascadeDelete
ServerNoForeignKey
Note: When DB Web Controls are connected to a DataTable that is a detail table in a relation, the control's rows
are automatically limited to the rows controlled by the current parent row in the master table.
NoMasterDelete
This option does not allow deletion of a master row containing detail rows. This option should be used when the
server enforces a foreign constraint between master and detail, but it does handle cascading deletes. You must:
1 Delete detail rows.
2 Apply the changes with an apply event (for example, the BdpDataAdapter. AutoUpdate event).
3 Delete the master row.
4 Call the apply event (for example, the BdpDataAdapter. AutoUpdate event).
ServerCascadeDelete
This option allows deletion of the master row. This option should be specified whenever the server is set up to
automatically handle cascading deletes. When a master row is deleted, the detail rows will automatically disappear
from view. Any time prior to applying the change, you can undo the parent row deletion and all the detail rows come
back into view. If the server is not set up to handle cascading deletes, an error may occur when attempting to send
changes to the server.
ServerNoForeignKey
This option automatically deletes all detail rows whenever a master row is deleted. This option should be specified
whenever there are no foreign key constraints between the master-detail tables on the server. Like the
312
ServerCascadeDelete option, when a master row is deleted, the detail rows will automatically disappear from view.
Any time prior to applying the change, it is possible to undo the master row deletion to redisplay the detail rows. If
you specify this option and foreign key constraints exist between master and detail tables, an error will be thrown
by the server when attempting to delete the master table.
Cascading Updates
In a master-detail application, the application typically uses an OnApplyChanges event to send the DataSet changes
to the server. It is necessary for the update method of the master data adapter (in BDP.NET, the AutoUpdate event)
to be called prior to the update method of the detail data adapter. Otherwise, insertion of detail rows fails if the master
row has not yet been inserted. If the master row is deleted prior to the detail row, the server might return an error.
The property CascadingUpdates, has been added to the DBWebDataSource control. This property specifies how
the server updates foreign-key values in master-detail applications. The CascadingUpdates property provides the
following three options:
NoMasterUpdate (default)
ServerCascadeUpdate
ServerNoForeignKey
Note: When DB Web Controls are connected to a DataTable that is a detail table in a relation, the rows of the control
are automatically limited to the rows controlled by the current parent row in the master table.
NoMasterUpdate
This option does not allow changes to the foreign key value of a master row if it has any associated detail rows. This
option is the default value for the CascadingUpdates property.
ServerCascadeUpdate
This option allows you to change the foreign key value of the master row. You should use this option whenever the
server automatically handles cascading updates. When the foreign key value of a master row is changed, the key
value is changed automatically in the detail rows. Anytime prior to applying the change, you can undo the change
to the master row and all the detail key changes will be undone also. If the server is not set up to handle cascading
updates, an error might occur when attempting to update the changes to the server.
ServerNoForeignKey
This option also allows changing the foreign key value of the parent row, but should be used whenever there is no
foreign key between the master and detail tables on the server.
313
XML file concurrently. When multiple clients are using the application, the XML file is constantly being overwritten
by different users. One way to avoid this is to write logic in your server application to check row updates and notify
various clients when there is a conflict. This is similar to what a database system does when it enforces table-level
or row-level locking. When using a text file, like an XML file, this level of control is more difficult to implement.
However, if you implement user authentication, you can create a real-world application by setting the
UseUniqueFileName property. This property specifies that the DBWebDataSource control will create uniquely
named XML files for each client that uses accesses the XML file specified in the XMLFileName property of the
DBWebDataSource. This helps avoid data collisions within a multi-user application. The drawback to this approach
is that each XML file will contain different data and your server application will need built-in logic to merge the unique
data from each client XML file.
Read-write applications using XMLFileName require that all web clients have write access to the XML files to which
they are writing. If the web client does not have write access, the client will get a permissions error on any attempt
to update the XML file. You must grant write access to the clients who will use the application.
315
Data Binding
In ASP.NET you can bind to a variety of data sources including databases, text files, XML files, arrays, and
collections. In Delphi 2005, controls provide a simple property-based interface to data sources. In the Object
Inspector, you can bind a selected control to a data source that is identified to your project by way of the BDP.NET
controls, SQL client controls, or other data or file controls. Each type of data control has different sets of binding
requirements. For instance, any collection control, such as the listbox control, data grid, or listview control, must bind
to a data source that implements the ICollection interface. Other controls, like buttons and text boxes, do not have
this requirement.
When you are programming with web controls, you must add the code to perform the data binding. For example, if
you created an instance of a data grid, the command that you would add would look like:
dataGrid1.DataBind();
When using DB Web Controls, you no longer need to add this code. DB Web Controls handle the data binding
operation for you. The DBWebDataSource component serves as a bridge between your data source component
316
and the specific DB Web control you want to use. The DBWebDataSource creates and manages the data binding
between the data source and the control. Although you can physically add the code to instantiate a DB Web control
and to perform the data binding, it is unnecessary to do so. You can drop your components onto a web form and
select the linkages from property drop down list boxes in the Object Inspector.
Note: When creating a new DB Web control or extending an existing control, you may need to add code to perform
binding of some properties.
When creating your own controls or extending existing controls, you must override the Render method to display
your control. The Render method is responsible for sending output to an instance of an HtmlTextWriter class.
HtmlTextWriter sends a sequence of HTML characters to the web forms page. The HTML characters are the
representation in HTML of your control. For example, a web grid control is represented on a web forms page as an
HTML table. Each control has its own HTML representation, but when you extend a control, you need to modify how
the HTML is emitted to accurately represent your new control.
///
///
///
///
strict protected
procedure Render(Output: HtmlTextWriter); override;
implementation
{$REGION 'Control.Render override'}
///
///
///
///
///
317
You would need to implement the preceding code even if you were trying to extend the capabilities of a standard
web control. To extend one of the DB Web Controls you need to make more adjustments to this code.
Make sure that you include the bitmap file in your project.
318
This method passes the definition of your control to an instance of HtmlTextWriter, called Output in this case. The
Text property will contain the HTML text that is to be rendered. If you wanted to code directly within the method, You
could add code, as follows:
procedure TWebControl1.Render(Output: HtmlTextWriter);
begin
Output.WriteFullBeginTag("html");
Output.WriteLine();
Output.WriteFullBeginTag("body");
Output.WriteLine();
Output.WriteEndTag("body");
Output.WriteLine();
Output.WriteEndTag("html");
Output.WriteLine();
end;
This results in an ASP.NET web page with the following HTML code:
<html>
<body>
</body>
</html>
The use of the Text property, however, makes the code easier to work with. Once you have defined your control and
its properties, along with various HTML tags, you can pass the entire structure to the Text property. From that point
319
forward, you need only refer to the Text property to act upon the control. You define the properties of your control
and pass them to the HtmlTextWriter by creating a Text property that contains the control definition. It is instructive
to look at the source code for some of the existing DB Web Controls. For example, the following code shows the
definition of the Text property for the DBWebNavigator control.
protected string Text{
get
{
// Create a new instance of StringWriter.
StringWriter sw = new StringWriter();
// Create a new instance of HtmlTextWriter.
HtmlTextWriter tw = new HtmlTextWriter(sw);
// Call the DataBind procedure.
DataBind();
// Call the AddButtons procedure.
AddButtons();
// Call the SetButtonsWidth procedure.
SetButtonsWidth();
// Add a style to a panel.
ClassUtils.AddStyleToWebControl(FPanel, this.Style);
// Render the HTML start tag for a panel control.
FPanel.RenderBeginTag(tw);
// Call the HtmlTextWriter.Write method and pass the table
//
and tablerow tags to the web forms page.
tw.Write("<table><tr>");
// If the ButtonType is set to ButtonIcons, iteratively create and render buttons
// to the web forms page.
if( ButtonType == NavigatorButtonType.ButtonIcons )
{
for( int i = 0; i < IconNavButtons.Count; i++ )
{
// Write the first table cell tag.
tw.Write("<td>");
// Instantiate an image button.
ImageButton b = (IconNavButtons[i] as ImageButton);
// Render the button on the web page.
b.RenderControl(tw);
// Write the closing table cell tag.
tw.Write("</td>");
}
}
else
// If the ButtonType is something other than ButtonIcons, iteratively create and
// Render default navigation buttons to the web forms page.
320
{
for( int i = 0; i < NavButtons.Count; i++ )
{
// Write the first table cell tag.
tw.Write("<td>");
// Instantiate a button.
Button b = (NavButtons[i] as Button);
// Render the button on the web page.
b.RenderControl(tw);
// Write the closing table cell tag.
tw.Write("</td>");
}
}
// Write the closing row and table tags.
tw.Write("</tr></table>");
// Render the Panel end tag.
FPanel.RenderEndTag(tw);
return sw.ToString();
}
}
321
{
ClassUtils.SetInnerAppearanceProperties(FLabel, this);
SetProportionalSize();
SetLabelFont();
FTextBox.Text = null;
}
// If there is a data source.
if( IColumnLink.DBDataSource != null )
{
// And if there is bound data.
if( FColumnLink.IsDataBound )
{
// Then set behavior properties.
ClassUtils.SetBehaviorProperties(FTextBox, this);
// Set appearance properties.
ClassUtils.SetAppearanceProperties(FTextBox, this);
// Set size properties.
ClassUtils.SetSizeProperties(FTextBox, this);
object o = IColumnLink.DBDataSource.GetColumnValue(Page, IColumnLink.
TableName, IColumnLink.ColumnName);
// If the page and the table and column names are not null,
// it means there is already bound data.
//
Put the string representation of the page, table, and
// column names into the textbox.
if( o != null )
FTextBox.Text = Convert.ToString(o);
else
// Otherwise, clear the textbox and bind it and
// its properties to the specified
column.
FTextBox.Text = "";
FTextBox.DataBind();
}
}
322
Pre-Deploy Recommendations
Before you deploy your application, you should disable debugging and rebuild the application to make it smaller and
more efficient:
For a Delphi ASP.NET or C# application, update the application web.config file to disable debugging. For details,
see the link to using the Deployment Manager at the end of this topic.
For a C# application, choose Project
Options and change the Debug/Release option set to the Release
option set and recompile the application.
323
324
The major components of the ASP.NET Web Services architecture include a client application, an ASP.NET Web
Services application, several files such as code files in the development language, .asmx files, and compiled .dll
files. You need a web server to house both ASP.NET Web Services application and the client. Optionally, you might
include a database server for storage and access of ASP.NET Web Services data.
WSDL (Web Services Description Language). WSDL is the language that describes a web service. A web
service can be defined in any number of implementation languages. As a single-purpose utility, each web service
must publish a description of its interface, which allows clients to interact with it. The WSDL document, at a
minimum, describes the required parameters a client must provide and the result a client can expect to receive.
The result description typically consists of the return data type.
UDDI (Universal Description, Discovery, and Integration). UDDI is an industry initiative that provides a
standard repository where businesses can publish web services for use by other companies. The UDDI
repository contains links to, and descriptions of, a variety of web services. You can use the UDDI browser in
the IDE to locate web services, download WSDL documents, and access additional information about web
services and the companies that provide them.
than over a LAN or corporate network, you might use web services. Web services do not require state
maintenance, thus offering potentially improved performance, particularly where a request-response behavior
is not absolutely required. For applications that require strict request-response behavior and high security, you
should consider using an older, more controlled model, such as COM, CORBA, or .NET remoting.
Description
.asmx
When you create an ASP.NET Web Services application, a text file is automatically generated with
the .asmx extension. The required Web Services directive is placed at the top of this file to correlate
between the URL address of the web service and its implementation. Within the .asmx file, you add Web
Services logic to the methods visible by the client application. The .asmx file acts as the base URL for
clients calling the XML web service. This file is compiled into an assembly, along with other files, for
deployment.
code-behind
When you create an ASP.NET Web Service application, a code-behind file is generated with a languagespecific extension. You add your Web Services logic to the public method to process Web Services
requests and responses.
compiled DLL files Web Services DLL files provide dynamic services on the web server.
.wsdl
This file is generated when you click the Add Web Reference feature to add the web service to your client
application. It describes the Web Services interface available to the client.
.map
This file enables the discovery of a web service that is exposed on a given server. It also contains links
to other resources that describe the web service.
327
328
Transport Layer
The Transport layer is the first component in the stack and is responsible for moving XML messages between
applications. The Transport protocol most commonly used is the standard HTTP protocol. Other commonly used
Web protocols are SMTP and FTP.
XML Messaging
The messaging layer in the protocol stack is based on an XML model. XML is widely used in Web Services
applications and is the foundation for all web services. XML is just one of the standards enabling web services to
map between technology domains. You will find many resources on the Web that describe XML messaging. For
more information, refer to the World Wide Web Consortium (W3C) site on Messaging listed in the link list below.
The XML Messaging specification is a broadly-defined umbrella under which a number of more specific protocols
are defined. SOAP is one of the more popular standards, and is one of the most significant standards in
communicating web services over the network. XML provides a means for communicating over the Web using an
XML document that both requests and responds to information between two disparate systems. SOAP allows the
sender and the receiver of XML documents to support a common data transfer protocol for effective networked
communication. You will find many resources on the Web that describe SOAP. For more information, refer to the
W3C site for SOAP listed in the link list below.
WSDL Layer
This layer represents a way of specifying a public interface for a web service. It contains information on available
functions, on data types for XML messaging, binding information about the transport protocol, and the location of
the specific web service.
Any client application that wants to know about a service, what data it expects to receive, whether or not it delivers
any results, and the supported transport, uses WSDL to find that information. When you create a Web Service, it
must be described and advertised to its potential customers before it can be used. WSDL provides a common format
for describing and publishing that web service information. Typically, WSDL is used with SOAP, and the WSDL
specification includes a SOAP binding.
Use Borland's Add Web Reference feature to obtain a WSDL document for your web service. The WSDL document,
or proxy file, is copied to the client and is used to call the server. This proxy file is named References.*, where the
file name extension reflects the language type. For more information about WSDL, refer to the W3C WSDL site listed
in the link list below.
UDDI Layer
This layer represents a way to publish and find web services over the Web. You can think of this layer as the White
and Yellow Pages of your phonebook. The White pages of web services provides general information about a specific
company, for instance, their business name, description, and address. The Yellow Pages includes the classification
of data for the services offered, for instance, industry type and products.
The protocol you use to publish your web services is known as UDDI. The UDDI Business Registry allows anyone
to search existing UDDI data and enables you to register your company and its services. With Delphi 2005, your
data automatically gets published to the registry, or a distributed directory for business and web services.
329
330
331
332
Windows Forms share common .NET Framework with other programming models, like ASP.NET and ADO.NET.
Windows Forms
Delphi 2005 provides an IDE for creating GUI applications in a RAD environment. Developers drag controls, dialogs,
and components onto the form Designer, set properties in Object Inspector, and code the logic to respond to
events.
333
334
335
336
VCL is a set of visual components for building Windows applications in the Delphi language. VCL for .NET is the
same library of components updated for use in the .NET Framework. VCL for .NET and the .NET Framework coexist
337
within Delphi 2005. Both VCL for .NET and .NET provide components and functionality that allow you to build .NET
applications:
VCL for .NET provides the means to create VCL Forms applications, which are Delphi forms that are .NETenabled, and use VCL for .NET components.
VCL for .NET provides VCL non-visual components which have been .NET-enabled to access databases. You
can also access databases through the ADO.NET and BDP.NET providers.
.NET provides the means to build .NET Windows Forms, Web Forms, and Console applications, using .NET
components, with Delphi code-behind.
You can build VCL Forms applications using VCL for .NET components, or Windows Forms applications using .NET
components. You can also build ASP.NET Web Forms applications using either VCL for .NET components or .NET
components.
Visual Components
Delphi 2005 provides a set of visual components, or controls, that you can use to build your applications. In addition
to the common controls, such as buttons, text boxes, radio buttons, and check boxes, you can also find grid controls,
scroll bars, spinners, calendar objects, a full-featured menu designer, and more. These controls are represented
differently in Delphi 2005 than they are in frameworks, such as the .NET Framework.
In an IDE for other languages, such as C# or Java, you will see code-centric representations of forms and other
visual components. These representations include physical definitions, such as size, height, and other properties,
as well as constructors and destructors for the components. In the Code Editor of Delphi 2005 you will not see a
code representation of your VCL for .NET components.
Delphi 2005 is a resource-centric system, which means that the primary code-behind representations are of event
handlers that you fill in with your program logic. Visual components are declared and defined in text files with the
extensions .dfm (Delphi Forms) or .nfm (Delphi 2005 forms). The nfm files are created by Delphi 2005 as you design
your VCL Forms on the Forms Designer, and are listed in the resource list in the Project Manager for the given project.
338
Non-Visual Components
You can use non-visual components to implement functionality that is not necessarily exposed visually. For example,
you can access data by using non-visual components like the BDP.NET components, which provide database
connectivity and DataSet access. Even though these components do not have visual runtime behavior, they are
represented by components in the Tool Palette at designtime. VCL for .NET provides a variety of non-visual
components for data access, server functions, and more.
Borland.VCL Namespace
VCL for .NET classes are found under the Borland.Vcl namespace. Database-related classes are in the
Borland.Vcl.DB namespace. Runtime library classes are in the Borland.Vcl.Rtl namespace.
Unit files have been bundled in corresponding Borland.Vcl namespaces. In some cases, units have been moved.
However, namespaces are identified in a way that will assist you in finding the functionality you want.
Source files for all of the Delphi 2005 objects are available in the c:\Program Files\Borland\BDS\3.0\Source
subdirectory.
339
340
341
ADO.NET Overview
ADO.NET is the .NET programming environment for building database applications based on native database
formats or XML data. ADO.NET is designed as a back-end data store for all .NET programming models, including
Web Forms, Web Services, and Windows Forms. Use ADO.NET to manage data in the .NET Framework.
Borland provides tools to simplify rapid ADO.NET development using Borland Data Providers for .NET (BDP.NET).
If you are familiar with rapid application development (RAD) and object oriented programming (OOP) using
properties, methods, and events, you will find the ADO.NET model for building applications familiar. If you are a
traditional database programmer, ADO.NET provides familiar concepts, such as tables, rows, and columns with
relational navigation. XML developers will appreciate navigating the same data with nodes, parents, siblings, and
children.
This topic discusses the major components of the ADO.NET architecture, how ADO.NET integrates with other
programming models in the .NET Framework, and key Delphi 2005 functionality to support ADO.NET.
This topic introduces:
ADO.NET Architecture
ADO.NET User Interfaces
BDP.NET Namespace
ADO.NET Architecture
The two major components of the ADO.NET architecture are the Data Provider and the DataSet. The data source
represents the physical database or XML file, the Data Provider makes connections and passes commands, and
the DataSet represents one or more data sources in memory. For more information about the general ADO.NET
model, see the Microsoft .NET Framework SDK documentation.
Data Source
The data source is the physical database, either local or remote, or an XML file. In traditional database programming,
the developer typically works with the data source directly, often requiring complex, proprietary interfaces. With ADO.
NET, the database developer works with a set of components to access the data source, to expose data, and to
pass commands.
342
Data Providers
Data Provider components connect to the physical databases or XML files, hiding implementation details. Providers
can connect to one or more data sources, pass commands, and expose data to the DataSet.
The .NET Framework includes providers for MS SQL, OLE DB, and Oracle. In addition to supporting the .NET
providers, this product includes BDP.NET. BDP.NET connects to a number of industry standard databases, providing
a consistent programming environment. For more information, see the Borland Data Providers for Microsoft .NET
topic.
The TADONETConnector component provides access to .NET DataSets either directly or through BDP.NET.
TADONETConnector is the base class for Delphi 2005 datasets that access their data using ADO.NET.
TADONETConnector descendants include TCustomADONETConnector. TADONETConnector is a descendent of
TDataSet.
DataSet
The DataSet object represents in-memory tables and relations from one or more data sources. The DataSet provides
a temporary work area or virtual scratch pad for manipulating data. ADO.NET applications manipulate tables in
memory, not within the physical database. The DataSet provides additional flexibility over direct connections to
physical databases. Much like a typical cursor object supported by many database systems, the DataSet can contain
multiple DataTables, which are representations of tables or views from any number of data sources. The DataSet
works in an asynchronous, non-connected mode, passing update commands through the Provider to the data source
at a later time.
Delphi 2005 provides two kinds of DataSets for your use: standard DataSets and typed DataSets. A standard DataSet
is the default DataSet that you get when you define a DataSet object implicitly. This type of DataSet is constructed
based on the layout of the columns in your data source, as they are returned at runtime based on your Select
statement.
Typed DataSets provide more control over the layout of the data you retrieve from a data source. A typed DataSet
derives from a DataSet class. The typed DataSet lets you access tables and columns by name rather than collection
methods. The typed DataSet feature provides better readability, improved code completion capabilities, and data
type enforcement unavailable with standard DataSets. The compiler checks for type mismatches of typed DataSet
elements at compile time rather than runtime. When you create a typed dataset, you will see that some new objects
are created for you and are accessible through the Project Manager. You will notice two files named after your
dataset. One file is an XML .xsd file and the other is a code file in the language you are using. All of the data about
your dataset, including the table and column data from the database connection, is stored in the .xsd file. The program
code file is created based on the XML in the .xsd file. If you want to change the structure of the typed dataset, you
can change items in the .xsd file. When you recompile, the program code file is regenerated based on the modified
XML.
For more information about DataSets, see the Microsoft .NET Framework SDK documentation.
Web Forms
Web Forms in ASP.NET provide a convenient interface for accessing databases over the web. ASP.NET uses ADO.
NET to handle data access functions.
.NET and BDP.NET connection components ease integration between Web Forms and ADO.NET. DB Web Controls
support both ADO.NET and BDP.NET components, accelerating web application development.
343
Windows Forms
As an alternative to Web Forms, traditional, native-OS clients can function as a front end to ADO.NET databases.
In Delphi 2005 you can provide two types of Windows Forms: a TWinForm object, which is a descendant of TForm
and acts as the native .NET Windows Form, and a VCL.NET form.
BDP.NET Namespace
BDP.NET classes are found under the Borland.Data namespace.
BDP.NET Namespace
Namespace
Description
Borland.Data.Common Contains objects common to all Borland Data Providers, including Error and Exceptions classes, data
type enumerations, provider options, and Interfaces for building your own Command, Connection,
and Cursor classes.
Borland.Data.Provider
Contains key BDP.NET classes like BdpCommand, BdpConnection, BdpDataAdapter, and others
that provide the means to interact with external data sources, such as Oracle, DB2, Interbase, and
MS SQL Server databases.
Borland.Data.Schema
Contains Interfaces for building your own database schema manipulation classes, as well as a
number of types and enumerators that define metadata.
344
BDP.NET provides a high performance architecture for accessing data sources without a COM Interop layer.
The architecture exposes a set of interfaces for third-party integration. You can implement these interfaces for your
own database to provide designtime, tools, and runtime data access integration into the Borland IDE. BDP.NETmanaged components communicate with these interfaces to accomplish all basic data access functionality. These
interfaces were implemented to wrap database-specific native client libraries by way of Platform Invoke (P/Invoke)
services. Depending on the availability of managed database clients, you can implement a fully-managed provider
underneath BDP.NET.
The database-specific implementation is wrapped into an assembly and the full name of the assembly is passed to
the BdpConnection component as part of the connection string. Depending on the Assembly entry in the
ConnectionString property, BDP.NET dynamically loads the database-specific provider and consumes the
345
implementation for ISQLConnection, ISQLCommand, and ISQLCursor. This allows you to switch applications from
one database to another just by changing the ConnectionString property to point to a different provider.
BDP.NET Advantages
BDP.NET provides a number of advantages:
Unified programming model applicable to multiple database platforms
High performance data-access architecture
Open architecture, which supports additional databases easily
Portable code to write once and connect to any supported databases
Consistent data type mapping across databases where applicable
Logical data types mapped to .NET native types
No need for a COM Interop layer, unlike OLE DB
Lets you view live data as you design your application
Extends ADO.NET to provide interfaces for metadata services, schema creation, and data migration
Rich set of component designers and tools to speed database application development
Delphi 2005 extends .NET support to additional database platforms, providing a consistent connection architecture
and data type mapping.
346
Namespace
InterBase
Borland.Data.Interbase
Oracle
Borland.Data.Oracle
IBM DB2
Borland.Data.Db2
Borland.Data.Msacc
Sybase
Borland.Data.Sybase
The BDP.NET components, metadata access, and designers are defined under the following namespaces:
Borland.Data.Provider
Borland.Data.Common
Borland.Data.Schema
Borland.Data.Design
BDP.NET Interfaces
You can extend BDP.NET to support other DBMSs by implementing a subset of the .NET Provider interface. BDP.
NET generalizes much of the functionality required to implement data providers. While the .NET Framework gives
you the capabilities to create individual data providers for each data source, Borland has simplified the task by offering
a generalized set of capabilities. Instead of building separate providers, along with corresponding DataAdapters,
DataReaders, Connection objects, and other required objects, you can implement a set of BDP.NET interfaces to
build your own data source plug-ins to the Borland Data Provider.
Building plug-ins is a much easier task than building a completely new data provider. You build an assembly that
contains the namespace for your provider, as well as classes that encapsulate provider-specific functionality. Much
of the functionality you need to connect to, execute commands against, and retrieve data from your data source has
already been defined in the Borland Data Provider interfaces.
347
Data Types
The .NET Framework includes a wide range of logical data types. BDP.NET inherits logical data types, providing
built-in mappings to supported databases. BDP.NET supports logical data type mappings for DB2, InterBase, MS
SQL, MSDE, and Oracle.
DB2
BDP.NET supports the following DB2 type mappings.
DB2 Type
CHAR
String
stFixed
String
VARCHAR
String
NA
String
SMALLINT
Int16
NA
Int16
BIGINT
Int64
NA
Int64
INTEGER
Int32
NA
Int32
DOUBLE
Double
NA
Double
FLOAT
Float
NA
Single
REAL
Float
NA
Single
DATE
Date
NA
DateTime
TIME
Time
NA
DateTime
TIMESTAMP Datetime NA
DateTime
NUMERIC
Decimal
NA
Decimal
DECIMAL
Decimal
NA
Decimal
BLOB
Blob
stBinary
Byte[]
CLOB
Blob
stMemo
Char[]
348
InterBase
BDP.NET supports the following InterBase type mappings.
InterBase Type
CHAR
String
stFixed
String
VARCHAR
String
NA
String
SMALLINT
Int16
NA
Int16
INTEGER
Int32
NA
Int32
FLOAT
Float
NA
Single
DOUBLE
Double
NA
Double
stBinary
Byte[]
stMemo
Char[]
TIMESTAMP
Datetime NA
DateTime
BIGINT
Int64
NA
Int64
INT
Int32
NA
Int32
SMALLINT
Int16
NA
Int16
TINYINT
Int16
NA
Int16
BIT
Boolean
NA
Boolean
DECIMAL
Decimal
NA
Decimal
NUMERIC
Decimal
NA
Decimal
MONEY
Decimal
NA
Decimal
SMALLMONEY
Decimal
NA
Decimal
FLOAT
Double
NA
Double
REAL
Float
NA
Single
DATETIME
DateTime NA
DateTime
SMALLDATETIME
DateTime NA
DateTime
CHAR
String
stFixed
String
VARCHAR
String
NA
String
TEXT
Blob
stMemo
Char[]
BINARY
VarBytes NA
Byte[]
VARBINARY
VarBytes NA
Byte[]
IMAGE
Blob
Byte[]
TIMESTAMP
VarBytes NA
stBinary
Byte[]
349
UNIQUEIDENTIFIER Guid
NA
Guid
Oracle
BDP.NET supports the following Oracle type mappings.
Oracle Type
CHAR
String
stFixed
String
NCHAR
String
stFixed
String
VARCHAR
String
NA
String
NVARCHAR
String
NA
String
VARCHAR2
String
NA
String
NVARCHAR2 String
NA
String
NUMBER
Decimal
NA
Decimal
DATE
Date
NA
DateTime
BLOB
Blob
stHBinary
Byte[]
CLOB
Blob
stHMemo
Char[]
LONG
Blob
stMemo
Char[]
LONG RAW
Blob
stBinary
Byte[]
BFILE
Blob
stBFile
Char[]
ROWID
String
NA
String
Sybase
BDP.NET supports the following Sybase type mappings.
Sybase Type
CHAR
String
stFixed
String
VARCHAR
String
NA
String
INT
Int32
NA
Int32
SMALLINT
Int16
NA
Int16
TINYINT
Int16
NA
Int16
NA
Single
FLOAT
Float
NA
Single
REAL
Float
NA
Single
NUMERIC
Decimal
NA
Decimal
DECIMAL
Decimal
NA
Decimal
SMALLMONEY
Decimal
NA
Decimal
MONEY
Decimal
NA
Decimal
350
SMALLDATETIME
DateTime NA
DateTime
DATETIME
DateTime NA
DateTime
IMAGE
Blob
stBinary
Byte[]
TEXT
Blob
stMemo
Char[]
BIT
Boolean
NA
Boolean
TIMESTAMP
VarBytes NA
Byte[]
BINARY
Bytes
NA
Byte[]
VARBINARY
VarBytes NA
Byte[]
SYSNAME
String
String
NA
351
352
The Data Explorer to browse database server-specific schema objects and use drag-and-drop techniques to
automatically populate data from a data source to your Delphi for .NET project
Connections Editor
The Connections Editor manages connection strings and database-specific connection options. Using the
Connections Editor you can add, remove, delete, rename, and test database connections. Changes to the
connection information are saved into the BdpConnections.xml file, where they are accessed whenever you need
to create a new connection object. Once you have chosen a particular connection, the Connections Editor
generates the connection string and any connection options, then assigns them to the ConnectionString and
ConnectionOptions properties, respectively.
Display the Connections Editor dialog box by dragging the BdpConnection component from the Tool Palette onto
the form, and then clicking the component designer verb at the bottom of the Object Inspector.
353
are all set for the BdpCommand, the stored procedure can be executed at runtime by making a single call to
ExecuteReader or ExecuteNonQuery.
Generate DataSets
The Generate Dataset designer is used to build a DataSet. Using this tool results in strong typing, cleaner code,
and the ability to use code completion. A DataSet is first derived from the base DataSet class and then uses
information in an XML Schema file (an .xsd file) to generate a new class. Information from the schema (tables,
columns, and so on) is generated and compiled into this new dataset class as a set of first-class objects and
properties. Display this dialog box by dragging a BdpDataAdapter component from the Tool Palette onto the form,
and clicking the component designer verb at the bottom of the Object Inspector. If this component is not displayed,
choose Component
Installed .NET Components to add it to the Tool Palette.
Data Explorer
The Data Explorer is a hierarchical database browser and editing tool. The Data Explorer is integrated into the IDE
and can also be run as a standalone executable. To access the Data Explorer within the IDE, choose View Data
Explorer. Use the context menus in the Data Explorer to perform the following tasks:
Manage database connectionsadd a new connection, modify, delete, or rename your existing connections
Browse database structure and dataexpand and open provider nodes to browse database server-specific
schema objects including tables, views, stored procedure definitions, and indexes
Add and modify tablesspecify the data structure for a new table, or add or remove columns, and alter column
information for an existing table
View and test stored procedure parametersspecify values for Input or InputOutput parameters and execute
the selected stored procedure
Migrate datamigrate table schema and data of one or more tables from one provider to another
Drag-and-drop schema objects onto forms to simplify application developmentdrag tables or stored
procedures onto your application form for the .NET Framework to add connection components and automatically
generate connection strings
The Data Explorer provides connectivity to several industry-standard databases, and can be extended to connect
to other popular databases. The Data Explorer uses the ISQLDataSource interface to get a list of available providers,
database connections, and schema objects that are supported by different providers. The list of available providers
is persisted in the BdpDataSources.xml file, and the available connections are persisted in the
BdpConnections.xml file. Once you have chosen a provider the ISQLMetadata interface is used to retrieve
354
metadata and display a read-only tree view of database objects. The current implementation provides a list of tables,
views, and stored procedures for all BDP.NET-supported databases.
The Data Explorer lets you create new tables, alter or drop existing tables, migrate data from multiple tables from
one provider to another, and copy and paste individual tables across BDP-supported databases. For all these
operations, the Data Explorer calls into the ISQLSchemaCreate implementation of the provider.
Additionally, the Data Explorer can be used to drag data from a data source to any Delphi 2005 project for the .NET
framework. Dragging a table onto a form adds BdpConnection and BdpDataAdapter components to your application
and automatically configures the BdpDataAdapter for the given table. Dragging a stored procedure onto a form adds
BdpConnection and BdpCommand components to your application, and sets the CommandType property of the
BdpCommand object to StoredProcedure.
355
356
357
Function
Represents any data available through dbExpress, or to send commands to a database accessed through
dbExpress
TSQLQuery
A query-type dataset that encapsulates an SQL statement and enables applications to access the resulting
records, if any
TSQLTable
A table-type dataset that represents all of the rows and columns of a single database table
TSQLStoredProc A stored procedure-type dataset that executes a stored procedure defined on a database server
TSQLMonitor
Intercepts messages that pass between an SQL connection component and a database server and saves
them in a string list
TSimpleDataSet
A client dataset that uses an internal TSQLDataSet and TDataSetProvider for fetching data and applying
updates
358
Function
Represents any data available through dbGo, or to send commands to a database accessed through dbGo
TADOQuery
A query-type dataset that encapsulates an SQL statement and enables applications to access the resulting
records, if any, from an ADO data store
TADOTable
A table-type dataset that represents all of the rows and columns of a single database table
TADOStoredProc A stored procedure-type dataset that executes a stored procedure defined on a database server
TADOCommand
Represents the ADO Command object, which is used for issuing commands against a data store accessed
through an ADO provider
TADODataSet
359
IBX components
The following components are located on the InterBase tab of the component palette.
Icon
Component Name
Description
TIBTable
TIBQuery
TIBStoredProc
TIBDatabase
TIBTransaction
Provides discrete transaction control over a one or more database connections in a database
application.
TIBUpdateSQL
Provides an object for updating read-only datasets when cached updates are enabled.
TIBDataSet
TIBSQL
Provides an object for executing an InterBase SQL statement with minimal overhead.
TIBDatabaseInfo
TIBSQLMonitor
TIBExtract
TIBCustomDataSet The base class for all datasets that represent data fetched using InterBase Express.
Though they are similar to BDE components in name, the IBX components are somewhat different. For each
component with a BDE counterpart, the sections below give a discussion of these differences.
There is no simple migration from BDE to IBX applications. Generally, you must replace BDE components with the
comparable IBX components, and then recompile your applications. However, the speed you gain, along with the
access you get to the powerful InterBase features make migration well worth your time.
IBDatabase
Use a TIBDatabase component to establish connections to databases, which can involve one or more concurrent
transactions. Unlike BDE, IBX has a separate transaction component, which allows you to separate transactions
and database connections.
To set up a database connection:
360
Warning: Tip: You can store the username and password in the IBDatabase component's Params property by
setting the LoginPrompt property to false after logging in. For example, after logging in as the system
administrator and setting the LoginPrompt property to false, you may see the following when editing the
Params property:
user_name=sysdba
password=masterkey
IBTransaction
Unlike the Borland Database Engine, IBX controls transactions with a separate component, TIBTransaction. This
powerful feature allows you to separate transactions and database connections, so you can take advantage of the
InterBase two-phase commit functionality (transactions that span multiple connections) and multiple concurrent
transactions using the same connection.
Use an IBTransaction component to handle transaction contexts, which might involve one or more database
connections. In most cases, a simple one database/one transaction model will do.
To set up a transaction:
1 Set up an IBDatabase connection as described above.
2 Drop an IBTransaction component onto the form or data module
3 Set the DefaultDatabase property to the name of your IBDatabase component.
4 Set the Active property to true to start the transaction.
IBQuery
Use an TIBQuery component to execute any InterBase DSQL statement, restrict your result set to only particular
columns and rows, use aggregate functions, and join multiple tables.
IBQuery components provide a read-only dataset, and adapt well to the InterBase client/server environment. To set
up an IBQuery component:
1 Set up an IBDatabase connection as described above.
2 Set up an IBTransaction connection as described above.
361
IBDataSet
Use an TIBDataSet component to execute any InterBase DSQL statement, restrict your result set to only particular
columns and rows, use aggregate functions, and join multiple tables. IBDataSet components are similar to IBQuery
components, except that they support live datasets without the need of an IBUpdateSQL component.
The following is an example that provides a live dataset for the COUNTRY table in employee.gdb:
1 Set up an IBDatabase connection as described above.
2 Specify the associated database and transaction components.
3 Add an IBDataSet component to your form or data module.
4 Enter SQL statements for the following properties: SelectSQL, RefreshSQL, ModifySQL, DeleteSQL, InsertSQL.
SQL Statement
SelectSQL
DeleteSQL
InsertSQL
Note: Note: Parameters and fields passed to functions are case-sensitive in dialect 3. For example,
FieldByName(EmpNo)
362
IBUpdateSQL
Use an TIBUpdateSQL component to update read-only datasets. You can update IBQuery output with an
IBUpdateSQL component:
1 Set up an IBQuery component as described above.
2 Add an IBUpdateSQL component to your form or data module.
3 Enter SQL statements for the following properties: DeleteSQL, InsertSQL, ModifySQL, and RefreshSQL.
4 Set the IBQuery component's UpdateObject property to the name of the IBUpdateSQL component.
5 Set the IBQuery component's Active property to true.
IBSQLMonitor
Use an TIBSQLMonitor component to develop diagnostic tools to monitor the communications between your
application and the InterBase server. When the TraceFlags properties of an IBDatabase component are turned on,
active IBSQLMonitor components can keep track of the connection's activity and send the output to a file or control.
A good example would be to create a separate application that has an IBSQLMonitor component and a Memo control.
Run this secondary application, and on the primary application, activate the TraceFlags of the IBDatabase
component. Interact with the primary application, and watch the second's memo control fill with data.
IBDatabaseInfo
Use an TIBDatabaseInfo component to retrieve information about a particular database, such as the sweep interval,
ODS version, and the user names of those currently attached to this database.
For example, to set up an IBDatabaseInfo component that displays the users currently connected to the database:
1 Set up an IBDatabase connection as described above.
2 Put an IBDatabaseInfo component on the form or data module and set its Database property to the name of the
database.
3 Put a Memo component on the form.
4 Put a Timer component on the form and set its interval.
5 Double click on the Timer's OnTimer event field and enter code similar to the following:
Memo1.Text := IBDatabaseInfo.UserNames.Text;
//
Delphi example
IBEvents
Use an IBEvents component to register interest in, and asynchronously handle, events posted by an InterBase
server.
To set up an IBEvents component:
1 Set up an IBDatabase connection as described above.
2 Put an IBEvents component on the form or data module and set its Database property to the name of the
database.
3 Enter events in the Events property string list editor, for example: IBEvents.Events.Add
363
If you have InterBase 6 installed, you can use the InterBase 6 Administration components, which allow you to use
access the powerful InterBase Services API calls.
The components are located on the InterBase Admin tab of the IDE and include:
TIBConfigService
TIBBackupService
TIBRestoreService
TIBValidationService
TIBStatisticalService
TIBLogService
TIBSecurityService
TIBLicensingService
TIBServerProperties
TIBInstall
TIBUnInstall
365
Data Provider
Location
Borland.Data.Common.dll All
GAC
Borland.Data.Provider.dll
All
GAC
Borland.Data.DB2.dll
DB2
GAC
Borland.Data.Interbase.dll Interbase
GAC
Borland.Data.Mssql.dll
MS SQL/MSDE GAC
Borland.Data.Oracle.dll
Oracle
GAC
Borland.Data.Msacc.dll
MS Access
GAC
Borland.Data.Sybase.dll
Sybase
GAC
Note: If you are deploying a distributed database application that uses the BDP.NET Remoting components, such
as DataHub, DataSync, RemoteConnection, and RemoteServer, you must install Borland.Data.DataSync.dll
to the GAC.
Copy unmanaged database driver DLLs to the following location:
DLLs
Data Provider
Location
bdpint20.dll
Interbase
search path
bdpdb220.dll DB2
search path
Oracle
search path
bdpmsa20.dll MS Access
search path
bdpsyb20.dll
search path
Sybase
Data Provider
Location
Borland.VclDbExpress.dll All
GAC
Borland.VclDbCtrls.dll
All
GAC
Borland.VclDbxCds.dll
366
Note: For database applications using Informix or MSSQL, you cannot deploy a standalone executable. Instead,
deploy an executable file with the driver DLL (listed in the following table).
If you are not deploying a stand-alone executable, you can deploy associated dbExpress.NET drivers and DataSnap
DLLs with your executable. Copy unmanaged database driver DLLs to the following location:
DLLs
Data Provider
Location
dbexpinf.dll
Informix
search path
dbexpint.dll
InterBase
search path
dbexpora.dll
Oracle
search path
dbexpdb2.dll
DB2
search path
dbexpmss.dll
MS SQL
search path
search path
367
368
Seamless interoperability is achieved through stand-in objects called Runtime Callable Wrappers (RCW). The RCW
is a layer of communication between your managed application, and the actual unmanaged COM server.
Metadata
In the context of .NET and COM, metadata is a term used to mean type information. In COM, type information can
be stored in a variety of ways. For instance, a C++ header file is a language-specific container for type information.
A type library is also a container for type information, but being a binary format, type libraries are language neutral.
Unlike the COM development model where type libraries are not required, language neutral metadata is mandatory
for all .NET assemblies. Every assembly is self-describing; its metadata contains complete type information,
including private types and private class members.
Custom Attributes
Developers often tag program entities (such as classes and their methods) with descriptive attributes such as static,
private, protected, and public. In the .NET Framework, you can tag any entity, including classes, properties, methods,
and even assemblies themselves, with an attribute of your own design and meaning. Custom attributes are
369
expressed in source code, and are processed by the compiler. At the end of the build process, custom attributes are
emitted into the output assembly just like all metadata.
Reflection
A unique characteristic of the .NET Framework is that type information is not lost during the compilation process.
Instead, all metadata, including custom attributes, is emitted by the compiler into the final output assembly. Metadata
is available at runtime, through .NET Reflection services. The .NET Framework SDK provides a reflection tool called
ildasm that allows the developer to open any .NET assembly, and inspect the types declared therein. Such reflection
tools often allow programmers to directly view the IL code generated by the compiler. The Delphi 2005 IDE contains
its own integrated reflection tool, in the form of the meta data explorer tool that appears when you open a .NET
assembly.
Strong Names
The concept of a strong name is similar to that of the 128-bit Globally Unique Identifier (GUID) in COM programming.
A GUID is a name that is guaranteed to be globally unique. Every .NET assembly has a basic name, which consists
of a text string, a version number, and optional culture information. For shared assemblies installed into the GAC,
the basic name alone is not enough to guarantee the assembly is uniquely identified. To generate a globally unique
name, an encryption key with public and private components is used to generate a digital signature. The signature
is then applied to the assembly using the .NET Framework SDK Assembly Linker (al.exe), or by using assembly
attributes in source code.
370
When you use a COM object in a managed application, the Common Language Runtime (CLR) creates an RCW,
which is the interface between managed and unmanaged code. The complexities of data marshaling and reference
counting are handled by the RCW. In fact the RCW does not even expose the IUnknown and IDispatch interfaces.
When you use a .NET component in an unmanaged application, the system creates a stand-in called a COM Callable
Wrapper (CCW).
371
properties of the Windows.Forms.Control class, while properties exposed by the ActiveX Control will be grouped
under the Misc category.
373
BOOL SystemParametersInfo(
UINT uiAction, // system parameter to retrieve or set
UINT uiParam,
// depends on action to be taken
PVOID pvParam, // depends on action to be taken
UINT fWinIni
// user profile update option
);
Depending on the value of uiAction, pvParam can be one of dozens of different structures or simple data types.
Since there is no way to represent this with one single managed declaration, multiple overloaded versions of the
function must be declared (see Borland.Vcl.Windows.pas), where each overload covers one specific case. The
parameter pvParam can also be given the generic declaration IntPtr. This places the burden of marshaling on the
caller, rather than the built in marshaler. Note that the data types used in a managed declaration of an unmanaged
function must be types that the default marshaler supports. Otherwise, the caller must declare the parameter as
IntPtr and be responsible for marshaling the data.
Data Types
Most data types do not need to be changed, except for pointer and string types. The following table shows commonly
used data types, and how to translate them for managed code:
Unmanaged Data Type
Output Parameter
String
StringBuilder
Untyped parameter/buffer
TBytes
TBytes
const TRect
var TRect
374
const Byte
var Byte
array of Integer
array of Integer
IntPtr
IntPtr can also represent all pointer and string types, in which case you need to manually marshal data using the
Marshal class. When working with functions that receive a text buffer, the StringBuilder class provides the easiest
solution. The following example shows how to use a StringBuilder to receive a text buffer:
The StringBuilder class is automatically marshaled into an unmanaged buffer and back. In some cases it may not
be practical, or possible, to use a StringBuilder. The following examples show how to marshal data to send and
retrieve strings using SendMessage:
An unmanaged buffer is allocated, and the string copied into it by calling StringToHGlobalAuto. The buffer must
be freed once its no longer needed. To marshal a pointer to a structure, use the Marshal. StructureToPtr method
to copy the contents of the structure into the unmanaged memory buffer.
The following example shows how to receive a text buffer and marshal the data into a string:
375
It is important to ensure the buffer is large enough, and by using the SystemDefaultCharSize method, the buffer is
guaranteed to hold BufSize characters on any system.
Advanced Techniques
When working with unmanaged APIs, it is common to pass parameters as either a pointer to something, or NULL.
Since the managed API translations dont use pointer types, it might be necessary to create an additional overloaded
version of the function with the parameter that can be NULL declared as IntPtr.
Special Cases
There are cases where a StringBuilder and even the Marshal class will be unable to correctly handle the data that
needs to be passed to an unmanaged function. An example of such a case is when the string you need to pass, or
receive, contains multiple strings separated by NULL characters. Since the default marshaler will consider the
first NULL to be the end of the string, the data will be truncated (this also applies to the StringToHGlobalXXX and
PtrToStringXXX methods). In this situation TBytes can be used (using the PlatformStringOf and PlatformBytesOf
functions in Borland.Delphi.System to convert the byte array to/from a string). Note that these utility functions
do not add or remove terminating NULL characters.
When working with COM interfaces, the UnmanagedType enumeration (used by the MarshalAsAttribute class) has
a special value, LPStruct. This is only valid in combination with a System.Guid class, causing the marshaler to
convert the parameter into a Win32 GUID structure. The function CoCreateInstance that is declared in Delphi 7
as:
Structures
The biggest difference between calling unmanaged functions and passing structures to unmanaged functions is that
the default marshaler has some major restrictions when working with structures. The most important are that dynamic
arrays, arrays of structures and the StringBuilder class cannot be used in structures. For these cases IntPtr is required
(although in some cases string paired with various marshaling attributes can be used for strings).
Data Types
The following table shows commonly used data types, and how to translate them for managed code:
Unmanaged Data Type
Output Parameter
String
IntPtr
String
String
IntPtr
IntPtr
376
IntPtr or flatten
IntPtr or flatten
IntPtr
IntPtr
IntPtr
IntPtr
IntPtr
IntPtr
IntPtr
IntPtr
When working with arrays and strings in structures, the MarshalAs attribute is used to describe additional information
to the default marshaler about the data type. A record declared in Delphi 7, for example:
type
TMyRecord = record
IntBuffer: array[0..31] of Integer;
CharBuffer: array[0..127] of Char;
lpszInput: LPTSTR;
lpszOutput: LPTSTR;
end;
type
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
TMyRecord = record
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
IntBuffer: array[0..31] of Integer;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
CharBuffer: string;
[MarshalAs(UnmanagedType.LPTStr)]
lpszInput: string;
lpszOutput: IntPtr;
end;
The above declarations assume that the strings contain platform dependant TChars (as commonly used by the
Win32 API). It is important to note that in order to receive text in lpszOutput, the Marshal. AllocHGlobal method
needs to be called before passing the structure to an API function.
A structure can contain structures, but not pointers to structures. For such cases an IntPtr must be declared, and
the Marshal. StructureToPtr method used to move data from the managed structure into unmanaged memory. Note
that StructureToPtr does not allocate the memory needed (this must be done separately). Be sure to use Marshal.
SizeOf to determine the amount of memory required, as Delphis SizeOf is not aware of the MarshalAs attribute (in
the example above, CharBuffer would be 4 bytes using Delphis SizeOf when it in fact should occupies 128 bytes
on a single byte system). The following examples show how to send messages that pass pointers to a structure:
377
end;
end;
procedure GetRect(Handle: HWND; var Rect: TRect);
var
Buffer: IntPtr;
begin
Buffer := Marshal.AllocHGlobal(Marshal.SizeOf(TypeOf(TRect)));
try
SendMessage(Handle, EM_GETRECT, 0, Buffer);
Rect := TRect(Marshal.PtrToStructure(Buffer, TypeOf(TRect)));
finally
Marshal.DestroyStructure(Buffer, TypeOf(TRect));
end;
end;
It is important to call DestroyStructure rather than FreeHGlobal if the structure contains fields where the marshaling
layer needs to free additional buffers (see the documentation for DestroyStructure for more details).
Advanced topics
Working with unmanaged APIs it is not uncommon to need to convert a byte array into a structure (or retrieve one
or more fields from a structure held in a byte array), or vice versa. Although the Marshal class contains a method to
retrieve the offset of a given field, it is extremely slow and should be avoided in most situations. Informal performance
tests show that for a structure with eight or nine numeric fields, it is much faster to allocate a block of unmanaged
memory, copy the byte array to the unmanaged memory and call PtrToStructure than finding the position of just one
field using Marshal. OffsetOf and converting the data using the BitConverter class. Borland.Vcl.WinUtils
contains helper functions to perform conversions between byte arrays and structures (see StructureToBytes and
BytesToStructure).
Special cases
There are cases where custom processing is required, such as sending a message with a pointer to an array of
integers. For situations like this, the Marshal class provides methods to copy data directly to the unmanaged buffer,
at specified offsets (so you can construct an array of a custom data type after allocating a buffer). The following
example shows how to send a message where the LParam is a pointer to an array of Integer:
Callback Functions
When passing a function pointer for a managed function to an unmanaged API, a reference must be maintained to
the delegate or it will be garbage collected. If you pass a pointer to your managed function directly, a temporary
378
delegate will be created, and as soon as it goes out of scope (at the end of MyFunction in the example below), it
is subject to garbage collection. Consider the following Delphi 7 code:
In order for this to work in a managed environment, the code needs to be changed to the following:
const
MyCallbackDelegate: TFNMyCallback = @MyCallback;
function MyFunction: Integer;
begin
...
RegisterCallback(MyCallbackDelegate);
...
end;
This will ensure that the callback can be called as long as MyCallbackDelegate is in scope.
Data types
The same rules apply for callbacks as any other unmanaged API function.
Special cases
Any parameters used in an asynchronous process must be declared as IntPtr. The marshaler will free any memory
it has allocated for unmanaged types when it returns from the function call. When using an IntPtr, it is your
responsibility to free any memory that has been allocated.
Data types
The following table shows
Unmanaged Data Types
Receive Data
GCHandle
The GCHandle provides the primary means of passing an object references to unmanaged code, and ensuring
garbage collection does not happen. A GCHandle needs to be allocated, and later freed when no longer needed.
There are several types of GCHandle, GCHandleType.Normal being the most useful when an unmanaged client
holds the only reference. In order pass a GCHandle to an API function once it is allocated, type cast it to IntPtr (and
379
optionally onwards to LongInt, depending on the unmanaged declaration). The IntPtr can later be cast back to a
GCHandle. Note that IsAllocated must be called before accessing the Target property, as shown below:
procedure MyProcedure;
var
Ptr: IntPtr;
Handle: GCHandle;
begin
...
if Ptr <> nil then
begin
Handle := GCHandle(Ptr);
if Handle.IsAllocated then
DoSomething(Handle.Target);
end;
...
end;
Advanced techniques
The use of a GCHandle, although relatively easy, is fairly expensive in terms of performance. It also has the possibility
of resource leaks if handles arent freed correctly. If object references are maintained in the managed code, it is
possible to pass a unique index, for example the hash code returned by the GetHashCode method, to the unmanaged
API instead of an object reference. A hash table can be maintained on the managed side to facilitate retrieving an
object instance from a hash value if needed. An example of using this technique can be found in the TTreeNodes
class (in Borland.Vcl.ComCtrls).
IAutoComplete = interface(IUnknown)
['{00bb2762-6a77-11d0-a535-00c04fd7d062}']
function Init(hwndEdit: HWND; punkACL: IUnknown;
pwszRegKeyPath: LPCWSTR; pwszQuickComplete: LPCWSTR): HRESULT; stdcall;
function Enable(fEnable: BOOL): HRESULT; stdcall;
end;
380
Note the custom attributes used to describe the GUID and type of interface. It is also essential to use the
ComImportAttribute class. There are some important notes when importing COM interfaces. You do not need to
implement the IUnknown/IDispatch methods, and inheritance is not supported.
Data types
The same rules as unmanaged functions apply for most data types, with the following additions:
Unmanaged Data Type Managed Data Type
Supply Data
Receive Data
GUID
System.Guid
System.Guid
IUnknown
TObject
TObject
IDispatch
TObject
TObject
Interface
TObject
TObject
Variant
TObject
TObject
array of <type>
array of <type>
BSTR
String
String
Using the MarshalAsAttribute custom attribute is required for some of the above uses of TObject, specifying the
exact unmanaged type (such as UnmanagedType.IUnknown, UnmanagedType.IDispatch or
UnmanagedType.Interface). This is also true for certain array types. An example of explicitly specifying the
unmanaged type is the Next method of the IEnumString interface. The Win32 API declares Next as follows:
HRESULT Next(
ULONG celt,
LPOLESTR * rgelt,
ULONG * pceltFetched
);
Advanced techniques
When working with safearrays, the marshal layer automatically converts (for example) an array of bytes into the
corresponding safearray type. The marshal layer is very sensitive to type mismatches when converting safearrays.
If the type of the safearray does not exactly match the type of the managed array, an exception is thrown. Some of
the Win32 safearray APIs do not set the type of the safearray correctly when the array is created, which will lead to
a type mismatch in the marshal layer when used from .NET. The solutions are to either ensure that the safearray is
created correctly, or to bypass the marshal layers automatic conversion. The latter choice may be risky (but could
be the only alternative if you dont have the ability to change the COM server that is providing the data). Consider
the following declaration:
381
If the return value is known to always be a safearray (that doesnt describe its type correctly) wrapped in a variant,
we can change the declaration to the following:
type
TSafeByteArrayData = packed record
VType: Word;
Reserved1: Word;
Reserved2: Word;
Reserved3: Word;
VArray: IntPtr; { This is a pointer to the actual SafeArray }
end;
function AS_GetRecords(const ProviderName: WideString; Count: Integer;
out RecsOut: Integer; Options: Integer; const CommandText: WideString;
var Params: OleVariant; var OwnerData: OleVariant): TSafeByteArrayData;
Knowing that an OleVariant is a record, the TSafeByteArrayData record can be extracted from Delphi 7s TVarData
(equivalent to the case where the data type is varArray). The record will provide access to the raw pointer to the
safearray, from which data can be extracted. By using a structure instead of an OleVariant, the marshal layer will
not try to interpret the type of data in the array. You will however be burdened with extracting the data from the actual
safearray.
Special cases
Although it is preferred to use Activator.CreateInstance when creating an instance, it is not fully compatible with
CoCreateInstanceEx. When working with remote servers, CreateInstance will always try to invoke the server
locally, before attempting to invoke the server on the remote machine. Currently the only known work-around is to
use CoCreateInstanceEx.
Since inheritance isnt supported, a descendant interface needs to declare the ancestors methods. Below is the
IAutoComplete2 interface, which extends IAutoComplete.
382
Standard PInvoke
To call an unmanaged function from managed code, you must use a .NET service called Platform Invoke, or PInvoke.
The Platform Invoke service requires you to declare in source code, a prototype for each unmanaged function you
wish to call. You can do this either within an existing .NET class, or you can create an entirely new class to organize
the prototypes. You must also tag each unnamaged prototype declaration with the DllImport attribute.
The DllImport attribute requires you to specify the name of the DLL in which the unmanaged function resides.
Since the unmanaged prototype is tagged with the DllImport attribute at compile-time, dynamic discovery of DLLs
and their exported unmanaged functions is difficult. Furthermore, if the unmanaged function is not actually exported
from the DLL named in the DllImport attribute, a runtime failure will result. To avoid a runtime failure, you would
have to use LoadLibrary to load the exact DLL you require, and then call GetProcAddress to verify the existance
of the unmanaged function. Even so, you would not be able to directly call the function using the pointer returned
from GetProcAddress. Instead you would have to pass the pointer along to a function in another unmanaged DLL.
That function would then use the pointer to make the call.
function AFunction
: Boolean;
function AnotherFunction : Boolean;
To call these functions from managed code, add the Borland.Vcl.Win32 unit to the uses clause and declare an
interface in Delphi:
383
To call the unmanaged functions, first call Supports to load the DLL, and create the interface on the DLL:
var
MyFunctions : IMyFunctions;
begin
if Supports("MyFunctions.dll", IMyFunctions, MyFunctions) then
if MyFunctions.AFunction then
begin
...
end;
end;
end;
Virtual Library Interfaces have the same limitations in terms of compatible native parameter types and their mapping
to .NET types. In addition, all unmanaged functions are expected to use the stdcall calling convention.
384
CORBA is an architectural specification that provides the capability for distributed applications to interoperate without
understanding detailed communication requirements on one end or the other. The most common model of a CORBA
application is shown in the illustration. It is a typical client-server model, with the exception that it uses a middle layer,
known as middleware, or more specifically, an ORB, as a proxy between the client and server. The client, in this
case, sends a request to the server, without knowing where the server is located or how to reach it. The request is
intercepted by the ORB which manages the details of locating the server, locating the specific object on the server
that can answer this particular client request, and of sending the request. The server receives the request as if it
were receiving it directly from a known client. The server responds to the request. The ORB intercepts the response,
decodes it in order to make it intelligible to the client, and sends it back to the client. This illustrates the essential
characteristics of the CORBA model.
Understanding CORBA
The basic concept at the heart of CORBA is simple. As an example, consider a client that needs to communicate
with the objects in a remote server. The client, in this example, has no knowledge of the interface to the server or
385
the location of the server on the network. All the client possesses is the name of object and the methods it needs to
access. The client constructs a message, which it sends to the ORB, a set of services that contains an object
reference to the target object. The ORB sends the message to the server, using the object reference, then waits for
a response. When it receives a response from the server, the ORB decodes the message, reconstructs it into a form
that the client expects, and sends it to the client.
Description
Stubs
Methods or proxies in the ORB that create an interface between IDL-defined operations and a
particular non-object-oriented programming language. Depending on the implementation
language, you might not be required to use stubs.
Dynamic invocation interface An interface that allows the dynamic construction of object invocations rather than having to
write static IDL.
Implementation skeleton
An interface in the ORB to the methods that implement each type of object for a particular
language mapping.
An interface to dynamically handle object invocations. Can be invoked through either client
stubs or through the dynamic invocation interface.
Object adapters
Interfaces to specific kinds of objects. Object adapters provide many services which can include
such things as object reference generation and interpretation, method invocation, interaction
security, object activation and deactivation, mapping of object references to implementations
and more. The Portable Object Adapter (POA) included with VisiBroker is an example of an
object adapter.
ORB interface
An interface that goes directly to the ORB and which is the same for all ORBs.
Interface repository
A service that provides persistent objects that represent the IDL information in a form available
at runtime.
Implementation repository
A database that contains information allowing ORBs to locate and activate object
implementations.
Writing IDL
IDL is a descriptive language you use to describe your CORBA interfaces to remote objects. You use an IDL compiler
to generate a client stub file and a server skeleton file in your implementation language, usually C++, Java, C#, or
another high-level language. The OMG has defined specifications for language mappings to a variety of other
languages, including Ada, COBOL, C, C++, Java, Lisp, Smalltalk, XML and Python. IDL is an extensive language
and isn't covered in this documentation. Refer to the OMG IDL specification for details on IDL syntax and usage.
You can write your IDL code in but you need an IDL compiler. If you use the Borland Janeva product, you can use
one of the IDL compilers included in that product. The IDL compiler reads the IDL file and generates a class or other
addressable object that includes stubs. The stub passes the request to the object implementation, on the server for
example, and, on receiving a response, decodes the response and returns the results to the calling application, or
client.
CORBA applications as unmanaged applications in the .NET framework. As a consequence, you might experience
a performance impact on these programs, compared to the performance you currently experience.
Fortunately, if you use an ORB that supports the .NET framework, such as Janeva, you can take advantage of the
capabilities of the framework, without suffering the performance impact of running your CORBA application as an
unmanaged application.
Portable interceptors: provides the ability to augment IIOP packets with user- or system-level data.
Note: The POA is not supported in the initial release of Janeva, which can impact support for CORBA servers.
Janeva Requirements
Janeva can be installed directly from the installation program. You can also purchase and install Janeva separately.
You can download the product from the Borland website.
You must install both the Microsoft .NET Framework 1.1 and the Microsoft Visual J# Redistributable Package 1.1.
During the installation, you are prompted to install the Microsoft .NET Framework 1.1, if it is not already installed on
your system. You might, however, need to download and install the Microsoft Visual J# Redistributable Package 1.1
from the Microsoft website.
388
389
390
391
392
Procedures
393
ASP.NET
394
New
Other.
categories.
The New ASP.NET Application dialog box appears.
3 In the Name field, enter a name for your project.
4 In the Location field, enter a path for your project.
Tip:
5 If necessary, click the View Server Options button to change your Web server settings.
Tip:
The default Server Options will usually be sufficient, so this step is optional.
6 Click OK.
The Button control appears on the Designer. Make sure the control is selected.
3 In Object Inspector, set the Text property to Dead or Alive?.
4 From the Web Controls category of the Tool Palette, place a TextBox component onto the Designer above the
Button.
This is where you type your query to the Web Service.
5 Place a Label component below the Button.
This is where the results of the web service query are displayed.
Use the UDDI browser to locate the DeadOrAlive Web Service on the internet. This allows you to use the methods
and objects published by the Web Service Definition Language (WSDL).
2 In the Borland UDDI Browser web dialog box, click the XMethods Full link in the list of available UDDI
directories.
A list of various web services published on the XMethods Web site appears.
395
Tip:
You can use Ctrl+F to search within the Borland UDDI Browser.
A WSDL document appears. This XML document describes the interface to the DeadOrAliveWS web service.
5 Click Add Reference to add the WSDL document to the client application.
A Web References folder containing a com.abundanttech.www node is added to the Project directory in the
Project Manager.
code :
procedure TWebForm1.Button1_Click(sender: System.Object; e: System.EventArgs);
var
result: DataSet;
ws: DeadOrAlive;
currentTable: DataTable;
currentRow: DataRow;
currentCol: DataColumn;
begin
//This initializes the web service
ws := DeadOrAlive.Create;
//Send input to the web service
result := ws.getDeadOrAlive(TextBox1.Text);
//parse results and display them
Label1.Text := '';
for currentTable in result.Tables do
begin
Label1.Text := Label1.Text + '<p>' + #13#10;
for currentRow in currentTable.Rows do
begin
for currentCol in currentTable.Columns do
begin
Label1.Text := Label1.Text + currentCol.ColumnName + ': ';
Label1.Text := Label1.Text + (currentRow[currentCol]).ToString;
Label1.Text := Label1.Text + '<br>' + #13#10;
end;
end;
Label1.Text := Label1.Text + '</p>';
end;
end;
When you added the Web Reference to your application, Delphi 2005 used the WSDL to generate a proxy class
representing the "Hello World" web service. The Click event uses methods from the proxy class to access the
396
web service. For Delphi for .NET Web Services, you may need to add the unit name of the proxy class,
abundanttech.deadoralive, to the uses clause of your Web Form unit to prevent errors in your Click event.
4 For a C# Web Services application, implement the Click event in the Code Editor with the following code :
private void button1_Click(object sender, System.EventArgs e)
{
DataSet result;
//This initializes the web service
DeadOrAlive source = new DeadOrAlive();
//Send input to the web service
result = source.getDeadOrAlive(textBox1.Text);
//parse results and display them
label1.Text = "";
foreach (DataTable currentTable in result.Tables) {
label1.Text += "<p>\n";
foreach (DataRow currentRow in currentTable.Rows) {
foreach (DataColumn currentCol in currentTable.Columns) {
label1.Text += currentCol.ColumnName + ": ";
label1.Text += currentRow[currentCol] + "<br>\n";
}
}
label1.Text += "</p>";
}
}
Note:
As you can see by the added application logic code, the DeadOrAliveWS web service returns
query results in the form of a dataset. Web Services can, however, return data in a variety of
formats.
Tip:
If you are using Microsoft IIS, the URL is the path of the .aspx file after Inetpub\wwwroot. For
example, if the path of your Web Application is c:\Inetpub\wwwroot\WebApplication1 and
your .aspx file is named "WebForm1.aspx", the URL would be http://localhost/WebApplication1/
WebForm1.aspx.
4 If necessary, enter your user name and password for your ASP.NET server.
Your web application requests the information from the DeadOrAliveWS web service and displays the result in
the label.
397
Note:
If no information is displayed, that name may not be in the database. Check your spelling or try
a different name.
398
New
Tip:
Tip:
3 Click OK.
The code-behind Designer appears, cursor in place between event handler brackets.
2 Add your logic.
3 Run the application.
The application saves and compiles. Once you compile the application, the generated .aspx file displays HTML
in the default web browser.
399
Tip: For testing purposes, use the employee.gdb database included with Interbase, if included with your version of
the product.
New
Tip:
Tip:
3 Click OK.
400
Tip:
Alternatively, use Data Explorer to drag and drop a table on to the Designer surface. Data
Explorer sets the connection string automatically.
To set up a connection
1 In Borland Data Provider: Connections Editor, select the appropriate item from the Connections list.
2 In Connection Settings, enter the Database path.
Note:
If referring to a database on the local disk, prepend the path with localhost:. If using Interbase,
for example, you would enter the path to your Interbase database: localhost:C:\Program Files
\Borland\Interbase\Examples\Database\employee.gdb (or whatever the actual path might be
for your system).
3 Complete the UserName and Password fields for the database as needed.
4 Click Test to confirm the connection.
In the Command tab, the areas for Tables and Columns are updated with information from your connection.
To set a command
1 In the Select area, enter an SQL command.
Tip:
For Interbase's employee.gdb database, you might enter select * from SALES, as an example.
401
The code-behind Designer appears, cursor in place between event handler brackets.
5 Code the DataBind call:
this.dataGrid1.DataBind();
Self.dataGrid1.DataBind();
Note:
6 Choose Run
If you are using data aware controls, for instance from a third-party provider, you may not need
to code the DataBind call.
Run.
The application compiles and the HTTP server displays a Web Form with the datagrid.
While presenting a minimum number of steps required to build a database project, the preceding procedure
demonstrates the major components of the ASP.NET, ADO.NET, and BDP.NET architectures at work, including:
providers, datasets, and adapters. The adapter connects to the physical data source via a provider, sending a
command that will read data from the data source and populate a dataset. Once populated, a datagrid displays data
from the dataset.
Once created, use other BDP.NET designers to modify and maintain the components of your project.
402
New
Tip:
3 Click OK.
The Button control appears on the Designer. Make sure the control is selected.
3 In Object Inspector, set the Text property to Hello, world!.
The code-behind Designer appears, cursor in place between event handler brackets.
2 Code the application logic:
button1.Text = button1.Text + "Hello, developer!";
403
Run.
The application compiles and the HTTP server displays a Web Form in your default browser with the "Hello,
world!" button.
2 Click the "Hello, world!" button.
The server updates the page with the response, "Hello, developer!".
3 Close the Web browser to return to the IDE.
404
component.
2 Specify XML and XSD filenames for non-existent files in the DBWebDataSource component.
Note:
It is best to create these files in the project directory or in a subdirectory off the project directory,
typically on your web server.
Run.
The first time the application runs, it creates the XSD file using the server metadata.
The first time a user runs the application, the application retrieves data from the server. When the user changes
data, thereafter, the application saves those changes to the server in a unique filename based on the username. If
the user shuts down the application and runs it again at a later time, the application restores the user's specific data.
At this point, the user can undo or modify the data. Anytime the OnApplyChangesRequest is called successfully,
the application deletes the unique user files and creates new ones.
Warning: If the tables or columns accessed by the application are altered after the application has run, you must
delete the XSD file to avoid a mismatch between the XSD file and the server metadata. Otherwise, you
can experience runtime errors and unpredictable behavior.
405
or DataTable.
3 Drag and drop a DBWebGrid and other control onto the Designer.
DataView, or DataTable.
Tip:
For more information about setting up BDP.NET data access components, see the related
procedure for building an ASP.NET database application. Instead of using a DataGrid and adding
a DataBind call, in the following procedure you use DB Web Controls without a DataBind call.
To configure a DBWebDataSource
1 Place a DBWebDataSource component on the Designer.
2 In the Object Inspector, select the DataSource property.
3 Select an existing data source (by default, this is called dataSet1).
Tip:
406
Note:
9 Choose Run
The application compiles and the HTTP server displays a Web Form with a DBWebGrid displaying data.
Tip: Dragging web components from the Tool Palette places them in an absolute position on an ASP.NET web
form. Double-clicking components in the Tool Palette leaves them in ASP.NET flow layout. Flow layout is
much easier to manage. For instance, controls in an absolute position on a web form can overwrite other
controls if they change sizes at runtime. Overwriting might occur when you add rows to and remove rows from
a grid control, making the grid control change size.
407
TABLE1 is the id that was added automatically to the the table tag in Step 3.
TABLE1.BgColor := 'blue';
TABLE1.BgColor = "blue";
7 Choose Run
408
bodytag.Attributes['bgcolor'] := 'yellow';
bodytag.Attributes["bgcolor"] = "yellow";
7 Choose Run
409
New
For the sake of illustration, we'll use the following XML records.
<?xml version="1.0" standalone="yes"> /// XML Declaration
<NewSongs>
/// <song> becomes the table name in your DataSet.
<song>
/// <songid> becomes Column1 in your DataSet.
<songid>1001</songid>
/// <title> becomes Column2 in your DataSet.
<title>Mary Had a Little Lamb</title>
</song>
<song>
<songid>1003</songid>
<title>Twinkle, Twinkle Little Star</title>
</song>
</NewSongs>
2 Change the TableName property to song.
3 Click the Columns (Collection) property to display the Columns Collection Editor.
4 Click Add to add a new column.
5 Change the ColumnName property to songid.
6 Click Add to add another new column.
7 Change the ColumnName property to title.
8 Click Close to close the Columns Collection Editor.
9 Click Close to close the Tables Collection Editor.
You have now created the metadata to match the XML file data.
410
New
2 Create a database connection and data adapter using the BDP.NET controls or other data adapter controls.
3 Drag and drop a DBWebDataSource control onto the Designer from the DB Web area of the Tool Palette.
4 In the XMLFileName property or in the XMLSchemaFile property, specify a new file name of a file that does not
yet exist.
5 Generate a DataSet from the data adapter.
6 Set the DataSource property of the DBWebDataSource to dataSet1.
7 Set the Active property of the data adapter to True.
8 Choose Run
Run.
This runs the application but also creates the XML file or XSD file and fills it with data from the DataSet.
To specify the XML file as a data source for a new ASP.NET application
1 Choose File
New
2 Drag and drop a DataSet component onto the Designer from the Data Components area of the Tool Palette.
3 Drag and drop a DBWebDataSource control onto the Designer from the DB Web area of the Tool Palette.
4 Specify the existing XML file name in the XMLFileName property of the DBWebDataSource control.
Note:
If you created an XSD file instead of an XML file, you specify the XSD file name in this step.
5 Specify the DataSet component in the DataSource property of the DBWebDataSource control.
6 Drag and drop a DBWebGrid control onto the Designer from the DB Web area of the Tool Palette.
7 Set the DBDataSource property of the DBWebGrid to the name of the DBWebDataSource
8 Choose Run
The application pulls data from the DataSet and XML file to fill the DBWebGrid.
Warning: It is possible for you to specify an existing XML file in the XMLFileName property of your
DBWebDataSource along with an active BdpDataAdapter and its DataSet. You can run the application
and the DBWeb controls will display the data from the XML file. However, this is not the intended use or
behavior of the XML capabilities of the DBWebDataSource. Although your XML file data may display
properly, the results of an update or any other operations on the data will be unpredictable.
411
Options
Debugger
ASP.NET.
The default application settings are displayed. Accept the default settings or change them as needed.
3 If you are creating a virtual directory for use with Internet Information Server (IIS), click the Server Options button
The virtual directory is created for you, enabling you to run the application.
412
The text box is filled with the value based on the type of aggregate you selected and the values in the column
you selected.
Note:
If you think there may be NULL values in your selected column, set the IgnoreNullValues
property to True, otherwise you may get an error.
413
<compilation
debug="true"
defaultLanguage="c#">
</compilation>
with this:
<compilation defaultLanguage="c#" debug="true">
<assemblies>
<add assembly="Borland.dbkasp, Version=9.0.0.1,
Culture=neutral, PublicKeyToken=b0524c541232aae7"/>
</assemblies>
</compilation>
<httpModules>
<add name="DbgConnect" type = "Borland.DbkAsp.DbkConnModule,
Borland.dbkasp,Version=9.0.0.1, Culture=neutral,
PublicKeyToken=b0524c541232aae7"/>
</httpModules>
3 Save the web.config file.
4 Open the application project in the IDE and run it.
Note: Before deploying an ASP.NET application, you should disable debugging and remove debugger references
from the web.config file, as described in the topic listed below.
414
Internet Options.
This turns off friendly messages and provides meaningful ASP.NET messages.
415
The properties in the Connections Editor are organized into three categories: Connections, Options, and Provider
Settings. The Connections options designate the database and authentication parameters. The Options area
includes various database-specific database options, including transaction isolation types. The Provider Settings
area specifies assemblies and the client libraries required to accomplish the connection to the given database.
Note:
All of the procedures in this topic assume that you already have installed a database client,
server, or both, and that the database instance is running.
Description
Default
CommitRetain
False
LoginPrompt
Determines if you want the user to be prompted for a login every time
the application tries to connect to the database.
False
QuoteObjects
Specifies that table names, column names, and other objects should
be quoted or otherwise delimited when included in a SQL statement.
This is required for databases that allow spaces in names, such as MS
Access.
False
RoleName
If there is a role for you in the database, you can enter the rolename
here. The role is generally an authentication alias, that combines your
identify with your access rights.
myRole
ServerCharSet
SQLDialect
TransactionIsolation
Shared locks are held while the data is being read to avoid dirty reads,
but the data can be changed before the end of the transaction, resulting
ReadCommitted
416
False
4 You should be able to accept the defaults for the following Provider Settings:
Option
Default
Assembly
Provider
Interbase
VendorClient
gds32.dll
Note:
If you are writing ASP.NET applications, and are running the ASP.NET Web Forms locally for
testing purposes, you might need to modify the path statement that points to your database, to
include the localhost: designation. For example, you would modify the path shown earlier
in this topic as such: localhost:C:\Program Files\Common Files\Borland Shared
\Data\employee.gdb.
For example, use one of the sample MS SQL Server databases, such as Pubs or Northwind. There is no need
to add the file extension to the name.
2 Enter the hostname.
If you are using a local database server, enter (local) in this field.
3 If you are deferring to your OS authentication, set OSAuthentication to True.
4 If you are using database authentication, enter the password and username into the appropriate fields.
Description
Default
BlobSize
1024
LoginPrompt
Determines if you want the user to be prompted for a login every time
the application tries to connect to the database.
False
417
QuoteObjects
Specifies that table names, column names, and other objects should
be quoted or otherwise delimited when included in a SQL statement.
This is required for databases that allow spaces in names, such as MS
Access.
False
TransactionIsolation
Shared locks are held while the data is being read to avoid dirty reads,
but the data can be changed before the end of the transaction, resulting
in non-repeatable reads or phantom data. This specifies the value for
the BdpTransaction. IsolationLevel property.
ReadCommitted
6 You should be able to accept the defaults for the following Provider Settings:
Option
Default
Assembly
Provider
MSSQL
VendorClient
sqloledb.dll
Note:
If you are writing ASP.NET applications, and are running the ASP.NET Web Forms locally for
testing purposes, you might need to modify the path statement that points to your database, to
include the localhost: designation, prepended to the path.
Description
Default
LoginPrompt
Determines if you want the user to be prompted for a login every time
the application tries to connect to the database.
False
QuoteObjects
Specifies that table names, column names, and other objects should
be quoted or otherwise delimited when included in a SQL statement.
This is required for databases that allow spaces in names.
False
TransactionIsolation
Shared locks are held while the data is being read to avoid dirty reads,
but the data can be changed before the end of the transaction, resulting
in non-repeatable reads or phantom data. This specifies the value for
the BdpTransaction. IsolationLevel property.
ReadCommitted
418
4 You should be able to accept the defaults for the following Provider Settings:
Option
Default
Assembly
Provider
DB2
VendorClient
db2cli.dll
This means that the system defers to your local system username and password to login to the database.
3 If you are using database authentication, enter the password and username into the appropriate fields.
For example, the typical Oracle username and password for the sample database is SCOTT and TIGER,
respectively.
4 Set the following database options, if necessary.
Description
Default
LoginPrompt
Determines if you want the user to be prompted for a login every time
the application tries to connect to the database.
False
QuoteObjects
Specifies that table names, column names, and other objects should
be quoted or otherwise delimited when included in a SQL statement.
This is required for databases that allow spaces in names.
False
TransactionIsolation
Shared locks are held while the data is being read to avoid dirty reads,
but the data can be changed before the end of the transaction, resulting
in non-repeatable reads or phantom data. This specifies the value for
the BdpTransaction. IsolationLevel property.
ReadCommitted
5 You should be able to accept the defaults for the following Provider Settings:
Option
Default
Assembly
Provider
Oracle
VendorClient
oci.dll
If you have the Office Component Toolkit installed, you might find Northwind in C:\Program Files\Office
Component Toolpack\Data\Northwind.mdb.
2 Enter the username and password.
By default, you can generally try admin for the username and leave the password field empty.
3 Set the following database options, if necessary.
Description
Default
BlobSize
1024
LoginPrompt
Determines if you want the user to be prompted for a login every time
the application tries to connect to the database.
False
QuoteObjects
Specifies that table names, column names, and other objects should
be quoted or otherwise delimited when included in a SQL statement.
This is required for databases that allow spaces in names, such as MS
Access.
False
TransactionIsolation
Shared locks are held while the data is being read to avoid dirty reads,
but the data can be changed before the end of the transaction, resulting
in non-repeatable reads or phantom data. This specifies the value for
the BdpTransaction. IsolationLevel property.
ReadCommitted
4 You should be able to accept the defaults for the following Provider Settings:
Option
Default
Assembly
Provider
MSAccess
VendorClient
msjet40.dll
Description
Default
BlobSize
1024
ClientAppName
420
ClientHostName
LoginPrompt
Determines if you want the user to be prompted for a login every time
the application tries to connect to the database.
False
PacketSize
Specifies the number of bytes per network packet transferred from the
database server to the client.
512
QuoteObjects
Specifies that table names, column names, and other objects should
be quoted or otherwise delimited when included in a SQL statement.
This is required for databases that allow spaces in names, such as MS
Access.
False
TransactionIsolation
Shared locks are held while the data is being read to avoid dirty reads,
but the data can be changed before the end of the transaction, resulting
in non-repeatable reads or phantom data. This specifies the value for
the BdpTransaction. IsolationLevel property.
ReadCommitted
4 You should be able to accept the defaults for the following Provider Settings:
Option
Default
Assembly
Provider
Sybase
VendorClient
libct.dll
421
These include, but are not restricted to InvokeRegistry, RIO, and SOAPHTTPClient.
Warning:
The preceding list of units is not inclusive. Make sure you identify all SOAP units, regardless
of naming convention. Not all of the units include the word SOAP in the name.
2 Remove the reference to the Delphi for Win32 WSDL Importer-generated Interface proxy unit.
3 Remove the proxy unit from the project.
Once you have saved the project, the UDDI Browser appears.
2 Enter the URL you want to use, either a service you are already familiar with, or the one saved from your RIO
If you want to locate a WSDL file on your local disk, you can click the ellipsis button next to the
list box and search for the document. You can also navigate to one of the web service sites
listed in the UDDI Browser if you want to use a published service.
3 Click the Add Reference button to add the WSDL document to your project.
Delphi 2005 creates the necessary web reference and the corresponding proxy unit based on the WSDL
document. A new Web References node appears in the Project Manager. Expand it to see the associated WSDL
and proxy code files.
4 Choose File
Use Unit.
422
to
var
// This is the type of the new proxy class.
HelloService: Service3;
This assumes the name of your service is Service3. Change the name accordingly.
Note:
You will see that what was formerly created as an interface is now created as a class.
The .NET Framework provides automatic garbage collection, and so certain restrictions placed
on the use of classes in previous versions of Delphi may no longer apply when using Delphi
2005.
to:
HelloService := Service3.Create;
423
begin
HelloService := Service3.Create;
Caption := HelloService.HelloWorld;
end;
Your code is most likely more complex than this example. However, these instructions cover the basic steps for
porting any Delphi for Win32 application that uses web services to Delphi 2005.
424
Warning:
If you choose to bind columns in this way, you must set the AutoGenerateColumns property
to False. Setting this property to True raises a runtime error, and does not allow the visible
restriction of columns at designtime. If the same column is bound to a grid more than once,
you may get a runtime error.
425
Folder Options.
To give users rights when UseUniqueFileName is true and user authentication is in use
1 On the Windows Control Panel User Accounts dialog, create a new user.
2 In the IIS virtual directory where your web application is built, create a new folder named CacheFiles.
Note: You must make sure that the Use Simple File Sharings option in your Windows Folder Options is
unchecked.
426
Note:
If you want to know the various command flags for the aspnet_regiis.exe utility, follow the basic
command with a ? character instead of the -i flag.
Control Panel
Warning:
Do not give your ASPNET user administrator privileges. This opens up a security hole in
your system and makes deployed ASP.NET applications vulnerable to hacking. Instead,
create an impersonated user.
427
Control Panel
To restart IIS
1 Choose Start
Control Panel
Administrative Tools
If there is not a folder or virtual directory, you will need to create a virtual directory.
2 Select the folder.
3 Right-click and select Properties.
4 Click the Virtual Directory tab.
5 Under the Application Settings area, click the Create button.
If the Remove button is displayed instead, you can remove, then create the virtual directory again, if necessary.
Control Panel
Administrative Tools
428
If there is not a folder or virtual directory, you will need to create a virtual directory.
2 Select the folder.
3 Right-click and select Properties.
4 Click the Directory Security tab.
5 Click Edit.
6 Select the Anonymous Access check box.
7 In the User name: field, enter the name of the ASPNET user you created.
8 Check the Integrated Windows authentication check box or add your own password.
9 Click OK twice.
429
Note:
New
Other
You can also use the separate DB Web Control Wizard for C#. It works identically to the wizard
described here.
This informs the wizard to add to the control file code that implements IDBWebDataLink. This interface defines
the means to access data source and table information.
4 Select Bind to DataColumn if you want to bind to a column, for instance, if your control supports a single type
of data.
This informs the wizard to add to the control file code that implements IDBWebColumnLink. This interface defines
the means to access a column in the table accessed by way of IDBWebDataLink.
5 If you select Bind to DataColumn and your control is one of the lookup controls, such as a listbox, radio button
group, or check box control, and you want the new control to be a lookup control also, check the Supports
Lookup check box.
This informs the wizard to add to the control file code that implements IDBWebLookupColumnLink. This interface
defines the means to access the lookup table, the text field and value field of the column accessed by way of
IDBWebColumnLink.
The DB Web Control Wizard creates a template file and displays it in the Code Editor. You then modify this file to
inherit from a specific DB Web control.
430
431
New Other Deployment ASP.NET Deployment and click OK. (The Deployment node
is not displayed in the New Items dialog box unless an ASP.NET project is open.)
The Deploy tab is displayed and a .bdsdeploy file is added to the project directory and displayed in the Project
Manager. The files required for deployment are listed on the left side of the Deploy tab under Source Files.
Tip:
Only files that have been saved are displayed in the list; save any new files and refresh the
Deployment Manager to display the files.
3 In the Destination drop-down list, select either Folder Location or FTP Location.
If you select Folder Location, the Browse For Folder dialog box is displayed. You can select an existing
directory or click Make New Folder to create a new one.
If you select FTP Location, the FTP Site dialog box is displayed. Enter the connection information. Click Help
for an explanation of each field.
Click OK to return to the Deployment Manager.
4 If you selected an FTP location, check the Connected check box to connect and display the files, if any, in the
destination directory.
5 Review the files in the Source Files list.
Click a file to display detailed file information in the text box below the file list.
6 To copy all of the files to the destination directory, click the Copy All New or Modified Files to Destination
button
on the toolbar at the top of the Deployment Manager. The files are copied immediately to the
destination directory and displayed in the Destination Files list.
To modify the file list, right-click anywhere in the file list and use the context menu commands, or use the file list
status buttons, as described below.
Tip:
To select a file in the list, click the file name. To select multiple files, press CTRL and click the
files. To select a range of files, press CTRL+SHIFT, click the first file in the range and then the last
file in the range.
Description
Refresh
432
Displays all of the files in the project directory, even those that
are not required to deploy the application.
Ignore Group(s)
Ignore File(s)
Causes all of the files in a node of the source files list to be ignored
by the Deployment Manager.
Enable Logging
View Log
7 When you are satisfied with the deployment criteria, save your changes to the .bdsdeploy file.
When you reopen the project, you can open the Deployment Manager from the Project Manager and deploy
the application as is, or modify the deployment criteria as described above.
The following buttons indicate the status of the files in the file list and can be used to copy or delete the file, as
described below.
File List Status Button Description
The file is elligible to copy (it does not exist in the destination directory, or the source file has changed
since it was last copied to the destination).
Click the button to copy the file to the destination directory.
The file exists in the destination directory, but not in the project directory. You can probably safely
delete it from the destination directory.
Click the button to delete the file from the destination directory.
The status of the file in the is unknown. It might have a later time stamp than the file in the project
directory.
Click the button to replace the file in the destination directory.
Control Panel
Administrative Tools
Internet Information
2 In the Internet Information Services dialog box, expand the tree view to display the local computer node.
3 Right-click the Default Web Site node and choose New
Virtual Directory.
For more information about virtual directories, refer to the IIS online Help system.
433
The HTML code appears in the tag editor window, with syntax highlighting. The gray header of the tag editor now
displays the higher level tag, usually the FORM tag that defines this particular Web Form.
Note:
If a control is defined using several lines of HTML code, when you select the control, the first
line of the code is displayed in the gray header of the tag editor. The additional code appears
below in the tag editor window.
The editor displays the HTML code for each element as you drop them on the Designer surface.
2 Click anywhere on the Designer surface to deselect all controls.
This displays the code for all the controls in the tag editor, with syntax highlighting.
To modify a control
1 Click anywhere on the Designer surface to deselect all controls.
2 Locate the tag that corresponds to the control you want to modify.
3 Modify the code, and the change is immediately reflected in the control on the Designer surface.
4 Save your project to make the modifications permanent.
Options
HTML/ASP.NET Options.
You can only use this feature when the cursor is somewhere in the tag editor, rather than on
the Designer surface.
434
2 To zoom in so that you can view only the content within the FORM tags, click the right-hand blue arrow in the
You can only use this feature when the cursor is somewhere in the tag editor, rather than on
the Designer surface.
Options
HTML/ASP.NET Options.
435
New
Other
ASP.NET User Control.
A new .ascx file is added to the Project Manager and the empty page is displayed in the Designer.
Optionally, rename the .ascx file by right-clicking it in the Project Manager and choosing Rename. Any
associated files, such at the .pas or .resx files, are also renamed.
3 Design the page by adding controls, setting properties, and adding code to the code-behind .pas file as needed.
4 Save and compile the project.
Insert User Control to display the Insert User Control dialog box.
3 Select a user control from the drop-down list or use the Browse button to navigate to a user control file (.ascx).
4 Click OK to add the user control to the Web Form.
5 Optionally, in the Object Inspector, provide a descriptive name for the user control button with the Id property.
6 Save and compile the project.
The Web Form is displayed in the browser and the user control button is replaced with its encapsulated controls.
Tip: The runtime appearance of the user control depends on the appearance of the encapsulated page and controls,
not the position of the user control button. If you are adding multiple user controls to a page, run the application
to ensure that the controls do not overlap each other.
436
Database
437
Data Explorer.
Tip: If you need to modify your new connection settings, right-click on your new connection and scroll down to
modify a connection. A Connection Editor dialog appears. Enter your connection settings and click OK.
438
Data Explorer.
Note:
If you receive an error because your connection is not live, you should refresh your provider,
and/or modify your connection.
This operation returns a result set into a tabbed Data Explorer page in the Code Editor.
Tip:
You can also select a table in the Data Explorer and right-click to display a pop-up menu with a
Retrieve Data From Table command.
Data Explorer.
You can also select a procedure in the Data Explorer and right-click to display a pop-up menu
with an Execute command.
439
Tip: For testing purposes, use the employee.gdb database included with Interbase, if included with your version of
the product.
New
Tip:
Tip:
3 Click OK.
440
Tip:
Alternatively, use Data Explorer to drag and drop a table on to the Designer surface. Data
Explorer sets the connection string automatically.
To set up a connection
1 In Borland Data Provider: Connections Editor, select the appropriate item from the Connections list.
2 In Connection Settings, enter the Database path.
Note:
If referring to a database on the local disk, prepend the path with localhost:. If using Interbase,
for example, you would enter the path to your Interbase database: localhost:C:\Program Files
\Borland\Interbase\Examples\Database\employee.gdb (or whatever the actual path might be
for your system).
3 Complete the UserName and Password fields for the database as needed.
4 Click Test to confirm the connection.
In the Command tab, the areas for Tables and Columns are updated with information from your connection.
To set a command
1 In the Select area, enter an SQL command.
Tip:
For Interbase's employee.gdb database, you might enter select * from SALES, as an example.
441
The code-behind Designer appears, cursor in place between event handler brackets.
5 Code the DataBind call:
this.dataGrid1.DataBind();
Self.dataGrid1.DataBind();
Note:
6 Choose Run
If you are using data aware controls, for instance from a third-party provider, you may not need
to code the DataBind call.
Run.
The application compiles and the HTTP server displays a Web Form with the datagrid.
While presenting a minimum number of steps required to build a database project, the preceding procedure
demonstrates the major components of the ASP.NET, ADO.NET, and BDP.NET architectures at work, including:
providers, datasets, and adapters. The adapter connects to the physical data source via a provider, sending a
command that will read data from the data source and populate a dataset. Once populated, a datagrid displays data
from the dataset.
Once created, use other BDP.NET designers to modify and maintain the components of your project.
442
component.
2 Specify XML and XSD filenames for non-existent files in the DBWebDataSource component.
Note:
It is best to create these files in the project directory or in a subdirectory off the project directory,
typically on your web server.
Run.
The first time the application runs, it creates the XSD file using the server metadata.
The first time a user runs the application, the application retrieves data from the server. When the user changes
data, thereafter, the application saves those changes to the server in a unique filename based on the username. If
the user shuts down the application and runs it again at a later time, the application restores the user's specific data.
At this point, the user can undo or modify the data. Anytime the OnApplyChangesRequest is called successfully,
the application deletes the unique user files and creates new ones.
Warning: If the tables or columns accessed by the application are altered after the application has run, you must
delete the XSD file to avoid a mismatch between the XSD file and the server metadata. Otherwise, you
can experience runtime errors and unpredictable behavior.
443
New
The BdpDataAdapter, BdpConnection, and other BDP.NET components can be found on the Tool Palette in the
Borland Data Provider area.
3 At the bottom of the Object Inspector, click the Designer Verb Connection Editor.
Note:
Designer verbs are action phrases that appear in the lower left-hand corner of the Object
Inspector. When you move the cursor over the phrase, the cursor changes to a hand pointer.
Tip: Alternatively, use Data Explorer to drag and drop a table on to the designer surface. Data Explorer sets the
connection string automatically.
To set up a connection
1 Click the Connections Editor Designer Verb at the bottom of the Object Inspector.
2 In the Borland Data Provider: Connections Editor dialog box, select an existing connection from the
444
Tip:
If using Interbase, you would enter the path to your Interbase database, which may be located
locally in c:\Program Files\Common Files\Borland Shared\Data. If connecting to a
shared network location, you will need to enter the network path and you will need to have access
rights for that remote server.
4 Complete the UserName and Password fields for the database as needed.
Tip:
If you are using a sample Interbase database, the username and password are, respectively,
sysdba and masterkey.
To create a dataset
1 To make sure you get the data you want, click the Preview Data tab on the Data Adapter Configuration editor.
2 Click Refresh.
Column and row data should appear. If they don't appear, it may be that you either do not have a live connection
to a database or your SQL statement is incorrect.
3 Click the DataSet tab.
4 Click New DataSet.
5 Either accept the default name or enter a more descriptive name.
6 Click OK.
A new DataSet component appears in the Component Tray at the bottom of the IDE.
445
5 Select the DataSet component that you generated previously (the default is dataSet1).
6 In the Object Inspector, select the DataMember property drop-down.
7 Select the appropriate table.
Run.
446
or DataTable.
3 Drag and drop a DBWebGrid and other control onto the Designer.
DataView, or DataTable.
Tip:
For more information about setting up BDP.NET data access components, see the related
procedure for building an ASP.NET database application. Instead of using a DataGrid and adding
a DataBind call, in the following procedure you use DB Web Controls without a DataBind call.
To configure a DBWebDataSource
1 Place a DBWebDataSource component on the Designer.
2 In the Object Inspector, select the DataSource property.
3 Select an existing data source (by default, this is called dataSet1).
Tip:
447
Note:
9 Choose Run
The application compiles and the HTTP server displays a Web Form with a DBWebGrid displaying data.
Tip: Dragging web components from the Tool Palette places them in an absolute position on an ASP.NET web
form. Double-clicking components in the Tool Palette leaves them in ASP.NET flow layout. Flow layout is
much easier to manage. For instance, controls in an absolute position on a web form can overwrite other
controls if they change sizes at runtime. Overwriting might occur when you add rows to and remove rows from
a grid control, making the grid control change size.
448
New
Typically, this will be either a Windows Form, a VCL Form, or an ASP.NET application.
4 Expand the Data Explorer Tree by drilling down to the Table or View level.
If the connection to your database is live, the small red x will disappear when you expand the connection node
for the database. If it's not live, you may need to modify the connection string.
5 Using the cursor, grab one of the tables named in the list.
6 Drag and drop the table object onto your form.
For instance, set the Active property to True if you want to be able to view data in your component at design time.
Note: A DataGrid will not appear automatically so make sure you drop a DataGrid component onto your form to
appropriately display data, when necessary.
449
Note:
To create an application
1 Choose File
New
You should see two objects in the Component Tray: a BdpDataAdapter and a BdpConnection.
For more information about how to create database applications, refer to the additional ADO.NET and database
topics in this Help system.
This creates a new dataset and displays an icon for it in the Component Tray.
450
4 If you checked the Use a dataset to suggest table and column names check box, you can choose the dataset
If there is more than one table in the data source, their names appear in the drop down list.
6 If you chose to use a dataset to suggest table and column names, and that dataset contains more than one table,
you can select the table you want to use from the Dataset table drop down list.
The column names from the source table and from the dataset should appear in the Column mappings grid.
As they are displayed by default, they represent the mapping from source to dataset; in other words, the data
adapter reads data from each column named on the left side of the grid and stores the data in the dataset column
named in the corresponding field on the right side of the grid. You can change the names on either side by typing
new names or by selecting different tables. This allows you to store queried data into different dataset columns
than the ones created in the dataset by default.
7 If you want to modify a mapping, type a new name in the Dataset table column next to the target Source table
column.
This results in the data from the Source table column being stored in the new dataset column.
Note:
If you want to reset the column names so that the dataset columns match the data source
columns, you can click the Reset button.
To delete a mapping
1 Select the grid row that you want to delete.
2 Click Delete.
This will cause the query to ignore that column in the source table and to not fill the dataset column with any data.
451
New
For the sake of illustration, we'll use the following XML records.
<?xml version="1.0" standalone="yes"> /// XML Declaration
<NewSongs>
/// <song> becomes the table name in your DataSet.
<song>
/// <songid> becomes Column1 in your DataSet.
<songid>1001</songid>
/// <title> becomes Column2 in your DataSet.
<title>Mary Had a Little Lamb</title>
</song>
<song>
<songid>1003</songid>
<title>Twinkle, Twinkle Little Star</title>
</song>
</NewSongs>
2 Change the TableName property to song.
3 Click the Columns (Collection) property to display the Columns Collection Editor.
4 Click Add to add a new column.
5 Change the ColumnName property to songid.
6 Click Add to add another new column.
7 Change the ColumnName property to title.
8 Click Close to close the Columns Collection Editor.
9 Click Close to close the Tables Collection Editor.
You have now created the metadata to match the XML file data.
452
New
2 Create a database connection and data adapter using the BDP.NET controls or other data adapter controls.
3 Drag and drop a DBWebDataSource control onto the Designer from the DB Web area of the Tool Palette.
4 In the XMLFileName property or in the XMLSchemaFile property, specify a new file name of a file that does not
yet exist.
5 Generate a DataSet from the data adapter.
6 Set the DataSource property of the DBWebDataSource to dataSet1.
7 Set the Active property of the data adapter to True.
8 Choose Run
Run.
This runs the application but also creates the XML file or XSD file and fills it with data from the DataSet.
To specify the XML file as a data source for a new ASP.NET application
1 Choose File
New
2 Drag and drop a DataSet component onto the Designer from the Data Components area of the Tool Palette.
3 Drag and drop a DBWebDataSource control onto the Designer from the DB Web area of the Tool Palette.
4 Specify the existing XML file name in the XMLFileName property of the DBWebDataSource control.
Note:
If you created an XSD file instead of an XML file, you specify the XSD file name in this step.
5 Specify the DataSet component in the DataSource property of the DBWebDataSource control.
6 Drag and drop a DBWebGrid control onto the Designer from the DB Web area of the Tool Palette.
7 Set the DBDataSource property of the DBWebGrid to the name of the DBWebDataSource
8 Choose Run
The application pulls data from the DataSet and XML file to fill the DBWebGrid.
Warning: It is possible for you to specify an existing XML file in the XMLFileName property of your
DBWebDataSource along with an active BdpDataAdapter and its DataSet. You can run the application
and the DBWeb controls will display the data from the XML file. However, this is not the intended use or
behavior of the XML capabilities of the DBWebDataSource. Although your XML file data may display
properly, the results of an update or any other operations on the data will be unpredictable.
453
The text box is filled with the value based on the type of aggregate you selected and the values in the column
you selected.
Note:
If you think there may be NULL values in your selected column, set the IgnoreNullValues
property to True, otherwise you may get an error.
454
Data Explorer.
2 Select a connection.
3 Right-click the connection and choose SQL Window.
To execute SQL
1 Enter a valid SQL statement or stored procedure name in the multi-line text box at the top of the SQL Window.
2 Click Execute SQL.
If the SQL statement or stored procedure is valid, the result set appears in the bottom pane of the SQL Window.
Note:
The SQL statement or stored procedure must operate against the current connection and its
target database. You cannot execute SQL against a database to which you are not connected.
3 Click Clear All SQL to clear the SQL statement or stored procedure from the multi-line text box.
455
same name, or, if there is no corresponding dataset column, if you want the adapter to perform the action specified
in the MissingSchemaAction property.
3 Select Ignore if you want to keep data from being loaded when data source columns are not properly mapped
to dataset columns.
This could occur if mapped columns are of incompatible data types, lengths, or have other errors.
4 Select Error if you want the adapter to raise an error that you can trap.
Setting the MissingMappingAction property to Passthrough and the MissingSchemaAction to Add results in a
duplication of data source table and column names in the dataset.
2 Select AddWithKey if you want the data source table or column added to the dataset and its schema along with
456
Data Explorer.
The Data Explorer page for data migration opens in the Code Editor. This data migration page lets you select
one or more tables from a source provider connection and a destination connection to which the tables will be
migrated.
3 Choose a connection from the Source Connection drop-down list box.
The tables associated with this connection appear in the list box beneath the connection.
4 Choose a connection from the Destination Connection drop-down list box.
The tables associated with this connection appear in the list box beneath the connection.
5 Select one or more tables to migrate from the list of tables associated with the source connection.
To select consecutive tables, click the first table, press and hold down the SHIFT key, and then click the last table.
To select nonconsecutive tables, press and hold down CTRL, and then click each table.
6 Click the Include (>) button to include these tables for migration to the destination connection.
The selected tables appear in the list of tables for the destination connection. If one of the selected tables has
the same name as a table in the destination connection, it cannot be migrated.
7 Click Migrate to copy the tables to the destination connection.
The Data Migration page shows the progress as SQL types are mapped, tables are created, data is retrieved
from the source connection, and data is populated in the new table in the destination connection. The result of
each operation is reported for each table.
8 Right-click the Tables node in the destination provider and choose Refresh.
Data Explorer.
2 Expand the Tables node in the source provider, and select the database table containing the data and structure
457
458
To modify connections
1 Choose View
Data Explorer.
2 Select a provider.
3 Right-click to display a pop-up menu to view your options.
To refresh a connection
1 Choose View
Data Explorer.
2 Select a provider.
3 Right-click to display a pop-up menu.
4 Choose Refresh.
This operation reinitializes all connections defined for the selected provider.
To delete a connection
1 Choose View
Data Explorer.
2 Select a connection.
3 Right-click to display a pop-up menu.
4 Choose Delete Connection.
This displays a confirmation message that asks if you want to delete the connection.
5 Click OK.
To modify a connection
1 Choose View
Data Explorer.
2 Select a connection.
3 Right-click to display a pop-up menu.
4 Choose Modify Connection.
To close a connection
1 Choose View
Data Explorer.
2 Select a connection.
3 Right-click to display a pop-up menu.
4 Choose Close Connection.
459
If the Close Connection command is disabled in the menu, the connection is not open.
To rename a connection
1 Choose View
Data Explorer.
2 Select a connection.
3 Right-click to display a pop-up menu.
4 Choose Rename Connection.
The Data Explorer displays the connection with its new name.
460
The properties in the Connections Editor are organized into three categories: Connections, Options, and Provider
Settings. The Connections options designate the database and authentication parameters. The Options area
includes various database-specific database options, including transaction isolation types. The Provider Settings
area specifies assemblies and the client libraries required to accomplish the connection to the given database.
Note:
All of the procedures in this topic assume that you already have installed a database client,
server, or both, and that the database instance is running.
Description
Default
CommitRetain
False
LoginPrompt
Determines if you want the user to be prompted for a login every time
the application tries to connect to the database.
False
QuoteObjects
Specifies that table names, column names, and other objects should
be quoted or otherwise delimited when included in a SQL statement.
This is required for databases that allow spaces in names, such as MS
Access.
False
RoleName
If there is a role for you in the database, you can enter the rolename
here. The role is generally an authentication alias, that combines your
identify with your access rights.
myRole
ServerCharSet
SQLDialect
TransactionIsolation
Shared locks are held while the data is being read to avoid dirty reads,
but the data can be changed before the end of the transaction, resulting
ReadCommitted
461
False
4 You should be able to accept the defaults for the following Provider Settings:
Option
Default
Assembly
Provider
Interbase
VendorClient
gds32.dll
Note:
If you are writing ASP.NET applications, and are running the ASP.NET Web Forms locally for
testing purposes, you might need to modify the path statement that points to your database, to
include the localhost: designation. For example, you would modify the path shown earlier
in this topic as such: localhost:C:\Program Files\Common Files\Borland Shared
\Data\employee.gdb.
For example, use one of the sample MS SQL Server databases, such as Pubs or Northwind. There is no need
to add the file extension to the name.
2 Enter the hostname.
If you are using a local database server, enter (local) in this field.
3 If you are deferring to your OS authentication, set OSAuthentication to True.
4 If you are using database authentication, enter the password and username into the appropriate fields.
Description
Default
BlobSize
1024
LoginPrompt
Determines if you want the user to be prompted for a login every time
the application tries to connect to the database.
False
462
QuoteObjects
Specifies that table names, column names, and other objects should
be quoted or otherwise delimited when included in a SQL statement.
This is required for databases that allow spaces in names, such as MS
Access.
False
TransactionIsolation
Shared locks are held while the data is being read to avoid dirty reads,
but the data can be changed before the end of the transaction, resulting
in non-repeatable reads or phantom data. This specifies the value for
the BdpTransaction. IsolationLevel property.
ReadCommitted
6 You should be able to accept the defaults for the following Provider Settings:
Option
Default
Assembly
Provider
MSSQL
VendorClient
sqloledb.dll
Note:
If you are writing ASP.NET applications, and are running the ASP.NET Web Forms locally for
testing purposes, you might need to modify the path statement that points to your database, to
include the localhost: designation, prepended to the path.
Description
Default
LoginPrompt
Determines if you want the user to be prompted for a login every time
the application tries to connect to the database.
False
QuoteObjects
Specifies that table names, column names, and other objects should
be quoted or otherwise delimited when included in a SQL statement.
This is required for databases that allow spaces in names.
False
TransactionIsolation
Shared locks are held while the data is being read to avoid dirty reads,
but the data can be changed before the end of the transaction, resulting
in non-repeatable reads or phantom data. This specifies the value for
the BdpTransaction. IsolationLevel property.
ReadCommitted
463
4 You should be able to accept the defaults for the following Provider Settings:
Option
Default
Assembly
Provider
DB2
VendorClient
db2cli.dll
This means that the system defers to your local system username and password to login to the database.
3 If you are using database authentication, enter the password and username into the appropriate fields.
For example, the typical Oracle username and password for the sample database is SCOTT and TIGER,
respectively.
4 Set the following database options, if necessary.
Description
Default
LoginPrompt
Determines if you want the user to be prompted for a login every time
the application tries to connect to the database.
False
QuoteObjects
Specifies that table names, column names, and other objects should
be quoted or otherwise delimited when included in a SQL statement.
This is required for databases that allow spaces in names.
False
TransactionIsolation
Shared locks are held while the data is being read to avoid dirty reads,
but the data can be changed before the end of the transaction, resulting
in non-repeatable reads or phantom data. This specifies the value for
the BdpTransaction. IsolationLevel property.
ReadCommitted
5 You should be able to accept the defaults for the following Provider Settings:
Option
Default
Assembly
Provider
Oracle
VendorClient
oci.dll
If you have the Office Component Toolkit installed, you might find Northwind in C:\Program Files\Office
Component Toolpack\Data\Northwind.mdb.
2 Enter the username and password.
By default, you can generally try admin for the username and leave the password field empty.
3 Set the following database options, if necessary.
Description
Default
BlobSize
1024
LoginPrompt
Determines if you want the user to be prompted for a login every time
the application tries to connect to the database.
False
QuoteObjects
Specifies that table names, column names, and other objects should
be quoted or otherwise delimited when included in a SQL statement.
This is required for databases that allow spaces in names, such as MS
Access.
False
TransactionIsolation
Shared locks are held while the data is being read to avoid dirty reads,
but the data can be changed before the end of the transaction, resulting
in non-repeatable reads or phantom data. This specifies the value for
the BdpTransaction. IsolationLevel property.
ReadCommitted
4 You should be able to accept the defaults for the following Provider Settings:
Option
Default
Assembly
Provider
MSAccess
VendorClient
msjet40.dll
Description
Default
BlobSize
1024
ClientAppName
465
ClientHostName
LoginPrompt
Determines if you want the user to be prompted for a login every time
the application tries to connect to the database.
False
PacketSize
Specifies the number of bytes per network packet transferred from the
database server to the client.
512
QuoteObjects
Specifies that table names, column names, and other objects should
be quoted or otherwise delimited when included in a SQL statement.
This is required for databases that allow spaces in names, such as MS
Access.
False
TransactionIsolation
Shared locks are held while the data is being read to avoid dirty reads,
but the data can be changed before the end of the transaction, resulting
in non-repeatable reads or phantom data. This specifies the value for
the BdpTransaction. IsolationLevel property.
ReadCommitted
4 You should be able to accept the defaults for the following Provider Settings:
Option
Default
Assembly
Provider
Sybase
VendorClient
libct.dll
466
multiple providers
2 Add and configure a DataSync component to connect the providers
3 Add and configure a DataHub component to connect the DataSync to a DataSet
New
3 Expand the Data Explorer Tree to expose the providers and database tables you want to use.
You must have a live connection to expand provider nodes. If you do not have a live connection, you may need
to modify the connection string.
4 Drag and drop tables from one or more providers onto your form.
For each table you drag onto your form, a BdpConnection and a BdpDataAdapter appear in the component tray.
If you add multiple tables from the same provider, you can delete all but one BdpConnection for that provider.
5 Configure each BdpDataAdapter component.
There is no need to set the Active or DataSet properties, as the DataSet will be populated by the DataHub
component.
6 Add a DataSet component to your form from the Data Components category of the Tool Palette.
7 Add and configure a DataGrid component to your form from the Data Controls category of the Tool Palette.
Set the DataSource property for the DataGrid to the name of the added DataSet component (for example,
dataSet1).
Collection Editor.
4 In the the DataProvider Collection Editor, add a DataProvider for each table you want to provide and resolve.
467
appropriate BdpDataAdapter.
6 When you have finished configuring your DataProviders, click OK to close the DataProvider Collection Editor.
7 In the Object Inspector, set the CommitBehavior property to specify how failures are handled during resolving.
Run.
468
To pass a parameter
1 Create a data adapter and connection to the Interbase employee.gdb database.
2 Add a text box control, a button control, and a data grid control to your form.
3 Configure the data adapter.
4 To add a parameter to the data adapter.
5 Configure the data grid.
6 Add code to the button Click event..
7 Compile and run the application.
New
This creates a BdpDataAdapter and BdpConnection and displays their icons in the Component Tray.
4 Select the data adapter icon, then click the Configure Data Adapter designer verb in the Designer Verb area
As you can see, this statement is limiting the number of fields. It also contains a ? character as part of the Where
clause. The ? character is a wildcard that represents the parameter value that your application passes in at
runtime. There are at least two reasons for using a parameter in this way. The first reason is to make the
application capable of retrieving numerous instances of the data in the selected columns, while using a different
value to satisfy the condition. The second reason is that you may not know the actual values at design time. You
can imagine how limited the application might be if we retrieved only data where FIRST_NAME = 'Bob'.
6 Click the DataSet tab.
7 Click New DataSet.
8 Click OK.
469
Inspector.
You should be able to see your Select statement in the SelectCommand property drop down list box.
2 Change the ParameterCount property to 1.
3 Click the (Collection) entry next to the Parameters property.
ParameterName to emp.
7 Click OK.
8 In the Object Inspector, set the Active property under Live Data to True.
fields of data.
This should display the column names of the columns specified in the SQL statement that you entered into the
data adapter.
470
*
*/
bdpDataAdapter1.Active = true;
/* This re-activates the data adapter so the refreshed data appears in the data grid. */
Self.bdpSelectCommand1.Close();
/* This closes the command to make sure that we will pass the parameter to */
/* the most current bdpSelectCommand.
Self.BdpDataAdapter1.Active := false;
/* This clears the data adapter so that we don't maintain old data
Self.bdpSelectCommand1.Parameters['emp'].Value := textBox1.Text;
/* This sets the parameter value to whatever value is in the text field.
*/
Self.BdpDataAdapter1.Active := true;
/* This re-activates the data adapter so the refreshed data appears in the data grid. */
If you have changed the names of any of these items, you need to update these commands to reflect the new
names.
3 Save your application.
This displays the employee number, first name, last name, and salary of the employee with that name in the data
grid. If there is more than one person with the same first name, the grid displays all occurrences of employees
with that name.
471
Warning:
If you choose to bind columns in this way, you must set the AutoGenerateColumns property
to False. Setting this property to True raises a runtime error, and does not allow the visible
restriction of columns at designtime. If the same column is bound to a grid more than once,
you may get a runtime error.
472
473
defined. Your associated BdpDataAdapter object must also be defined and must have the DataSet Active
property set to True.
This populates the Tables and Columns list boxes with data from the database.
2 Select a table from the Tables list box.
3 Select each column that you want to appear in your SQL statements.
As you select the column names, they appear in the SQL text box.
4 Select the check box next to each statement type you want to generate.
5 Click the Generate SQL button.
474
defined.
This populates the Tables and Columns list boxes with data from the database.
2 Select a table from the Tables list box.
3 Select each column that you want to appear in your SQL statements.
4 Select the check box next to each statement type you want to generate.
5 Click the Generate SQL button.
6 Edit the generated text if desired, or reselect different columns and click Generate SQL again.
7 Click OK.
Note:
Command components are automatically created as needed based on the selections in the
dialog.
475
dialog.
3 Click Add to display the Add New Connection dialog.
4 Select a provider from the Provider Name drop-down list box.
5 Enter a new name for the connection in the Connection Name text box.
6 Click OK.
7 Enter the appropriate values for your particular data source.
8 Click OK.
To remove a connection
1 Select the connection type until it is highlighted.
2 Click Remove.
To rename a connection
1 Right-click on the connection and choose Rename.
2 Type the new name of the connection.
3 Click OK.
476
To use DataSets
1 Generate a DataSet.
2 Add multiple tables to a DataSet.
3 Define primary keys for DataTables in the DataSet.
4 Define column properties for your DataSet columns.
5 Define constraints for your columns.
6 Define relationships between tables in your DataSet.
To generate a DataSet
1 From the Data Explorer, select a data source.
2 Drill down in the tree, then drag and drop the name of a table onto your Windows Form or Web Form.
This creates the BdpDataAdapter and BdpConnection for that data source and displays icons for those objects
in the Component Tray.
Note:
You can also drag a data source only onto the form, rather than a table, but in that case, Delphi
2005 creates only a connection object for you. You must still create and configure the
BdpDataAdapter object explicitly.
Inspector.
This displays the Data Adapter Configuration dialog.
5 If the SQL statement that is pre-filled on the dialog is acceptable, click the DataSet tab, otherwise, modify the
Tip:
You can accept the default name or change the name of the DataSet.
A DataSet icon appears in the Component Tray indicating that your DataSet has been created.
Note:
By reviewing the code for the DataSet in the Code Editor, you can see that the columns are
defined as generic dataColumns, whose columnName properties are assigned the value of the
column name from the database table. This differs from how a typed DataSet is constructed,
wherein the object name is constructed from the actual database column name, rather than
assigned as a property value.
477
or Web Form.
This creates the BdpDataAdapter for each table and one BdpConnection for that data source and displays icons
for those objects in the Component Tray.
3 Click the BdpDataAdapter icon (named bdpDataAdapter1, by default) to select it.
4 Click the Configure Data Adapter... designer verb in the Designer Verb area at the bottom of the Object
Inspector.
This displays the Data Adapter Configuration dialog.
5 If the SQL statement that is pre-filled on the dialog is acceptable, click the DataSet tab, otherwise, modify the
Tip:
You can accept the default name or change the name of the DataSet.
A DataSet icon appears in the Component Tray indicating that your DataSet has been created.
8 Repeat the Data Adapter configuration for each of the other data adapters, but select Existing Data Set on the
DataSet tab when generating the DataSets for all data adapters except the first one you configure.
This generates a DataTable for each data adapter and stores them all in one DataSet.
Note:
It is also possible to generate multiple DataSets, either one for each data adapter, or
combinations of DataTables.
This displays the Tables Collection Editor. If you have set all of the data adapters' Active properties to True,
the Tables Collection Editor will contain one member for each DataTable stored in the corresponding DataSet.
4 Select a table from the members list.
5 In the Primary Key field in the Table Properties, click on the DataColumn[] entry to display a pop-up list of column
names.
6 Click the gray check box next to the column name of the column or columns that comprise the Primary Key.
This displays the Columns Collection Editor for the selected column.
2 Set the property values for the individual columns.
478
This displays the Constraints Collection Editor for the selected column.
2 Click Add to add either a Unique Constraint or a Primary Key Constraint.
3 If you selected Unique Constraint, the Unique Constraint dialog appears. Select one or more of the displayed
column names. You can also select the Primary Key check box if you want to set the column as a primary key.
By setting the Unique Constraint on a column, you are enforcing the rule that all values in the column must be
unique. This is useful for columns that contain identification numbers, such as employee numbers, social security
numbers, part numbers, and so on.
Note:
If you have already defined a primary-foreign key relationship between two tables, you may not
be able to set a column as a primary key, based on the fact that it may already be set as the
primary key, or based on a conflict with another relationship.
4 If you selected Foreign Key Constraint, the Foreign Key Constraint dialog appears. Select the tables you want
to relate by choosing them from the Parent table and Child table drop down lists.
5 Click Key Columns to select the primary key column from the list.
6 Click Foreign Key Columns to select the foreign key column from the list.
Warning:
The primary key and foreign key columns must have the same data type and must contain
unique values. Columns that can contain duplicates are not good choices for primary or
foreign keys. It is common to choose the same column name from each table for your
primary-foreign key relationship.
already selected.
2 Click the ellipsis (...) button next to the Relations property in the Object Inspector.
child table.
Note:
Warning:
If you have already performed this procedure while setting constraints for your DataTables, you
may find that all of the appropriate values are already established.
The primary key and foreign key columns must have the same data type and must contain
unique values. Columns that can contain duplicates are not good choices for primary or
479
foreign keys. It is common to choose the same column name from each table for your
primary-foreign key relationship.
7 Click OK.
8 Repeat the process to define additional relations between the same DataTables.
480
This displays a BdpConnection icon and a BdpDataAdapter icon in the Component Tray.
3 Select the BdpDataAdapter.
4 Click the Configure Data Adapter... designer verb in the Designer Verb area beneath the Object Inspector.
Note:
Do not create a DataSet by selecting the DataSet tab in the Configure Data Adapter... dialog.
That tab applies only to standard DataSets.
7 Click the Generate Typed Dataset... designer verb in the Designer Verb area beneath the Object Inspector.
This creates an instance of the typed DataSet and displays an icon <DataSet Name>1 in the Component
Tray. For example, if your DataSet is DataSet1, the new instance will be named dataSet11. You will also see
that an XML .xsd file and a new program file appear in the Project Manager under your project.
table.
3 Click the (Collection) entry next to the TableStyles property.
Note:
By default the item is created as a Text Box Column. You can also expand the Add button and
select the BoolColumn if you want a boolean.
481
8 Click the MappingName property, select the column you want to display in your grid, then change any additional
properties you want, including the header name that will appear as the column header in the runtime grid.
9 Click OK twice.
Note:
When you build and run the application, only the columns that you explicitly defined by following
the steps in this procedure appear.
You can change data types, names, and anything else about the structure.
3 If you have the program code file (<dataset>.cs or <dataset>.pas) open in the Code Editor, close it now.
4 Choose Project
If you re-open the program code file, you will see that the file contains the changes you made to the XML in the .xsd file.
If you re-open the program code file, you will see that the InitClass() class now contains the new namespace.
482
New
Data Explorer to access the Data Explorer, and expand the Data Explorer Tree to expose
the providers and database tables you want to use.
You must have a live connection to expand provider nodes. If you do not have a live connection, you may need
to modify the connection string.
3 Drag and drop tables from one or more providers onto your form.
For each table you drag onto your form, a BdpConnection and a BdpDataAdapter appear in the component tray.
If you add multiple tables from the same provider, you can delete all but one BdpConnection for that provider.
4 Configure each BdpDataAdapter component.
There is no need to set the Active or DataSet properties, as the DataSet will be populated by the DataHub
component on the client-side.
5 Drag a DataSync component onto your form from the Borland Data Provider category of the Tool Palette, and
Description
Providers
Specifies a collection of DataProviders to use as data sources. Click the ellipsis button to open
the DataProvider Collection Editor, and add a DataProvider for each table you want to provide
and resolve.
CommitBehavior
Specifies the logic (Atomic, Individual, or ForceIndividual) for handling failures during resolving.
6 Drag a RemoteServer component onto your form from the Borland Data Provider category of the Tool
Palette, and configure the following RemoteServer properties in the Object Inspector:
483
Property
Description
DataSync
Specifies the DataSync that needs remoting. Select the DataSync from the drop-down list in the
Object Inspector.
AutoStart
Specifies whether or not to start the remote server automatically when the application runs. Set this
property to True.
ChannelType
Specifies the channel type: Http (HTTP) or Tcp (TCP/IP). Select the channel type from the dropdown list in the Object Inspector.
Port
Specifies the port the remote server will be listening on. Enter a new value, or accept the default
port value, 8000.
URI
Specifies the universal resource identifier for the remote server. By default, the URI property is the
same as the Name property.
7 Choose Run
New
DataSource property for the DataGrid to the name of the added DataSet component (for example, dataSet1).
4 Drag a RemoteConnection component onto your form from the Borland Data Provider category of the Tool
Palette, and configure the following RemoteConnection properties in the Object Inspector:
Property
Description
ProviderType
Specifies the type of provider published by the remote server. In this case, the property should be
set to Borland.Data.Provider.DataSync. If the remote server is running, you can select this value
from the drop-down list. Otherwise, you must enter the value.
ChannelType
Specifies the channel type: Http (HTTP) or Tcp (TCP/IP). Select the channel type from the dropdown list in the Object Inspector. This should match the setting for the remote server.
Host
Port
Specifies the port the remote server will be listening on. Enter a new value, or accept the default
port value, 8000. This should match the setting for the remote server.
URI
Specifies the universal resource identifier for the remote server. This should match the URI property
for the RemoteServer component in the remote server application.
5 Drag a DataHub component onto your form from the Borland Data Provider category of the Tool Palette, and
Description
DataPort
Specifies the data source. Set the DataPort property to the added RemoteConnection component (for
example, RemoteConnection1).
DataSet
Specifies the DataSet to hold the data retrieved from the specified data source. Set this property to the
added DataSet (for example, dataSet1).
6 Choose Run
Run.
484
485
Note:
New
Other
You can also use the separate DB Web Control Wizard for C#. It works identically to the wizard
described here.
This informs the wizard to add to the control file code that implements IDBWebDataLink. This interface defines
the means to access data source and table information.
4 Select Bind to DataColumn if you want to bind to a column, for instance, if your control supports a single type
of data.
This informs the wizard to add to the control file code that implements IDBWebColumnLink. This interface defines
the means to access a column in the table accessed by way of IDBWebDataLink.
5 If you select Bind to DataColumn and your control is one of the lookup controls, such as a listbox, radio button
group, or check box control, and you want the new control to be a lookup control also, check the Supports
Lookup check box.
This informs the wizard to add to the control file code that implements IDBWebLookupColumnLink. This interface
defines the means to access the lookup table, the text field and value field of the column accessed by way of
IDBWebColumnLink.
The DB Web Control Wizard creates a template file and displays it in the Code Editor. You then modify this file to
inherit from a specific DB Web control.
486
487
New
2 Select the New ECO Files category, or the ECO/C# Files category, depending on the type of project you have
open.
3 Double-click the ECO Enabled Windows Form icon.
The ECO Enabled Windows Form Wizard adds a new System.Windows.Forms.Form to your project.
The new class provides a constructor that takes an instance of the application's ECO Space. The constructor then
uses this instance to initialize the form's ECO Space property. An ECO-enabled form also provides a root handle
and the four extender providers described in the table above.
488
New
2 Select the New ECO Files category, or the ECO/C# Files category, depending on the type of project you have
open.
3 Double-click the ECO UML Package icon.
The ECO UML Package Wizard generates a source code file that contains the declarations necessary for the new
UML package.
You can populate the UML package with new classes, and draw the relationships between them on the Class
Diagram surface. As you design the classes, the IDE generates source code into the source file that contains the
ECO UML package. The generated source code will be adorned with the necessary ECO attributes to support the
services provided by the ECO framework.
Note: The default name convention for the new UML package is CoreClassesUnitX, where X is a progressively
increasing number. You can change the default UML package name in the Source Code Editor. You can
change the source code file name in the Project Manager window.
489
The referenced DLL will appear under the References node of the Model View window. You can view the diagrams
in the model and reference its classes and associations in your source code, but you will not be able to change the
model or draw associations from your classes to the classes contained in the ECO package DLL.
490
If you are using a datagrid that references the expression handle for which you created a new column, the new
column will appear in the designtime datagrid.
If the column you are adding is a relationship to another class, then the column is called a nesting. In this case, the
OCL expression you enter for the column will return an object or objects that have their own set of attributes (i.e.
columns). You must configure the nested columns using the Nesting Collection Editor.
Editor.
6 Click OK to close the Column Collection Editor.
7 Click the ellipsis button of the Nestings property.
8 Click Add to add a new nesting.
Enter the Name of the nesting; this is the name you entered in step 5, above.
9 Click the ellipsis button to open the Column Collection Editor.
Each nesting has its own collection of columns, which you must configure using the Column Collection Editor.
Note:
491
step in the EcoSpace property accessor for the ECO enabled windows form.
Add the line of code to the property accessor as shown:
492
return ecoSpace;
}
}
The vhLastNameVariableHandle will get its value from the searchString text box.
vhLastName.Element.AsObject = searchString.Text;
You can also use the vhLastNameVariableHandle as the text box's databinding property. Using this method to
connect the components, you do not need an event handler. You do however, need to make sure the VariableHandle
has been assigned a value; you can do this in the constructor for the form. Usually you would set the initial value to
an empty string.
created above.
3 Click the Expression property editor.
When the OCL evaluator encounters the expression in the ehAllPersonsExpressionHandle, it will look up the
variable named vLastName in the OclVariables component, and retrieve its value from the
493
494
New
Other):
ECO Windows Forms Application
ECO ASP.NET Web Application
ECO ASP.NET Web Service Application
Note:
The code templates are available for both Delphi for .NET, and C# projects.
2 Create or edit your model using the integrated UML class diagramming tools:
The Model View window shows a logical, namespace (and ECO UML package) oriented view of your project.
The Class diagram surface allows you to draw your classes and build relationships between them.
The Tool Palette contains the class diagramming elements (classes and relationships) that you drop on the
diagram surface.
The Object Inspector allows you to edit properties on the classes and relationships in your model.
3 If you will be using a relational database, you need to create a new, empty database using the vendor-supplied
tools.
4 Configure your application's ECO Space using the ECO Space designer.
The ECO Space contains the model, and the runtime instances of the classes in your model; it is a middle layer
between your application's front-end, and the persistence layer. The ECO Space design tab contains all the tools
to:
Configure the application's persistence settings (RDBMS or XML file).
Create or evolve the database schema.
Select the ECO UML packages from the model, that you wish to persist.
Validate the model.
Note:
The ECO space designer contains other tools as well as those listed above. For example, you
can upgrade an existing database from a previous release of Delphi or C#Builder. You can also
create a model by "reverse engineering" an existing relational database. See the links below
for more information on these topics.
You can connect data-aware .NET components on your forms to the objects in your ECO Space through element
handles such as ExpressionHandle. The ExpressionHandle component provides a way to retrieve objects from
the ECO Space using an OCL expression. The element handle components implement the interfaces required
to render their values in a data-aware component. You can use the OCL Expression Editor to enrich the
specification of your model by adding invariant constraints and derived attributes.
6 Evolve your application using ECO code templates in the New Items dialog box:
496
New
2 Select either the Delphi for .NET Projects category, or the C# Projects category.
3 Double-click the ECO Package in a DLL icon.
497
Please refer to the procedure Adding Columns and Nestings to an ECO Handle for more information.
2 In the Column Collection editor, set the EventDerivedValue property of the column to True.
3 Click OK to close the Column Collection editor.
4 Select the handle on the form designer.
5 Select the Events tab in the Object Inspector.
6 Add an event handler for the DeriveValue event.
There is one event handler for all the event derived columns in the handles column collection. In the event handler
code, you can examine the Name property of the DeriveEventArgs parameter to determine which column value is
being requested.
Pass the computed value of the column back in the ResultElement property of the DeriveEventArgs parameter.
498
open.
3 Double-click the ECO Space icon.
The ECO Space Wizard will generate a source code file that contains a new subclass of the DefaultEcoSpace class.
499
New
2 Select either the Delphi for .NET Projects category, or the C# Projects category.
3 Double-click the ECO Windows Forms Application icon. The New Application dialog box appears.
4 Type the name of your project, and use the ellipses button to locate to the folder where you want to place the
project files.
The ECO Windows Forms Application Wizard generates a new project containing the following files:
File
Description
CoreClassesUnit.pas
Contains the source code for the UML packages, interfaces, classes and their associations,
and all other types in your model.
<ProjectName>EcoSpace.pas
WinForm.pas
Contains source code for the application's ECO-enabled, main WinForm. The main form for
an ECO application provides a property that holds an instance of the application's ECO
Space. The form also contains code to automatically allocate the ECO Space.
Borland.Eco.Windows.Forms.dll The ECO Windows Forms Application Wizard automatically adds references to these
assemblies, and they must be distributed with your application along with all other
Borland.Eco.Handles.dll
referenced assemblies.
Borland.Eco.Interfaces.dll
Borland.Eco.Ocl.ParserCore.dll
Borland.Eco.Persistence.dll
The generated ECO enabled form also contains the following extender providers for buttons, listboxes, and grids:
Extender Provider
Purpose
EcoGlobalActions
Extends buttons with the EcoAction property. This property allows buttons to perform databaseoriented operations (such as Update, Undo, and Redo) without writing code.
EcoAutoForms
Extends grids and list boxes with the EcoAutoForm property. If the EcoAutoForm property is
set to True, double-clicking the grid or list box will open an automatically-generated form that
describes the selected object.
EcoListActions
Extends buttons with the CurrencyManager, and EcoListAction properties. The EcoListAction
property allows buttons to perform list-oriented operations (such as Add, Delete, MoveFirst, and
MoveNext) without writing code.
The CurrencyManager property must be set to the CurrencyManager used by the
ExpressionHandle that holds the list on which to operate.
EcoModelAwareDragDrop Extends grids and list boxes with the EcoDragSource and EcoDropTarget properties. If
EcoDragSource is set to true, you can drag objects from the listbox or grid. If EcoDropTarget
is set to True, you can drop an object onto the control. The object must conform to the target
500
list. For example, you can drag a Person object into a Customer list, but you cannot drag a
Person object into a City list.
501
New
The Persistence Mapper Provider wizard creates a source file called EcoPersistenceMapperProvider.pas (or .cs,
depending on the type of project you have open).
502
New
Other to open the New Items dialog box. for either Delphi for .NET or C#.
2 Select ECO ASP.NET Application for either Delphi for .NET or C#.
3 In the Name field, enter the name of your project.
4 In the Location field, accept the default path or enter another project path.
Tip:
Tip:
3 Click OK.
503
open.
2 Select the appropriate compiler settings in the Project Options dialog box.
Note:
3 Select Project
You must set the appropriate build settings on each project in your application's project group.
Build <Project Name> where <Project Name> is the actual name of your project to build your
application.
The build targets for each project in your application's project group will be generated per their own respective
project settings.
Referenced assemblies that have their Copy Local setting checked will be copied to the output directory of the
project that references them.
In addition to the other assemblies your project references, there are ECO-specific assemblies that must be deployed
with all ECO applications. The tables below show the ECO assemblies that are required, depending on the
deployment scenario.
ECO assemblies to be deployed in all cases
Borland.Eco.Core.dll
Borland.Eco.Handles.dll
Borland.Eco.Interfaces.dll
Borland.Eco.Ocl.ParserCore.dll
Borland.Delphi.dll
Note: this assembly is required even for C# projects.
Additional deployment scenarios
Scenario
Requirement
Borland.Eco.Persistence.dll
Borland.Eco.Windows.Forms
Borland.Eco.Web.dll
Borland.Data.Common, Borland.Data.Provider
You must also deploy the assemblies required for your particular database, such as
Borland.Data.Interbase.dll and bdpint20.dll, for InterBase.
The Delphi 2005 installer deploys these assemblies into the .NET Global Assembly Cache (GAC). The GAC cannot
be viewed or manipulated directly however, and copies of these files are kept with other shared assemblies in Delphi
2005's Common Files folder. The default path to this location is \Program Files\Common Files\Borland Shared\BDS
\Shared Assemblies\<version>, where <version> is the version number of Delphi 2005 that is installed on the
development machine.
504
On the end-user's machine, you can deploy the ECO assemblies into the GAC, or you can choose to deploy them
into the application's installation directory. If you will be deploying multiple ECO applications however, it's best to
deploy them as shared assemblies.
505
are computing the value of an attribute called fullName, the method signature would be:
compute the value of the attribute. To compute the value of a persons full name, you might write code such as
the following:
506
Now you must determine which elements need subscriptions. In the computation of the fullName attribute, you
used the firstName and lastName attributes of the Person class. Therefore, you must place subscriptions on
these two attributes so that the value of the fullName attribute will be reevaluated when a change occurs to either
a persons first or last name.
To place a subscription, call the method SubscribeToValue. This method is implemented by the framework for all
IElement types, so you must cast the object using the AsIObject method, and then you can place the subscription.
To place a subscription on an attribute, call the SubscribeToValue method in the DeriveAndSubscribe method,
as shown below ( SubscribeToValue is a method of the IElement interface).
Notice the Properties of an IObject can be indexed using the name of the attribute you are interested in.
Also notice that in this example, you only need a reevaluate subscription, since you are interested only in the atomic
values, firstName and lastName.
507
following:
using System;
using Borland.Eco.Subscription;
private class MySubscribingClass {
private class MySubscriberAdapter : SubscriberAdapterBase {
// Notice the actual subscriber class (MySubscribingClass) is
// passed on to the SubscriberAdapterBase class.
public MySubscriberAdapter(object subscriber) : base(subscriber)
{ }
}
}
2 Implement the DoReceive method in the utility class.
sender,EventArgs e) {
In the example code, the method ResondToEvent is implemented in the outer class, MySubscriberClass.
3 Implement in the RespondToEvent method in the outer class.
508
adapter, and a new method is defined to place the subscription. This example shows how to use the
IExtentService interface to receive subscription events whenever objects of a given class are created.
509
// Place a subscription
extentService.SubscribeToObjectAdded(myAdapter, subscribeToClass);
}
}
The DoReceive method of MySubscriberAdapter will be called whenever new objects of the type passed to
SubscribeToObject are created.
510
New
application.
7 In the Project Manager, rename CoreClassesUnit1.pas to CameraClassesUnit.pas.
Note:
The default source file name is CoreClassesUnit<n> where <n> is an increasing integer.
This will rename the source file and the unit generated by the ECO UML Package wizard.
8 In the Model View Window, rename CoreClasses1 to CameraClasses.
Note:
The default UML package name is CoreClasses<n> where <n> is an increasing integer.
Note:
In the CameraClassesUnit file, you may need to change the line [assembly:
RuntimeRequiredAttribute(TypeOf(CoreClasses))] to be [assembly:
RuntimeRequiredAttribute(TypeOf(CameraClasses))] to reflect the new name of
the core classes unit.
You now have a basic ECO application, with one additional UML package called CameraClasses. Using the Project
Manager, open the source file defining the application's ECO Space and examine the generated code (by default
the ECO Space source file is named ECOPhotoDBEcoSpace.pas). Notice the following aspects of the ECO Space:
511
uses
System.Threading,
Borland.Eco.Services,
Borland.Eco.UmlCodeAttributes,
Borland.Eco.Handles,
CoreClassesUnit;
The CameraClassesUnit is not yet present in the uses clause. This is because the CameraClasses UML
package has not yet been added to the ECO Space. This will be done below, when we configure the ECO Space.
Notice the properties in the TECOPhotoDBEcoSpace class:
TECOPhotoDBEcoSpace = class(Borland.Eco.Handles.DefaultEcoSpace)
private
procedure InitializeComponent;
class var typeSystemProvider: ITypeSystemService;
strict protected
function GetTypeSystemProvider: ITypeSystemService; override;
public
constructor Create;
class function GetTypeSystemService: ITypeSystemService; static;
procedure UpdateDatabase;
function get_PersistenceService: IPersistenceService;
property PersistenceService: IPersistenceService read get_PersistenceService;
function get_DirtyListService: IDirtyListService;
property DirtyListService: IDirtyListService read get_DirtyListService;
function get_UndoService: IUndoService;
property UndoService: IUndoService read get_UndoService;
function get_TypeSystemService: ITypeSystemService;
property TypeSystemService: ITypeSystemService read get_TypeSystemService;
function get_OclService: IOclService;
property OclService: IOclService read get_OclService;
function get_ObjectFactoryService: IObjectFactoryService;
property ObjectFactoryService: IObjectFactoryService read get_ObjectFactoryService;
function get_VariableFactoryService: IVariableFactoryService;
property VariableFactoryService: IVariableFactoryService read
get_VariableFactoryService;
end;
The ECO Space contains read-only properties that allow you to access each of the services provided by the ECO
framework.
Continuing with this example, we will now add classes and attributes to the CameraClasses UML package.
Class.
When you select a class in the Model View window, you can set properties on that class in the Object Inspector.
512
3 With each new attribute selected in the Model View window, change the properties as shown in the table, using
Name
Type
InitialValue
Attribute_1
ModelName
String
Nikon
Attribute_2
SerialNumber
String
0001
Attribute_3
DateAcquired
DateTime
01/01/2004
4 Repeat steps 1 through 3, adding the following attributes to the FilmRoll class and setting their properties as
Name
Type
InitialValue
Notes
Attribute_1
Process
String
E6
Attribute_2
Speed
Integer
125
5 Repeat steps 1 through 3 above, adding the following attributes to the Photo class and setting their properties
Name
Type
InitialValue
Notes
Attribute_1
DateTaken
DateTime
01/01/2004
NA
Attribute_2
Exposure
String
125
Attribute_3
fStop
String
2.8
Note: You will see the effect of setting an initial value for your attributes when you build a user interface.
To finish this part of the example, you will configure the application's ECO Space to persist data to an XML file.
Finally, you will add the CameraClasses UML package to the ECO Space.
513
Note:
If you have not yet recompiled the application, Delphi 2005 will prompt you to rebuild at this
time. Click Yes in the message box to rebuild.
After rebuilding, the Select UML Packages for the ECO Space dialog box will appear. The Available UML
Packages list shows those UML packages that have not yet been selected in the ECO Space. In this list you
should see the CameraClasses package added above.
2 Select the CameraClasses package and click the Add (>) button.
The CameraClasses package will be moved into the Selected UML Packages list.
3 Save all files and rebuild the application.
After adding the CameraClasses UML package, we can re-examine the source code in the
ECOPhotoDBEcoSpace.pas file. Notice the uses clause now:
uses
Borland.Eco.Services,
Borland.Eco.UmlCodeAttributes,
Borland.Eco.Handles,
CoreClassesUnit, CameraClassesUnit, Borland.Eco.Persistence;
Adding the CameraClasses package has resulted in its unit being added to the uses clause. Adding the
PersistenceMapperXml component caused the Borland.Eco.Persistence namespace to be added.
Also note the InitializeComponent method has been filled out to automatically create the persistence mapper,
set its properties, and link it to the ECO Space:
procedure TECOPhotoDBEcoSpace.InitializeComponent;
begin
Self.PersistenceMapperXml1 := Borland.Eco.Persistence.PersistenceMapperXml.Create;
//
// PersistenceMapperXml1
//
Self.PersistenceMapperXml1.CacheData := False;
Self.PersistenceMapperXml1.FileName := 'ECOPhotoDB.xml';
//
// TECOPhotoDBEcoSpace
//
Self.PersistenceMapper := Self.PersistenceMapperXml1;
end;
514
) to open the class diagram for the package. The diagram displays the
three classes added in Part 1 of this tutorial.
Note:
You do not need to click and drag (hold the mouse button down). A single click of the mouse
button "drops" one end of the association on the Camera class.
This completes both ends of the association between Camera and FilmRoll.
Tip:
You can reposition the classes on the diagram to make the text on the association ends easier
to see.
You now have an association between a Camera, and a FilmRoll. Next, you need to set properties on that
association, to further define the relationship between the two classes. Specifically, you need to designate that a
Camera can shoot zero or many rolls of film in its lifetime.
The association will become active, and you can set properties in the Object Inspector.
2 Type CameraFilmRoll in the association's Name property.
3 Expand the AssociationEnds category in the Object Inspector.
Notice there are two items, End1, and End2, one for each end of the association.
4 Set the Multiplicity property of End1 to 1, indicating that a roll of film is shot by exactly one camera.
5 Set the Multiplicity of End2 to 0..*, indicating the zero-to-many relationship between a camera and a roll of film.
Adding an association between classes as described in the steps above, introduces new properties on the classes
involved in the association. In this case, examining the generated source code for our Camera and FilmRoll
classes, you now find one additional property on each:
515
Since you have set the multiplicity on the FilmRoll association end to 0..*, Delphi 2005 has automatically generated
code to allow us to access the FilmRoll property as a list. In source code, you could add a new roll of film to a
camera with code such as the following:
...
var
F: FilmRoll;
C: Camera;
begin
// Note that EcoSpace is a property of an ECO-enabled Windows Form.
C := Camera.Create(EcoSpace);
F := FilmRoll.Create(EcoSpace);
...
C.FilmRoll.Add(F);
// Add a roll of film to the collection
F.Camera := C;
// Set the Camera attribute on the FilmRoll
...
Note: This source code is demonstrating how to work with classes and set their attributes; you do not need to insert
this code in this tutorial.
Similarly you can remove a FilmRoll object with the following code:
...
var
// Assuming C and F declared as above...
currentFilmRoll: FilmRoll;
begin
C.FilmRoll.RemoveAt(3);
// Remove a specific item at index 3 in the list, or..
C.FilmRoll.Remove(currentFilmRoll); // Remove a specific roll of film..
end;
Finally, you can access individual FilmRoll objects within the list with the code:
...
var
// Assuming C and F declared as above...
i: Integer;
begin
for i := 0 to C.FilmRoll.Count do
begin
F := C.FilmRoll[i];
// Do something with this roll of film...
end;
end;
You will finish by adding the association between a FilmRoll class and a Photo class.
516
517
One button will be used to add a new Camera object, one will be used to delete Camera objects, and one will
be for updating the database.
Do not set the caption for the buttons yet; the ECO extender providers can do this automatically.
5 In the Tool Palette, scroll to the Data Controls category, and drag a DataGrid onto the Windows Form.
6 In the Tool Palette, scroll to the Enterprise Core Objects category, and drag a CurrencyManagerHandle
The next step is to configure the AllCamerasExpressionHandle component. You must connect the component
to the form's ReferenceHandle (rhRoot), and enter an OCL expression that will fetch all Cameras in the database.
All OCL expressions exist within a certain context, which is displayed in the title of the OCL Expression Editor
window. The OCL Expression Editor allows you to build an OCL expression by selecting from items (for example,
the classes in your model), and operations that are valid in the current context. You can also type an OCL expression
by hand in the editor control. The OCL expression is validated as you build it, so you always know you are working
with a valid expression.
When you first open the OCL Expression Editor, the current context is the model itself, so the list in the right side
of the window shows all the classes you have currently defined in our model.
Tip: If the OCL Expression Editor does not display the classes previously defined, make sure you have added
the UML package containing the classes to the application's ECO Space.
518
Now we can connect the user interface controls to the ECO components. We will first connect the DataGrid to the
AllCamerasExpressionHandle, and finally, we will use the ECO extender providers to perform the basic
operations of adding, deleting, and updating the database without writing a single line of code.
Run.
When you run this application and click the Add button, a new row is added to the DataGrid. Notice that each column
contains the values we entered in the InitialValue property for the corresponding attribute when we built the
model.
To delete a row from the grid, select it and click the Delete button. A message box asks you to confirm the deletion.
The deletion message can be customized using the extender provider properties within the IDE.
Finally, after experimenting with adding and deleting rows, click the Update button to save the changes. The next
time you run the application, the DataGrid will automatically load with the last set of saved data.
519
ExpressionHandle to LinkFilmRollsExpressionHandle.
5 Set the RootHandle property to the CameraCurrencyManager component that was added in Part 3 of this
tutorial.
6 Set the Expression property to self.FilmRoll.
7 Select the second DataGrid and set its DataSource property to the LinkFilmRollsExpressionHandle.
8 Save all files and rebuild the project.
The two grids are now linked through the CameraCurrencyManager and the
LinkFilmRollsExpressionHandle. To understand how the OCL expression of the
LinkFilmRollsExpressionHandle works, remember that its RootHandle is set to the
CameraCurrencyManager. The CameraCurrencyManager in turn is linked to an OCL expression that results in
a list of all Camera objects. Therefore, the context of the LinkFilmRollsExpressionHandle OCL expression
is an individual Camera object. In a sense, the OCL we build in LinkFilmRollsExpressionHandle is continuing
the one in the AllCamerasExpressionHandle. Notice the title of the OCL Expression Editor window reflects
the current context. So, the OCL expression self.FilmRoll is accessing the FilmRoll attribute of an individual
Camera.
In order to see the two linked grids in action, you need to do some more work on the user interface. You need buttons
to add and remove FilmRoll objects. You will again use the ECO extender providers, which means you need to
add another CurrencyManagerHandle to the form.
Now you have a working link between the two grids. Assuming you have entered some Camera objects, you can
create FilmRoll objects and demonstrate the link.
A new row is added to the grid. Notice the initial values for the columns are the values you entered on the class
diagram when you built the model.
3 Click the Add button again, and this time change the Process column to the string "C41", and the Speed column
to 400.
The next logical step is to add a third grid to display individual Photo objects. To accomplish this you would
essentially repeat the procedure above in its entirety, adding a grid, buttons, an ExpressionHandle, and another
CurrencyManagerHandle to link the FilmRoll grid to the Photo grid.
In the IDE, activate the Windows form designer. Notice that the FilmRoll grid contains a column representing the
Camera attribute (which comes from the CameraFilmRoll association created in Part 1 of this tutorial). This column
is not really necessary, since you can see the selected camera in the Camera grid. To remove this column, you need
to add a DataGridColumnStyle object to the grid, and then add the columns you wish to display. You then map these
grid columns to the attributes of the FilmRoll class.
To understand how this mapping works, remember the DataSource of the FilmRoll grid is
LinkFilmRollsExpressionHandle. The OCL expression of LinkFilmRollsExpressionHandle is
self.FilmRoll. This allows you to map columns in the grid directly to the attributes of the FilmRoll class.
The FilmRoll grid now displays two columns, Process and Speed. The data for these columns is mapped to
the grid's data source (LinkFilmRollsExpressionHandle).
521
click the Wrap Existing Database with ECO button on the ECO space designer toolbar.
Two new files will be generated and added to the project: DatabaseNameCoreClasses.pas (or .cs), and
DatabaseName.xml. DatabaseName is the name of the database the persistence components are connected
to. The type of source file produced depends on the type of project, either C# or Delphi.
2 Drop a FileMappingProvider component onto the ECO space designer.
3 In the FileMappingProvider component's FileName property, enter the name of the XML file produced in step 1.
Note:
The XML mapping file must be distributed with the application. The path to this file is relative
to the executable file.
created in step 2.
6 Click the Select Packages button on the ECO space designer toolbar.
The new ECO UML packages generated in the source file are seen in the Model View window. You can open the
class diagram and make changes as you would if you had created the package yourself.
If the generated model and XML mapping do not accurately match the database schema, then you must make
modifications to fine tune them. These changes must be made on the class diagram surface and in the XML mapping
file.
522
After you make changes to the custom OR mapping file, there are two ways to evolve the database schema. You
(or your database administrator) can make the changes to the schema manually, using the appropriate database
tools supplied by your vendor. Or, you can use the database evolution tool on the ECO Space designer. If you use
the ECO Space designer, you must set the old and new OR mapping provider properties of the persistence mapper
component, as shown below.
2.
6 Set the NewMappingProvider property of the persistence mapper to the same FileMappingProvider as the
RuntimeMappingProvider property.
Tip: If you make changes to the database schema manually, you only need to set the RuntimeMappingProvider
property of the persistence mapper.
523
Here you will add the required persistence components to the ECO Space, and then configure the ECO Space
to use the chosen persistence method by setting properties in the Object Inspector.
3 Validate the Model.
This will cause the IDE to perform a number of checks to make sure the model is well-formed. For example, OCL
expressions are checked to make sure they are valid.
4 If you are using an RDBMS, create an empty database.
The exact procedure will vary depending on your database vendor. This procedure will use an InterBase database
as an example.
5 Add a connection handle component to the ECO Space and configure its connection string.
6 If you are using an RDBMS and you are starting from scratch, create the initial database schema by clicking on
the Create Database Schema button. Otherwise, if you have made changes to the model, or you want to add
or remove a UML package, you can use the Evolve Db button to update the existing database schema.
Note: When a class or attribute is deleted and the database schema evolved, the corresponding columns are
removed from the database and the data is lost. The IDE will warn you if this is the case, giving you a chance
to cancel the operation.
2 A full list of available UML packages is shown in the Available Packages list box.
UML packages that are already managed in the ECO Space are shown in the Selected Packages list box.
3 To add a single UML package, select it in the list, and click the left arrow button.
To add all available packages, click the double left arrow button.
524
4 To remove a single package from the ECO Space, select the package in the Selected Packages list, and click
It is often useful to store your objects in an XML file during initial development and prototyping,
and then switch to a relational database as your model becomes more stable.
2 Drag the appropriate persistence mapper from the Tool Palette to the ECO Space designer surface.
3 Click on an empty part of the ECO Space designer surface, so that the Object Inspector is showing the
Note: When using the PersistenceMapperXML component, it is not necessary to create or evolve a database
schema as described below. When persisting to an XML file, all that is required is to select the component
on the ECO Space designer, and set its FileName property.
Under the Databases node you will see a list of databases that reside on that server.
3 Right-click the Databases node, and choose Create Database.
4 Type the path and file name of the database.
The new database will be displayed under the Databases node in the IBConsole window.
6 Return to Delphi 2005 and add a connection handle. Connect the persistence mapper's Connection property to
Note:
If you are using a SQL Server persistence handle, locate the Data Components category.
2 Drag a BdpConnection (or SqlConnection) component onto the ECO Space designer surface.
3 Select the persistence mapper component on the designer surface.
525
4 Set the Connection property of the persistence mapper component to the connection handle you created in step 2.
5 Set the default vendor-specific configuration settings of the persistence mapper by right-clicking the persistence
mapper component, and choosing the appropriate item from the context menu.
For example, to set the default settings for an InterBase database, select InterBase
context menu.
6 Right-click the connection handle component and select Connection Editor. The ConnectionString will vary
depending on the database vendor. For an InterBase database, you will need to edit the connection string to
reflect the correct path to the database file. Default, vendor-specific connection strings are available both from
the Connections Editor dialog, and from the ConnectionString property's drop-down list in the Object Inspector.
If you are creating an ECO application from scratch, click the Create Database Schema button (
) on the
designer. This will create the tables necessary to support the classes and relationships you have created in the
model.
2 If you are working with an existing ECO application and you have made changes to the model, click the Evolve
Database button (
) on the designer.
Note: During creation or evolution of the database schema, the ECO tab in the message pane will display status
messages and results of the operation.
526
Class.
4 Name the class and add any attributes or operations you want.
5 Select the Project Manager tab, and open a source file containing a WinForm.
6 From the Tool Palette, drag an ExpressionHandle onto the form.
7 Select the root handle component (rhRoot).
8 Click the EcoSpaceType field in the Object Inspector and set the value to the name of an ECO Space.
9 Select the ExpressionHandle and double-click the Expression field in the Object Inspector, or click the
ellipses button.
The OCL Expression Editor appears.
Note:
A list of valid types and OCL operations appears in the right-hand pane. The list is constructed
based on the current context of the expression. The list changes as you build the expression;
it is always based on the context of the expression currently displayed in the left-hand pane of
the dialog box. If the list does not appear, make sure that the EcoSpaceType property for the
root handle is set to a valid ECO Space.
2 If you want to view the data types for the attributes of the class whose name you typed, check the Show
Note: As you add items to the expression, ECO automatically parses the expression to make sure it is valid. Keep
in mind that the expression may be valid without being logically correct.
Expression field.
This displays the OCL Expression Editor.
4 Construct your expression by double-clicking elements from the right-hand pane until you are satisfied with the
results.
5 Click OK.
6 Click OK to close the Column Collection Editor.
At runtime, this adds a new column to any data grid component that is linked to the expression handle.
528
PersistenceMapperProvider.
2 Click the Editor tab for the PersistenceMapperProvider source file, and select the Design tab.
3 Select one of the following PersistenceMapper components from the Tool Palette, and drop it onto the designer:
PersistenceMapperBdp
PersistenceMapperSqlServer
4 Click on an empty part of the PersistenceMapperProvider design surface, so that the Object Inspector is
PersistenceMapperProvider.
Note:
You must compile the application before the ECO space will appear in the dropdown list.
Note:
If you are using a SQL Server persistence handle, locate the Data Components category.
2 Drag a BdpConnection (or SqlConnection) component onto the PersistenceMapperProvider designer surface.
3 Select the persistence mapper component on the designer surface.
4 Set the Connection property of the persistence mapper component to the connection handle you created in step 2.
5 Set the default vendor-specific configuration settings of the persistence mapper by right-clicking the persistence
mapper component, and choosing the appropriate item from the context menu.
For example, to set the default settings for an InterBase database, select InterBase
context menu.
6 Right-click the connection handle component and select Connection Editor. The ConnectionString will vary
depending on the database vendor. For an InterBase database, you will need to edit the connection string to
reflect the correct path to the database file. Default, vendor-specific connection strings are available both from
the Connections Editor dialog, and from the ConnectionString property's drop-down list in the Object Inspector.
529
If you are creating an ECO application from scratch, click the Create Database Schema button (
) on the
designer. This will create the tables necessary to support the classes and relationships you have created in the
model.
2 If you are working with an existing ECO application and you have made changes to the model, click the Evolve
Database button (
) on the designer.
Note: During creation or evolution of the database schema, the ECO tab in the message pane will display status
messages and results of the operation.
Once the PersistenceMapperProvider is configured, you must decide if your ECO spaces will be running all
in a single process, or across processes. If all ECO space instances are within a single process, you will use a
PersistenceMapperSharer component to link the ECO space to the PersistenceMapperProvider. If ECO
spaces will be created in different processes, you will use a PersistenceMapperClient component.
Before continuing, save and compile your project.
PersistenceMapperProvider.
5 Click on an empty part of the ECO Space design surface, so the Object Inspector shows the properties of the
in step 3.
5 Click on an empty part of the ECO Space design surface, so the Object Inspector shows the properties of the
in step 3.
530
Reporting
531
New
Other.
532
This displays the currently installed ActiveX components on your local system.
3 Select the check box item next to each ActiveX control you want to appear in the Tool Palette.
4 Click OK.
This initiates a Delphi 2005 operation which returns the ActiveX component selection to its original settings.
2 Click OK.
533
To modify a report
1 Choose File
Open.
Tip:
You can drag-and-drop fields from the left-hand frame of the Report Designer to the various
sections on the report.
534
New
Other.
If you chose a blank report, the Main Report appears in the Designer frame, otherwise, the Crystal Report
Gallery displays the appropriate Crystal Report Expert for the report type you selected.
535
536
537
New
Other
box.
5 On the Provider page of the dialog, select Microsoft Jet 4.0 OLE DB Provider; then click the Next button to
ADOConnection1.
3 Set the CommandText to an SQL command, for example, Select * from orders.
You can either type the Select statement in the Object Inspector or click the ellipsis button to the right of
CommandText to display the Command Text Editor where you can build your own query statement.
Tip:
If you need additional help while using the CommandText Editor, click the Help button or press
F1.
538
A data source connects the client dataset with data-aware controls. Each data-aware control must be associated
with a data source component to have data to display and manipulate. Similarly, all datasets must be associated
with a data source component for their data to be displayed and manipulated in data-aware controls on the form.
Run.
539
New
Database field.
By default, the file is located in C:\Program Files\Common Files\Borland Shared\Data.
6 Accept the value in the User_Name field (sysdba) and Password field (masterkey).
7 To test the connection, click the button with the checkmark on it (just above the Connection Name list).
Note:
By default, you are prompted to log in. Use the masterkey password. If the connection works
a confirmation message appears. If you cannot connect to the database, make sure you have
installed Interbase and that the server is started.
For the SQL command, you can either type a Select statement in the Object Inspector or click the ellipsis to the
right of CommandText to display the Command Text Editor where you can build your own query statement.
Tip:
If you need additional help while using the Command Text Editor, click the Help button or press
F1.
4 In the Object Inspector, set the Active property to True to open the dataset.
540
2 In the Object Inspector, set the DataSet property drop-down list to SQLDataSet1.
A data source connects the client dataset with data-aware controls. Each data-aware control must be associated
with a data source component to have data to display and manipulate. Similarly, all datasets must be associated
with a data source component for their data to be displayed and manipulated in data-aware controls on the form.
Run.
541
New
Other.
542
New
Other
the form.
To add the cut and paste actions to the edit category in the main menu
1 Double-click MainMenu1 on the form.
The MainMenu1 Editor displays with the first blank command category selected.
2 In the Object Inspector, enter Edit for the Caption property and press ENTER.
Linkage category.
6 If not already filled in, expand the list of Action properties, enter Cut for the Caption property, enter Edit for the
7 In the MainMenu1 Editor, click the second blank action beneath Cut to select it.
8 In the Object Inspector, select EditPaste from the drop-down list of actions in the Action property, located in
Run.
The application executes, displaying a form with the main menu bar and the Edit menu.
3 In the application, select text in the memo.
4 Choose Edit
Cut.
Paste.
544
New
Other
The Code Editor appears, with the cursor in the TForm1.Button1Click event handler block.
3 Place the cursor before the begin reserved word and then press Return.
545
New
Other
2 From the Additional page of the Tool Palette, add a TActionManager component to the form.
3 Double-click the TActionManager component to display the Action Manager editor.
Tip:
Run.
The application executes, displaying a form with the main menu bar and the File menu.
2 Select File
Open.
546
547
2 Choose File
New
Other.
3 In the New Items dialog box, select Delphi for .NET Projects.
4 Double-click VCL Forms Application.
The code displays with the cursor in the TForm1.Button1Click event handler block.
3 Enter the following code to display the stock price for the first child node when the Borland button is clicked:
BorlandStock:=XMLDocument1.DocumentElement.ChildNodes[0];
Price:= BorlandStock.ChildNodes['price'].Text;
Memo1.Text := Price;
4 Add a var section just above the code block, above the begin statement in the event handler, and enter the
The code displays with the cursor in the TForm1.Button2Click event handler block.
7 Enter the following code to display the stock price for the second child node when the MyCompany button is
clicked:
MyCompany:=XMLDocument1.DocumentElement.ChildNodes[1];
Price:= MyCompany.ChildNodes['price'].Text;
Memo1.Text := Price;
548
8 Add a var section just above the code block, above the begin statement in the event handler, and enter the
549
New
2 Choose Component
Other
This displays the first page of the New VCL Component wizard.
3 Select VCL for Delphi.NET.
4 Click Next.
This displays the second page of the New VCL Component wizard and loads the page with ancestor
components.
5 Select an ancestor component from the list.
6 Click Next.
This displays the third page of the New VCL Component wizard.
textbox.
3 Enter the unit name in the Unit Name textbox.
4 Enter the search path in the Search Path textbox.
5 Click Next.
Note:
To create a unit
1 Select the Create Unit radio button.
2 Click Finish.
550
551
New
Other
The Code Editor displays with the cursor in the TForm1.Button1Click event handler block.
2 Enter the following event handling code, replacing MyFile.bmp with the name of the bitmap image in your project
directory:
Rect := TRect.Create(0,0,100,100);
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile('MyFile.bmp');
Form1.Canvas.Brush.Bitmap := Bitmap;
Form1.Canvas.FillRect(Rect);
finally
Form1.Canvas.Brush.Bitmap := nil;
Bitmap.Free;
end;
Tip:
You can change the size of the rectangle to be displayed by adjusting the Rect parameter values.
552
Run.
3 Click the button to display the image bitmap in a 100 x 100-pixel rectangle in the upper left corner of the form.
553
New
Other
2 In the Object Inspector, click the Design tab, if necessary, to display Form1.
The Code Editor displays with the cursor in the TForm1.FormPaint event handler block.
3 Enter the following event handling code:
Canvas.Rectangle (0, 0, ClientWidth div 2, ClientHeight div 2);
Canvas.Ellipse (0, 0, ClientWidth div 2, ClientHeight div 2);
Run.
3 The application executes, displaying a rectangle in the upper left quadrant, with an ellipse in the middle of the
rectangle.
554
New
Other
2 In the Designer, click the form, if necessary, to display Form1 properties in the Object Inspector.
The Code Editor displays with the cursor in the TForm1.FormPaint event handler block.
3 Enter the following event handling code:
Canvas.RoundRect(0, 0, ClientWidth div 2,
ClientHeight div 2, 10, 10);
Run.
3 The application executes, displaying a rounded rectangle in the upper left quadrant of the form.
555
New
Other
2 Click the Design tab, if necessary, to display Form1 properties in the Object Inspector.
The Code Editor displays with the cursor in the TForm1.FormPaint event handler block.
3 Enter the following event handling code:
with Canvas do
begin
MoveTo(0,0);
LineTo(ClientWidth, ClientHeight);
MoveTo(0, ClientHeight);
LineTo(ClientWidth, 0);
end;
Run.
The application executes, displaying a form with two diagonal crossing lines.
Tip:
To change the color of the pen to green, insert this statement following the first MoveTo()
statement in the event handler code: Pen.Color := clRed; Experiment using other canvas
and pen object properties.
556
New
Other
The Code Editor displays with cursor in the code block of the DrawItem event handler.
4 Enter the following code for the event handler:
Combobox1.Canvas.FillRect(rect);
ImageList1.Draw(ComboBox1.Canvas, Rect.Left, Rect.Top, Index);
557
Combobox1.Canvas.TextOut(Rect.Left+ImageList1.Width+2,
Rect.Top, ComboBox1.Items[Index]);
Run.
The bitmap image and the text string display as a list item.
558
New
Other
This displays the second page of the WinForm Control Import Wizard and lists all of the available components.
4 Check the check boxes next to the components you want to import.
Note:
If you want to import all components, click the Check All button.
5 Click Next.
This displays the third page of the Wizard, which provides generation options for the units.
6 Accept the defaults, and click Next.
This displays the fourth page of the Wizard, which allows you to set a location and a name for the package file.
7 Click Next.
This displays the fifth page of the Wizard, which allows you to overwrite any existing files of the same name.
8 Click Next.
This initiates the generation process and displays status messages for each file as it is created, including the
package (.dpk) file.
9 If you want to import additional controls, click New. Otherwise, click Finish.
Build <Project Name> from the main menu where <Project Name> is the name of your
project.
This creates the assembly file containing the package and the units.
3 Choose Components
The location depends on your project options directory locations. The file might also end up in your default
documents directory.
7 Click OK.
The individual controls appear in the Tool Palette under the WinForm Controls category. You can now add the
individual controls to your VCL.NET form applications.
560
Web Services
561
New
Other.
application.
For this example, we will create a Windows Forms application (either Delphi for .NET or C#).
3 Click OK.
2 From the Borland UDDI Browser web dialog box, enter the following URL in the address text box at the top:
http://localhost/WebService1/WebService1.asmx
Note:
The name of your application may not be WebService1. In that case, use your own application
name in place of WebService1 in the example preceding example.
3 Press Enter.
Note:
If you need to determine the proper path and you are using IIS, you can open the IIS
Administrator from the Windows XP Control Panel Administrative Tools. Find the WebService
you have saved and compiled in the list of IIS web sites, then review the name of the site and
the name of the .asmx file.
If you have entered the proper path, this should display information about the WebMethods.
4 Click the Service Description link to view the WSDL document.
5 Click Add Reference to add the WSDL document to the client application.
A Web References folder is added to the Project directory in the Project Manager which contains the
WebService1.wsdl file and the dialog box disappears.
562
When you added the Web Reference to your application, Delphi 2005 used the WSDL to generate a proxy class
representing the "Hello World" web service. The Click event uses methods from the proxy class to access the
web service. For a Delphi for .NET client, you may need to add the unit name of the proxy class (for example,
localhost.WebService1) to the uses clause of your Windows Form unit to prevent errors in your Click event.
4 For a C# client, implement the Click event in the Code Editor with the following code:
563
New
Other.
categories.
The New ASP.NET Application dialog box appears.
3 In the Name field, enter a name for your project.
4 In the Location field, enter a path for your project.
Tip:
5 If necessary, click the View Server Options button to change your Web server settings.
Tip:
The default Server Options will usually be sufficient, so this step is optional.
6 Click OK.
The Button control appears on the Designer. Make sure the control is selected.
3 In Object Inspector, set the Text property to Dead or Alive?.
4 From the Web Controls category of the Tool Palette, place a TextBox component onto the Designer above the
Button.
This is where you type your query to the Web Service.
5 Place a Label component below the Button.
This is where the results of the web service query are displayed.
Use the UDDI browser to locate the DeadOrAlive Web Service on the internet. This allows you to use the methods
and objects published by the Web Service Definition Language (WSDL).
2 In the Borland UDDI Browser web dialog box, click the XMethods Full link in the list of available UDDI
directories.
A list of various web services published on the XMethods Web site appears.
564
Tip:
You can use Ctrl+F to search within the Borland UDDI Browser.
A WSDL document appears. This XML document describes the interface to the DeadOrAliveWS web service.
5 Click Add Reference to add the WSDL document to the client application.
A Web References folder containing a com.abundanttech.www node is added to the Project directory in the
Project Manager.
code :
procedure TWebForm1.Button1_Click(sender: System.Object; e: System.EventArgs);
var
result: DataSet;
ws: DeadOrAlive;
currentTable: DataTable;
currentRow: DataRow;
currentCol: DataColumn;
begin
//This initializes the web service
ws := DeadOrAlive.Create;
//Send input to the web service
result := ws.getDeadOrAlive(TextBox1.Text);
//parse results and display them
Label1.Text := '';
for currentTable in result.Tables do
begin
Label1.Text := Label1.Text + '<p>' + #13#10;
for currentRow in currentTable.Rows do
begin
for currentCol in currentTable.Columns do
begin
Label1.Text := Label1.Text + currentCol.ColumnName + ': ';
Label1.Text := Label1.Text + (currentRow[currentCol]).ToString;
Label1.Text := Label1.Text + '<br>' + #13#10;
end;
end;
Label1.Text := Label1.Text + '</p>';
end;
end;
When you added the Web Reference to your application, Delphi 2005 used the WSDL to generate a proxy class
representing the "Hello World" web service. The Click event uses methods from the proxy class to access the
565
web service. For Delphi for .NET Web Services, you may need to add the unit name of the proxy class,
abundanttech.deadoralive, to the uses clause of your Web Form unit to prevent errors in your Click event.
4 For a C# Web Services application, implement the Click event in the Code Editor with the following code :
private void button1_Click(object sender, System.EventArgs e)
{
DataSet result;
//This initializes the web service
DeadOrAlive source = new DeadOrAlive();
//Send input to the web service
result = source.getDeadOrAlive(textBox1.Text);
//parse results and display them
label1.Text = "";
foreach (DataTable currentTable in result.Tables) {
label1.Text += "<p>\n";
foreach (DataRow currentRow in currentTable.Rows) {
foreach (DataColumn currentCol in currentTable.Columns) {
label1.Text += currentCol.ColumnName + ": ";
label1.Text += currentRow[currentCol] + "<br>\n";
}
}
label1.Text += "</p>";
}
}
Note:
As you can see by the added application logic code, the DeadOrAliveWS web service returns
query results in the form of a dataset. Web Services can, however, return data in a variety of
formats.
Tip:
If you are using Microsoft IIS, the URL is the path of the .aspx file after Inetpub\wwwroot. For
example, if the path of your Web Application is c:\Inetpub\wwwroot\WebApplication1 and
your .aspx file is named "WebForm1.aspx", the URL would be http://localhost/WebApplication1/
WebForm1.aspx.
4 If necessary, enter your user name and password for your ASP.NET server.
Your web application requests the information from the DeadOrAliveWS web service and displays the result in
the label.
566
Note:
If no information is displayed, that name may not be in the database. Check your spelling or try
a different name.
567
Note: Currently, using Delphi 2005 you can only create web services using the code-behind method. You cannot
use the code inline method, in which you code your web service in the <ServiceName>.asmx file. Currently,
Delphi 2005 does not support the code inline method of creating web services.
New
Other.
Note:
If you are using the Cassini Web Server, you need to change the Location and Server entries.
You also need to make sure you configure the Cassini Web Server before trying to run this
application. Choose Tools
Options and select ASP.NET Options to set the Path and
the Port for Cassini.
5 Click OK.
To create a WebMethod
1 Select the WebService.pas or WebService.asmx.cs tab at the bottom of the Code Editor.
If you named your web service application something other than the default, that will be the name that appears
on the tab. The code for the "Hello World" application is already included in the WebMethod that is created for
you when you created the Web Services application.
2 Uncomment the sample WebMethods in the code-behind file.
Delphi for .NET applications have two "Hello World" WebMethods to uncomment; one in the Interface module
and the other in the Implementation module.
In C# Web Services applications, uncomment the "HelloWorld" and "EchoString" WebMethods.
3 Choose Project
The pages you see will include sample SOAP and HTTP code that you can use to access the WebMethods. You
can run the samples and see how the results are passed to an output XML file.
The pages you see will include sample SOAP and HTTP code that you can use to access the WebMethods. You
can run the samples and see how the results are passed to an output XML file.
Note:
You may need to use a slightly different syntax than that shown in this step. For instance, on
some Windows XP machines, the localhost identifier should be your machine name. For
instance, if you machine name is MyMachine, the syntax would be: http://MyMachine/
WebService1/Webservice1.asmx.
569
These include, but are not restricted to InvokeRegistry, RIO, and SOAPHTTPClient.
Warning:
The preceding list of units is not inclusive. Make sure you identify all SOAP units, regardless
of naming convention. Not all of the units include the word SOAP in the name.
2 Remove the reference to the Delphi for Win32 WSDL Importer-generated Interface proxy unit.
3 Remove the proxy unit from the project.
Once you have saved the project, the UDDI Browser appears.
2 Enter the URL you want to use, either a service you are already familiar with, or the one saved from your RIO
If you want to locate a WSDL file on your local disk, you can click the ellipsis button next to the
list box and search for the document. You can also navigate to one of the web service sites
listed in the UDDI Browser if you want to use a published service.
3 Click the Add Reference button to add the WSDL document to your project.
Delphi 2005 creates the necessary web reference and the corresponding proxy unit based on the WSDL
document. A new Web References node appears in the Project Manager. Expand it to see the associated WSDL
and proxy code files.
4 Choose File
Use Unit.
570
to
var
// This is the type of the new proxy class.
HelloService: Service3;
This assumes the name of your service is Service3. Change the name accordingly.
Note:
You will see that what was formerly created as an interface is now created as a class.
The .NET Framework provides automatic garbage collection, and so certain restrictions placed
on the use of classes in previous versions of Delphi may no longer apply when using Delphi
2005.
to:
HelloService := Service3.Create;
571
begin
HelloService := Service3.Create;
Caption := HelloService.HelloWorld;
end;
Your code is most likely more complex than this example. However, these instructions cover the basic steps for
porting any Delphi for Win32 application that uses web services to Delphi 2005.
572
Windows Forms
573
New
The BdpDataAdapter, BdpConnection, and other BDP.NET components can be found on the Tool Palette in the
Borland Data Provider area.
3 At the bottom of the Object Inspector, click the Designer Verb Connection Editor.
Note:
Designer verbs are action phrases that appear in the lower left-hand corner of the Object
Inspector. When you move the cursor over the phrase, the cursor changes to a hand pointer.
Tip: Alternatively, use Data Explorer to drag and drop a table on to the designer surface. Data Explorer sets the
connection string automatically.
To set up a connection
1 Click the Connections Editor Designer Verb at the bottom of the Object Inspector.
2 In the Borland Data Provider: Connections Editor dialog box, select an existing connection from the
574
Tip:
If using Interbase, you would enter the path to your Interbase database, which may be located
locally in c:\Program Files\Common Files\Borland Shared\Data. If connecting to a
shared network location, you will need to enter the network path and you will need to have access
rights for that remote server.
4 Complete the UserName and Password fields for the database as needed.
Tip:
If you are using a sample Interbase database, the username and password are, respectively,
sysdba and masterkey.
To create a dataset
1 To make sure you get the data you want, click the Preview Data tab on the Data Adapter Configuration editor.
2 Click Refresh.
Column and row data should appear. If they don't appear, it may be that you either do not have a live connection
to a database or your SQL statement is incorrect.
3 Click the DataSet tab.
4 Click New DataSet.
5 Either accept the default name or enter a more descriptive name.
6 Click OK.
A new DataSet component appears in the Component Tray at the bottom of the IDE.
575
5 Select the DataSet component that you generated previously (the default is dataSet1).
6 In the Object Inspector, select the DataMember property drop-down.
7 Select the appropriate table.
Run.
576
New
Other
The Code Editor appears, cursor in place between the reserved words begin and end in the event handler.
2 Code your application logic.
3 Save and compile the application.
577
New
Other
The Code Editor appears, cursor in place within the event handler code block.
2 Code the application logic:
MessageBox.Show('Hello, Developer!');
3 Save and compile the application.
Run.
The application compiles and displays a Windows Form with the "Hello, world" button.
2 Click the "Hello, world!" button.
578
To create a menu
1 From the Tool Palette, place a ContextMenu or a MainMenu component on the Windows Forms Designer.
For convenience, the ContextMenu appears much like a MainMenu component when placed
on the designer.
When you select menu text, additional options appear for submenus and menu items. Complete as needed.
The Code Editor appears, cursor in place between event handler brackets.
2 Code your menu item logic.
3 Save and compile the application.
You can use Arrow, Shift, and other keys to manipulate menu items.
Description
Delete
Insert
Inserts a blank menu item before the currently selected menu item.
Enter
The Designer goes into editing mode on the currently selected item. If the Designer is already in editing
mode, the next item becomes the currently selected item.
Arrow
Changes the currently selected menu item to the next item in the arrow direction.
579
Menu Item
Description
Cut
Removes the currently selected menu item and places it on the clipboard.
Copy
Paste
Places a menu item from the clipboard above the currently selected menu item.
Delete
Insert New
Inserts a blank menu item before the currently selected menu item.
Insert Separator
Show Code
Goes to the code and generates an event handler if one does not already exist.
580
To pass a parameter
1 Create a data adapter and connection to the Interbase employee.gdb database.
2 Add a text box control, a button control, and a data grid control to your form.
3 Configure the data adapter.
4 To add a parameter to the data adapter.
5 Configure the data grid.
6 Add code to the button Click event..
7 Compile and run the application.
New
This creates a BdpDataAdapter and BdpConnection and displays their icons in the Component Tray.
4 Select the data adapter icon, then click the Configure Data Adapter designer verb in the Designer Verb area
As you can see, this statement is limiting the number of fields. It also contains a ? character as part of the Where
clause. The ? character is a wildcard that represents the parameter value that your application passes in at
runtime. There are at least two reasons for using a parameter in this way. The first reason is to make the
application capable of retrieving numerous instances of the data in the selected columns, while using a different
value to satisfy the condition. The second reason is that you may not know the actual values at design time. You
can imagine how limited the application might be if we retrieved only data where FIRST_NAME = 'Bob'.
6 Click the DataSet tab.
7 Click New DataSet.
8 Click OK.
581
Inspector.
You should be able to see your Select statement in the SelectCommand property drop down list box.
2 Change the ParameterCount property to 1.
3 Click the (Collection) entry next to the Parameters property.
ParameterName to emp.
7 Click OK.
8 In the Object Inspector, set the Active property under Live Data to True.
fields of data.
This should display the column names of the columns specified in the SQL statement that you entered into the
data adapter.
582
*
*/
bdpDataAdapter1.Active = true;
/* This re-activates the data adapter so the refreshed data appears in the data grid. */
Self.bdpSelectCommand1.Close();
/* This closes the command to make sure that we will pass the parameter to */
/* the most current bdpSelectCommand.
Self.BdpDataAdapter1.Active := false;
/* This clears the data adapter so that we don't maintain old data
Self.bdpSelectCommand1.Parameters['emp'].Value := textBox1.Text;
/* This sets the parameter value to whatever value is in the text field.
*/
Self.BdpDataAdapter1.Active := true;
/* This re-activates the data adapter so the refreshed data appears in the data grid. */
If you have changed the names of any of these items, you need to update these commands to reflect the new
names.
3 Save your application.
This displays the employee number, first name, last name, and salary of the employee with that name in the data
grid. If there is more than one person with the same first name, the grid displays all occurrences of employees
with that name.
583
Concepts
Win32
584
585
Windows Overview
The Windows platform provides several ways to help you create and build applications. The most common types of
Windows applications are:
GUI Applications
Console Applications
Service Applications
Packages and DLLs
GUI Applications
A graphical user interface (GUI) application 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)
Console Applications
Console applications are 32-bit programs that run in a console window without a graphical interface. These
applications typically do not 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.
Service Applications
Service applications take requests from client applications, process those requests, and return the information to
the client applications. Service applications typically run in the background without much user input. A Web, FTP,
or an email server is an example of a service application.
586
587
Web Services
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. This section gives an overview of web services and web
services support.
588
589
590
ISAPI
Selecting this type of application sets up your project as a DLL, with the exported methods expected by the 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.
CGI
Selecting this type of application sets up your project as a console application, and adds the required entries to the
uses clause of the project file.
server-side scripting makes development and maintenance easier for teams of developers and Web designers.
WebSnap allows HTML design experts on your team to make a more effective contribution to Web server
development and maintenance.
The final product of the WebSnap development process includes a series of scriptable HTML page templates. These
pages can be changed using HTML editors that support embedded script tags, like Microsoft FrontPage, or even a
text editor. Changes can be made to the templates as needed, even after the application is deployed. There is no
need to modify the project source code at all, which saves valuable development time. WebSnaps multiple module
support can be used to divide your application into smaller pieces during the coding phases of your project, so that
developers can work more independently.
Converting your application to another type of Web server application after debugging
When you have finished debugging your Web server application with the Web Application Debugger, you will need
to convert it to another type that can be installed on a commercial Web server.
592
593
dbGo Overview
dbGo provides the developers with a powerful and logical object model for programmatically accessing, editing, and
updating data from a wide variety of data sources through OLE DB system interfaces. The most common usage of
dbGo is to query a table or tables in a relational database, retrieve and display the results in an application, and
perhaps allow users to make and save changes to the data.
The ADO layer of an ADO-based application consists of the latest version of Microsoft ADO, an OLE DB provider
or ODBC driver for the data store access, client software for the specific database system used (in the case of SQL
databases), a database back-end system accessible to the application (for SQL database systems), and a database.
All of these must be accessible to the ADO-based application for it to be fully functional.
594
dbExpress Components
dbExpress is a set of lightweight database drivers that provide fast access to SQL database servers. For each
supported database, dbExpress provides a driver that adapts the server-specific software to a set of uniform
dbExpress interfaces. When you deploy a database application that uses dbExpress, you include a DLL(the serverspecific driver) with the application files you build.
dbExpress lets you access databases using unidirectional datasets. Unidirectional datasets are designed for quick
lightweight access to database information, with minimal overhead. Like other datasets, they can send an SQL
command to the database server, and if the command returns a set of records, obtain a cursor for accessing those
records. However, unidirectional datasets can only retrieve a unidirectional cursor. They do not buffer data in
memory, which makes them faster and less resource-intensive than other types of dataset. However, because there
are no buffered records, unidirectional datasets are also less flexible than other datasets.
The dbExpress category of the Tool Palette contains components that use dbExpress to access database
information. They are:
Components
Function
TSQLQuery
A query-type dataset that encapsulates an SQL statement and enables applications to access the resulting
records, if any
TSQLTable
A table-type dataset that represents all of the rows and columns of a single database table
TSQLStoredProc A stored procedure-type dataset that executes a stored procedure defined on a database server
TSQLMonitor
Intercepts messages that pass between an SQL connection component and a database server and saves
them in a string list
TSimpleDataSet
A client dataset that uses an internal TSQLDataSet and TDataSetProvider for fetching data and applying
updates
595
BDE Overview
The Borland Database Engine (BDE) is a data-access mechanism that can be shared by several applications. The
BDE defines a powerful library of API calls that can create, restructure, fetch data from, update, and otherwise
manipulate local and remote database servers. The BDE provides a uniform interface to access a wide variety of
database servers, using drivers to connect to different databases.
When deploying BDE-based applications, you must include the BDE with your application. While this increases the
size of the application and the complexity of deployment, the BDE can be shared with other BDE-based applications
and provides a broader range of support for database manipulation. Although you can use the API of the BDE directly
in your application, the components on the BDE category of the Tool Palette wrap most of this functionality for you.
596
IBX components
The following components are located on the InterBase tab of the component palette.
TIBTable
TIBQuery
TIBStoredProc
TIBDatabase
TIBTransaction
TIBUpdateSQL
TIBDataSet
TIBSQL
TIBDatabaseInfo
IBSQLMonitor
TIBEvents
TIBExtract
TIBCustomDataSet
Though they are similar to BDE components in name, the IBX components are somewhat different. For each
component with a BDE counterpart, the sections below give a discussion of these differences.
There is no simple migration from BDE to IBX applications. Generally, you must replace BDE components with the
comparable IBX components, and then recompile your applications. However, the speed you gain, along with the
access you get to the powerful InterBase features make migration well worth your time.
IBDatabase
Use a TIBDatabase component to establish connections to databases, which can involve one or more concurrent
transactions. Unlike BDE, IBX has a separate transaction component, which allows you to separate transactions
and database connections.
597
Warning: Tip: You can store the username and password in the IBDatabase component's Params property by
setting the LoginPrompt property to false after logging in. For example, after logging in as the system
administrator and setting the LoginPrompt property to false, you may see the following when editing the
Params property:
user_name=sysdba
password=masterkey
IBTransaction
Unlike the Borland Database Engine, IBX controls transactions with a separate component, TIBTransaction. This
powerful feature allows you to separate transactions and database connections, so you can take advantage of the
InterBase two-phase commit functionality (transactions that span multiple connections) and multiple concurrent
transactions using the same connection.
Use an IBTransaction component to handle transaction contexts, which might involve one or more database
connections. In most cases, a simple one database/one transaction model will do.
To set up a transaction:
1 Set up an IBDatabase connection as described above.
2 Drop an IBTransaction component onto the form or data module
3 Set the DefaultDatabase property to the name of your IBDatabase component.
4 Set the Active property to true to start the transaction.
IBQuery
Use an TIBQuery component to execute any InterBase DSQL statement, restrict your result set to only particular
columns and rows, use aggregate functions, and join multiple tables.
IBQuery components provide a read-only dataset, and adapt well to the InterBase client/server environment. To set
up an IBQuery component:
598
IBDataSet
Use an TIBDataSet component to execute any InterBase DSQL statement, restrict your result set to only particular
columns and rows, use aggregate functions, and join multiple tables. IBDataSet components are similar to IBQuery
components, except that they support live datasets without the need of an IBUpdateSQL component.
The following is an example that provides a live dataset for the COUNTRY table in employee.gdb:
1 Set up an IBDatabase connection as described above.
2 Specify the associated database and transaction components.
3 Add an IBDataSet component to your form or data module.
4 Enter SQL statements for the following properties:
SelectSQL
RefreshSQL
ModifySQL
DeleteSQL
InsertSQL
Note: Note: Parameters and fields passed to functions are case-sensitive in dialect 3. For example,
FieldByName(EmpNo)
Use an TIBSQL component for data operations that need to be fast and lightweight. Operations such as data
definition and pumping data from one database to another are suitable for IBSQL components.
In the following example, an IBSQL component is used to return the next value from a generator:
1 Set up an IBDatabase connection as described above.
2 Put an IBSQL component on the form or data module and set its Database property to the name of the database.
3 Add an SQL statement to the SQL property string list editor, for example:
IBUpdateSQL
Use an TIBUpdateSQL component to update read-only datasets. You can update IBQuery output with an
IBUpdateSQL component:
1 Set up an IBQuery component as described above.
2 Add an IBUpdateSQL component to your form or data module.
3 Enter SQL statements for the following properties: DeleteSQL, InsertSQL, ModifySQL, and RefreshSQL.
4 Set the IBQuery component's UpdateObject property to the name of the IBUpdateSQL component.
5 Set the IBQuery component's Active property to true.
IBSQLMonitor
Use an TIBSQLMonitor component to develop diagnostic tools to monitor the communications between your
application and the InterBase server. When the TraceFlags properties of an IBDatabase component are turned on,
active IBSQLMonitor components can keep track of the connection's activity and send the output to a file or control.
A good example would be to create a separate application that has an IBSQLMonitor component and a Memo control.
Run this secondary application, and on the primary application, activate the TraceFlags of the IBDatabase
component. Interact with the primary application, and watch the second's memo control fill with data.
IBDatabaseInfo
Use an TIBDatabaseInfo component to retrieve information about a particular database, such as the sweep interval,
ODS version, and the user names of those currently attached to this database.
For example, to set up an IBDatabaseInfo component that displays the users currently connected to the database:
1 Set up an IBDatabase connection as described above.
2 Put an IBDatabaseInfo component on the form or data module and set its Database property to the name of the
database.
3 Put a Memo component on the form.
4 Put a Timer component on the form and set its interval.
5 Double click on the Timer's OnTimer event field and enter code similar to the following:
Memo1.Text := IBDatabaseInfo.UserNames.Text;
// Delphi example
Memo1->Text = IBDatabaseInfo->UserNames->Text; // C++ example
IBEvents
600
Use an IBEvents component to register interest in, and asynchronously handle, events posted by an InterBase
server.
To set up an IBEvents component:
1 Set up an IBDatabase connection as described above.
2 Put an IBEvents component on the form or data module and set its Database property to the name of the
database.
3 Enter events in the Events property string list editor, for example:
IBEvents.Events.Add('EVENT_NAME');
// Delphi example
IBEvents->Events->Add("EVENT_NAME"); // C++ Example
601
Use an TIBConfigService object to configure database parameters, including page buffers, async mode, reserve
space, and sweep interval.
IBBackupService
Use an TIBBackupService object to back up your database. With IBBackupService, you can set such parameters
as the blocking factor, backup file name, and database backup options.
IBRestoreService
Use an TIBRestoreService object to restore your database. With IBRestoreService, you can set such options as
page buffers, page size, and database restore options.
IBValidationService
Use an TIBValidationService object to validate your database and reconcile your database transactions. With the
IBValidationService, you can set the default transaction action, return limbo transaction information, and set other
database validation options.
IBStatisticalService
Use an TIBStatisticalService object to view database statistics, such as data pages, database log, header pages,
index pages, and system relations.
IBLogService
Use an TIBLogService object to create a log file.
IBSecurityService
Use an TIBSecurityService object to manage user access to the InterBase server. With the IBSecurityService, you
can create, delete, and modify user accounts, display all users, and set up work groups using SQL roles.
IBLicensingService
Use an TIBLicensingService component to add or remove InterBase software activation certificates.
IBServerProperties
Use an TIBServerProperties component to return database server information, including configuration parameters,
and version and license information.
IBInstall
Use an TIBInstall component to set up an InterBase installation component, including the installation source and
destination directories, and the components to be installed.
IBUnInstall
Use an TIBUnInstall component to set up an uninstall component.
602
603
VCL Overview
This section introduces:
VCL Architecture
VCL versus VCL.NET
VCL Components
Working With Components
VCL Architecture
VCL is an acronym for the Visual Component Library, a set of visual components for rapid development of Windows
applications in the Delphi language. VCL contains a wide variety of visual, non-visual, and utility classes for tasks
such as Windows application building, web applications, database applications, and console applications. All classes
descend from TObject. TObject introduces methods that implement fundamental behavior like construction,
destruction, and message handling.
VCL Components
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 designtime. 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
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
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 designtime, nonvisual components
are represented by an icon. This allows you to manipulate their properties and events just as you would a visual
control.
604
or for transient tasks (such as storing data in a list). You cannot create instances of these classes at designtime,
although they are sometimes created by the components that you add in the Form Designer.
Using Events
Almost all the code you write is executed, directly or indirectly, in response to events. An event is a special kind of
property that represents a runtime occurrence, often a user action. The code that responds directly to an event,
called an event handler, is a Delphi procedure.
The Events page of the Object Inspector displays all events defined for a given component. Double-clicking an
event in the Object Inspector generates a skeleton event handling procedure, which you can fill in with code to
respond to that event. Not all components have events defined for them.
Some components have a default event, which is the event the component most commonly needs to handle. For
example, the default event for a button is OnClick. Double-clicking on a component with a default event in the
Form Designer will generate a skeleton event handling procedure for the default event.
You can reuse code by writing event handlers that respond to more than one event. For example, many applications
provide speed buttons that are equivalent to drop down menu commands. When a button performs the same action
as a menu command, you can write a single event handler and then assign it to the OnClick event for both the button
and the menu item by setting the event handler in the Object Inspector for both the events you want to respond to.
This is the simplest way to reuse event handlers. However, action lists, and in the VCL, action bands, provide
powerful tools for centrally organizing the code that responds to user commands. Action lists can be used in crossplatform applications; action bands cannot.
605
When you select a component on a form at design time, the Object Inspector displays its published properties and,
when appropriate, allows you to edit them.
When more than one component is selected, the Object Inspector displays all propertiesexcept Namethat are
shared by the selected components. If the value for a shared property differs among the selected components, the
Object Inspector displays either the default value or the value from the first component selected. When you change
a shared property, the change applies to all selected components.
Changing code-related properties, such as the name of an event handler, in the Object Inspector automatically
changes the corresponding source code. In addition, changes to the source code, such as renaming an event handler
method in a form class declaration, are immediately reflected in the Object Inspector.
606
607
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.
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.
608
Objects can have multiple interfaces, where each interface implements a feature. An interface provides a way to tell
the client what service it provides, without providing implementation details of how or where the object provides this
service.
Key aspects of COM interfaces are as follows:
Once published, interfaces do not change. You can rely on an interface to provide a specific set of functions.
Additional functionality is provided by additional interfaces.
By convention, COM interface identifiers begin with a capital I and a symbolic name that defines the interface,
such as IMalloc or IPersist.
Interfaces are guaranteed to have a unique identification, called a Globally Unique Identifier (GUID), which is a
128-bit randomly generated number. Interface GUIDs are called Interface Identifiers (IIDs). This eliminates
naming conflicts between different versions of a product or different products.
Interfaces are language independent. You can use any language to implement a COM interface as long as the
language supports a structure of pointers, and can call a function through a pointer, either explicitly or implicitly.
Interfaces are not objects themselves, they provide a way to access an object. Therefore, clients do not access
data directly, they access data through an interface pointer. Windows 2000 adds another layer of indirection,
known as an interceptor, through which it provides COM+ features such as just-in-time activation and object
pooling.
Interfaces are always inherited from the base interface, IUnknown.
Interfaces can be redirected by COM through proxies to enable interface method calls to call between threads,
processes, and networked machines, all without the client or server objects ever being aware of the redirection.
in-time activation, where the server can be deactivated and reactivated dynamically in a way that is opaque to the
client. To achieve this, COM+ guarantees that the interceptor behaves as if it were an ordinary vtable pointer.
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 objects implementation remains hidden. 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 objects 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 create 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 creates 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.
COM Clients
COM clients are applications that make use of a COM object implemented by another application or library. The
most common types are Automation controllers, which control an Automation server and ActiveX containers, which
host an ActiveX control.
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 containers 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.
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 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. Delphi lets 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.
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 servers type
information.
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
610
about the client using its objects. Similarly, clients dont need to know how an object provides the services, they
simply rely on server objects to provide the services they describe in their interfaces.
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. In addition, when
working in a large, distributed environment, you can create transactional COM objects. Prior to Windows 2000, these
objects were not an architectural part of COM, but ran in the Microsoft Transaction Server (MTS) environment. As
of Windows 2000, this support is integrated into COM+. Delphi provides wizards to easily implement applications
that use the above technologies in the Delphi environment.
Automation Servers
Automation refers to the ability of an application to control the objects in another application programmatically, such
as 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 defined by two major 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 the arguments to those methods. Typically, this information is available in a type library. The
Automation server can also generate type information dynamically when queried via its IDispatch interface.
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 objects 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.
Active X Controls
Delphi wizards allow you to easily create 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, which 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 already written
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.
611
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 either 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 for them by MTS or COM+.
Under MTS, the server object must be built into a DLL library, 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 does not need 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 objects
interface. The type information contained in type libraries provides needed information about objects and their
612
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 Delphis 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.
613
614
615
Procedures
616
Database
617
Set the CommandText property to specify the query, table, or stored procedure from which you want to fetch
data.
Set type of schema information to stNoSchema and call SetSchemaInfo method .
Note:
If you choose the second option, the dataset fetches the data specified by the CommandText
property.
618
New
Other.
Note: The relationship between the dbExpress driver or dynamic link library and the database name is stored in a
file called dbxdrivers.ini, which is updated when you install a dbExpress driver. The SQL connection
component looks the dbExpress drive and the dynamic-link library 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.
designtime.
2 Use the Params.Values to assign values to individual parameters at run time.
2 Edit the Params property to change the saved set of parameter values.
3 Set the LoadParamsOnConnect property to True to develop your application using one database and deploy it
using another.
This causes TSQLConnection to automatically set DriverName and Params to the values associated with
ConnectionName in dbxconnections.ini when the connection is opened.
4 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.
The dbExpress Connection Editor appears, with a drop-down drivers list, a list of connection names for the
currently selected driver, and a connection parameters table for the currently selected connection name.
2 From the Driver Name drop-down list, select a driver to indicate the connection to use.
3 From the Connection Name list, select a connection name.
4 Choose the configuration that you want.
5 Click the Test Connection button to check for a valid configuration.
parameter table.
2 Click OK.
620
621
New
Other.
to the form.
4 Set the SQLConnection property of the TSQLMonitor to the TSQLConnection component.
5 Set the Active property of the TSQLMonitor to True.
622
To execute commands
1 Choose File
New
Other.
TSQLDataSet.
2 Set the SQL property in the Object Inspectorto specify the SQL statement to pass to the server for a
TSQLQuery .
3 Set the StoredProcName property in the Object Inspector to specify the name of the stored procedure to execute
for a TSQLStoredProc .
Tip: If you are executing the query or stored procedure multiple times, it is a good idea to set the Prepared property
to True.
PROCEDURE statements.
4 To delete any of the above metadata objects, use DROP TABLE, DROP VIEW, DROP DOMAIN, DROP
623
624
New
Other.
Use GetMetadata property to selectively fetch metadata on a database object. Set GetMetadata
to False if you are fetching a dataset for read-only purposes.
Note: NextRecordSet returns a newly created TCustomSQLDataSet component that provides access to the next
set of records. That is, the first time you call NextRecordSet, it returns a dataset for the second set of records.
Calling NextRecordSet returns a third dataset, and so on, until there are no more sets of records. When there
are no additional datasets, NextRecordSet does not return anything.
625
New
Other.
Note: When you specify the query, it can include parameters, or variables, the values of which can be varied at
design time or runtime. Parameters can replace data values that appear in the SQL statement. SQL defines
queries such as UPDATE queries that perform actions on the server but do not return records.
TSQLDataSet generates a query based on the values of two properties: CommandText that specifies the name
of the database table that the TSQLDataSet object should represent and SortFieldNames that lists the names
of any fields to use to sort the data, in the order of significance
2 Drag a TSQLTable component to the form.
3 In the Object Inspector , set the TableName property to the table you want.
4 Set the IndexName property to the name of an index defined on the server or set the IndexFieldNames property
to a semicolon-delimited list of field names to specify the order of fields in the dataset.
626
Note: After you have identified a stored procedure, your application may need to enter values for any input
parameters of the stored procedure or retrieve the values of output parameters after you execute the stored
procedure.
627
New
Other.
628
Using BDE
To use BDE
1 Choose File
New
Other.
This will encapsulate the full structure of data in an underlying database table.
4 From the BDE category of the Tool Palette, drag a TQuery component to the form.
This will encapsulate an SQL statement and enables applications to access the resulting records.
5 From the BDE category of the Tool Palette, drag a TStoredProc component to the form.
629
Using DataSnap
A multi-tiered client/server application is partitioned into logical units, called tiers, which run in conjunction on
separate machines. Multi-tiered applications share data and communicate with one another over a local-area
network or even over the Internet. They provide many benefits, such as centralized business logic and thin client
applications.
Multi-tiered applications use the components on the DataSnap category in the Tool Palette. DataSnap provides
multi-tier database capability to Delphi applications by allowing client applications to connect to providers in an
application server.
New
Other.
This will establish a DCOM connection to a remote server in a multi-tiered database application.
4 From the DataSnap category of the Tool Palette, drag a TSocketConnection component to the form.
This will establish a TCP/IP connection to a remote server in a multi-tiered database application.
5 From the DataSnap category of the Tool Palette, drag a TSimpleObjectBroker component to the form.
This will locate a server for a connection component from a list of available application servers.
6 From the DataSnap category of the Tool Palette, drag a TWebConnection component to the form.
This will establish an HTTP connection to a remote server in a multi-tiered database application.
7 From the DataSnap category of the Tool Palette, drag a TConnectionBroker component to the form.
This will centralize all connections to the application server so that applications do not need major rewriting when
changing the connection protocol.
8 From the DataSnap category of the Tool Palette, drag a TSharedConnection component to the form.
This will connect to a child remote data module when the application server is built using multiple remote data
modules.
9 From the DataSnap category of the Tool Palette, drag a TLocalConnection component to the form.
This will provide access to IAppServer methods that would otherwise be unavailable, and make it easier to scale
up to a multi-tiered application at a later time. It acts like a connection component for providers that reside in the
same application.
630
Using dbExpress
To build a database applications using dbExpress
1 Connect to the database server and configure a TSQL connection.
2 Specify the data to display.
3 Fetch the data.
4 Execute the commands.
5 Access the schema information.
6 Debug dbExpress application using TSQLMonitor.
7 Use TSQLTable to represent a table on a database server that is accessed via TSQLConnection.
8 Use TSQLQuery to execute an SQL command on a database server that is accessed via TSQLConnection.
9 Use TSQLStoredProc to execute a stored procedure on a database server that is accessed via
TSQLConnection.
631
Using TBatchMove
TBatchMove copies a table structure or its data. It can be used to move entire tables from one database format to
another.
To use TBatchMove
1 Choose File
New
Other.
632
New
Other.
SessionName is set to Default," which means it is associated with the default session component that is
referenced by the global Session variable.
4 Add a TSession component for each session if you use multiple sessions.
5 Set the SessionName property of the TDatabase component to the SessionName property of the TSession
If SessionName is blank or Default," the Session property references the same TSession instance referenced
by the global Session variable.
Session enables applications to access the properties, methods, and events of a database components parent
session component without knowing the sessions actual name. If you are using an implicit database component,
the session for that database component is the one specified by the datasets SessionName property.
a database component.
633
Note:
This clears any value already assigned to DriverName. Alternatively, you can specify a driver
name instead of an alias when you create a local BDE alias for a database component using
the DatabaseName property. Specifying the driver name clears any value already assigned to
AliasName. To provide your own name for a database connection, set the DatabaseName. To
specify a BDE alias at designtime, assign a BDE driver.
down list.
6 To create or edit connection parameters at designtime, do one of the following:
Note: All of these methods edit the Params property for the database component. 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.
Note: Calling TDatabase. Rollback does not call TDataSet. Cancel for any data sets associated with the database.
634
Using TQuery
TQuery is a query-type dataset that encapsulates an SQL statement and enables applications to access the resulting
records.
To use TQuery
1 Choose File
New
Other.
database.
For the TDatabase component, database name is the value of the DatabaseName property of the database
component.
4 Specify a BDE alias as the value of DatabaseName if you want to use an implicit database component and the
A BDE alias represents a database plus configuration information for that database. The
configuration information associated with an alias differs by database type (Oracle, Sybase,
InterBase, Paradox, dBASE, and so on).
5 In the Object Inspector, set the DatabaseName to specify the directory where the database tables are located
if you want to use an implicit database component for a Paradox or dBASE database.
6 Use the default session to control all database connections in your application.
7 Set the SessionName property of the TSession component to associate your dataset with an explicitly created
session component .
Note: Whether you use the default session or explicitly specify a session using the SessionName property, you can
access the session associated with a dataset by reading the DBSession property. 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.
SQL explorer.
2 Leave the DatabaseName property of the TQuery component blank.
The names of the databases used will be specified in the SQL statement.
635
3 Set the SQL property to the SQL statement you want to execute.
4 Precede each table name in the statement with the BDE alias for the database of the table, enclosed in colons.
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, or an error code for SQL queries made against
a remote server.
applying updates and associate the query with an update object with the query .
3 Set the DeleteSQL, InsertSQL, and ModifySQL properties of the update object to the SQL statements that
636
New
Other.
637
Using TSimpleDataSet
TSimpleDataSet is a special type of client dataset designed for simple two-tiered applications. Like a unidirectional
dataset, it can use an SQL connection component to connect to a database server and specify an SQL statement
to execute on that server. Like other client datasets, it buffers data in memory to allow full navigation and editing
support.
To use TSQLStoredProc
1 From the dbExpress category of the Tool Palette, drag a TSimpleDataSet component to the form.
2 Set its Name property to a unique value appropriate to your application.
3 From the dbExpress section of the Tool Palette, drag a TSQLConnection component on the form.
4 Select TSimpleDataSet component. Set the Connection property to TSQLConnection component.
5 To fetch data from the server, do any of the following:
Set CommandType to ctQuery and set CommandText to an SQL statement you want to execute on the server.
Set CommandType to ctStoredProc and set CommandText to the name of the stored procedure you want to
execute.
Set CommandType to ctTable and set CommandText to the name of the database tables whose records you
want to use.
6 If the stored procedure returns a cursor to be used with visual data controls, add a data source component to
the form.
7 Set the DataSet property of the data source component to the TSimpleDataSet object.
8 To activate the dataset, use the Active property or call the Open method.
9 If you executed a stored procedure, use the Params property to retrieve any output parameters.
638
Using TSimpleObjectBroker
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.
To use TSimpleObjectBroker
1 Choose File
New
Other.
639
Using TSQLQuery
TSQLQuery represents a query that is executed using dbExpress. TSQLQuery can represent the results of a
SELECT statement or perform actions on the database server using statements such as INSERT, DELETE,
UPDATE, ALTER TABLE, and so on. You can add a TSQLQuery component to a form at design time, or create
one dynamically at runtime.
To use TSQLQuery
1 From the dbExpress category of the Tool Palette, drag a TSQLQuery component to the form.
2 In the Object Inspector, set its Name property to a unique value appropriate to your application.
3 Set the SQLConnection property.
4 Click the ellipsis button next to the SQL property of the TSQLQuery component.
640
Using TSQLStoredProc
TSQLStoredProc represents a stored procedure that is executed using dbExpress. TSQLStoredProc can
represent the result set if the stored procedure returns a cursor. You can add a TSQLStoredProc component to a
form at design time, or create one dynamically at runtime.
To use TSQLStoredProc
1 From the dbExpress category of the Tool Palette, drag a TSQLStoredProc component to the form.
2 In the Object Inspector, set its Name property to a unique value appropriate to your application.
3 Set the SQLConnection property.
4 Set the StoredProcName property to specify the stored procedure to execute.
5 If the stored procedure returns a cursor to be used with visual data controls, add a data source component to
the form.
6 Set the DataSet property of the data source component to the stored procedure-type dataset.
7 Provide input parameter values for the stored procedure, if necessary.
8 To execute the stored procedure that returns a cursor, use the Active property or call the Open method.
9 Process any results.
641
Using TSQLTable
TSQLTable represents a database table that is accessed using dbExpress. TSQLTable generates a query to fetch
all of the rows and columns in a table you specify. You can add a TSQLTable component to a form at designtime,
or create one dynamically at runtime.
To use TSQLTable
1 Choose File
New
Other.
642
Using TStoredProc
TStoredProc is a stored procedure-type dataset that executes a stored procedure that is defined on a database
server.
To use TStoredProc
1 Choose File
New
Other.
For TDatabase component, database name is the value of the DatabaseName property of the database
component.
3 Drag a TSession component to the form.
4 To control all database connections in your application, use the default session.
5 In the Object Inspector, set the SessionName property of the TSession component to associate your dataset
To bind parameters
1 From the BDE category of the Tool Palette, drag a TStoredProc component to the form.
2 Set the ParamBindMode property to default pbByName to specify how parameters should be bound to the
pbByNumber.
4 Determine the correct order and type of parameters.
5 Specify the correct parameter types in the correct order.
Note: 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.
643
Using TTable
TTable is a table-type dataset that represents all of the rows and columns of a single database table.
To use TTable
1 Choose File
New
Other.
session component.
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.
Note:
If the table is already in use when you attempt to open it, exclusive access is not granted. You
can attempt to set Exclusive on SQL tables, but some servers do not support exclusive table-
644
level locking. Others may grant an exclusive lock, but permit other applications to read data
from the table.
For each non-production index file or .NDX file, repeat Steps 3 and 4.
5 After adding all desired indexes, click the OK button in the Index Files editor.
Note: To do steps 3-5 at runtime, access the IndexFiles property using properties and methods of string lists.
operation to perform.
645
dataset provider.
6 Reopen the dataset.
7 Create SQL statements for update components.
8 Use multiple update objects.
9 Execute the SQL statements.
646
Interoperable Applications
647
New
Other.
This will indicate how COM launches the application that houses your COM object.
Note:
If your application implements more than one COM object, you should specify the same
instancing for all of them.
This will determine how COM serializes the threads of client requests when it calls your object.
7 Check the check box next to Include Type Library .
This will define interfaces using a Type Library editor and give clients an easy way to obtain information about
your object and its interfaces.
8 Check the check box next to Mark interface Oleautomation.
This will enable COM to set up the proxies and stubs for you and handle passing parameters across process
boundaries.
9 Optionally, add a description of your COM object.
This description appears in the type library for your object if you create one.
648
Reporting
649
Configure Tools.
This adds the path for the program and the working directory to the Tool Properties dialog box.
8 Click OK
9 Click Close.
This adds the command to your Tools menu that will initiate a Rave Reports session. Refer to the Rave Reports
online Help for information on how to build and integrate report objects.
650
VCL
651
New
Other.
A TMainMenu component creates a menu that is attached to the title bar of the form. A
TPopupMenu component creates a menu that appears when the user right-clicks in the form.
The menu appears in the form exactly as it will when you run the program.
3 To delete a menu item, select the menu item you want to delete. Press Delete.
4 To edit menu items, select the Windows form, select the menu item you want to edit, and edit its properties.
5 To make the menu item a separator bar, place the cursor on the menu where you want a separator to appear
and enter a hyphen (-) for the caption or press the hyphen (-) key.
6 To specify a keyboard shortcut for a menu item, in the Object Inspector, set the ShortCut property.
You can also open the Menu Designer by clicking the ellipsis(...) button next to the Items
property in the Object Inspector.
3 To name a menu component, in the Object Inspector, set the Caption property.
652
Tip:
Delphi derives the Name property from the caption, for e.g. if you give a menu item a Caption
property value of File, Delphi assigns the menu item a Name property of File1. However, if you
fill in the Name property before filling in the Caption property, Delphi leaves the Caption property
blank until you type a value.
4 Right-click anywhere on the Menu Designer to use the Menu Designer context menu.
The Select Menu dialog box appears. It lists all the menus associated with the form whose menu is currently
open in the Menu designer.
8 From the list in the Select Menu dialog box, choose the menu you want to view or edit.
The Code Designer appears, cursor in place between event handler brackets.
2 Code your menu item logic.
3 Save and compile the application.
new location.
2 Release the mouse button.
3 To move a menu item into a menu list, drag the item until the arrow tip of the cursor points to the new menu.
4 Release the mouse button.
653
The Code Editor appears, cursor in place between the reserved words begin and end in the event handler.
2 Code your application logic.
3 Save and compile the application.
654
New
Other....
The Code Editor appears, cursor in place within the event handler code block.
2 Code the application logic:
ShowMessage('Hello, Developer!');
3 Save and compile the application.
Run.
The application compiles and displays a form with the "Hello World" button.
2 Click the "Hello World" button.
655
New
Other
Tip:
Place non-visual components such as this one in the top left corner of the form to keep them out
of the way of visual components you will be adding.
The List of Available Fields: listbox displays the fields in the parts.db table.
5 Use CTRL+Click to select the PartNo, OnOrder, and Cost fields; then click the right-arrow button next to the
Dimensions: listbox.
PartNo, OnOrder, and Cost display in the listbox.
6 Select the OnOrder field; then click the right-arrow button next to the Summaries: listbox and select count from
Summaries: listbox and select sum from the pop-up that displays.
SUM(Cost) displays in the Summaries: listbox.
8 Click OK to close the Decision Query Editor.
Note: When you use the Decision Query Editor, the query is initially handled in ANSI-92 SQL syntax and then
translated (if necessary) into the dialect used by the server. The Decision Query editor reads and displays
656
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 than the SQL property.
form.
2 In the Object Inspector, select DecisionQuery1 from the drop-down list next to the decision cube's DataSet
property.
the form.
2 In the Object Inspector, select DecisionCube1 from the drop-down list next to the decision source's
DecisionCube property.
form.
Tip:
The decision pivot displays in the final application window. Place it to the right of the nonvisual
components.
2 In the Object Inspector, select DecisionSource1 from the drop-down list next to the decision pivot's
DecisionSource property.
decision pivot.
2 In the Object Inspector, select DecisionSource1 from the drop-down list next to the decision grid's
DecisionSource property.
DecisionSource property.
The visual decision graph, grid, and pivot components display data.
2 Choose Run
658
659
New
Other
5 Try commands that are automatically set up by the MDI Application wizard.
660
New
Other
New
Form
Options
661
The Code Editor displays with the cursor in the TfrMain.FormClose event handler block.
3 Enter the following code:
Action := caFree;
The Menu designer (frMain.MainMenu1) displays with the first blank menu item highlighted.
3 In the Object Inspector on the Properties tab, enter mnFile for the Name property and &File for the Caption
A blank command field displays in the File group. Select the blank command.
5 In the Object Inspector, enter mnNewChild for the Name property and &New child for the Caption property; then
press ENTER.
In the Menu designer, New child displays as the name of the first file command, and a blank command field
displays just beneath New child.
6 Select the blank command.
7 In the Object Inspector, enter mnCloseAll for the Name property and &Close All for the Caption property; then
press ENTER.
In the Menu designer, Close All displays as the name of the second file command.
To add event handlers for the New child and Close All commands
1 If necessary, open the Menu designer and select New child.
2 In the Object Inspector, double-click the OnClick event on the Events tab.
The Code Editor displays with the cursor in the TfrMain.mnNewChildClick event handler block.
3 At the cursor, enter the following code:
CreateChildForm('Child '+IntToStr(MDIChildCount+1));
662
The Code Editor displays with the cursor in the TfmMain.mnCloseAllClick event handler block.
6 At the cursor, enter the following code:
for i:=0 to MDIChildCount - 1 do
MDIChildren[i].Close;
7 Just before the code block in the event handler, declare the local variable i.
The first two lines of the event handler code should appear as shown here when you are done:
procedure TfrMain.mnCloseAllClick(Sender: TObject);
var i: integer;
Note:
The event handler minimizes the child window in the main window. To close the child window,
you must add an OnClose procedure to the child form (next).
The Code Editor displays with the cursor in the TfrChild.FormClose event handler block.
3 At the cursor, enter the following statement:
Action := caFree;
Run.
Close All.
663
New
Other
664
New
Other
This displays the first page of the New VCL Component wizard. By default, it should be set to Delphi for VCL
Win32.
3 Click Next.
This displays the second page of the New VCL Component wizard and loads the page with ancestor
components.
4 Select an ancestor component from the list.
5 Click Next.
This displays the third page of the New VCL Component wizard.
textbox.
3 Enter the unit name in the Unit Name textbox.
4 Enter the search path in the Search Path textbox.
5 Click Next.
Note:
To create a unit
1 Select the Create Unit radio button.
2 Click Finish.
665
2 Click Next.
666
New
Other
You can either type the Select statement in the Object Inspector or click the ellipsis to the right of CommandText
to display the CommandText Editor where you can build your own query statement.
Tip:
If you need additional help while using the CommandText Editor, click the Help button.
You are prompted to log in. Use admin for the username and no password.
667
on the form.
2 In the Object Inspector, select the ProviderName drop-down. Set it to DataSetProvider1.
3 Set the Active property to True to allow data to be passed to your application.
A data source connects the client dataset with data-aware controls. Each data-aware control must be associated
with a data source component to have data to display and manipulate. Similarly, all datasets must be associated
with a data source component for their data to be displayed and manipulated in data-aware controls on the form.
on the form.
2 In the Object Inspector, select the DataSet property drop-down. Set it to ClientDataSet1.
Run.
4 You are prompted to log in. Enter admin for the username and no password.
668
New
Other
Database field.
By default, the file is located in C:\Program Files\Common Files\Borland Shared\Data.
6 Accept the value in the User_Name field (sysdba) and Password field (masterkey).
7 Click OK to close the Connection Editor and save your changes.
If you need additional help while using the CommandText Editor, click the Help button.
4 In the Object Inspector, set the Active property to True to open the dataset.
669
A data source connects the client dataset with data-aware controls. Each data-aware control must be associated
with a data source component to have data to display and manipulate. Similarly, all datasets must be associated
with a data source component for their data to be displayed and manipulated in data-aware controls on the form.
on the form.
2 In the Object Inspector, select the DataSet property drop-down. Set it to ClientDataSet1.
Run.
You are prompted to enter a password. Enter masterkey. If you enter an incorrect password or no password, the
debugger throws an exception.
The application compiles and displays a VCL form with a DBGrid.
670
New
Other
671
New
Other
Tip:
The MainMenu1 editor displays with the first blank command category selected.
2 In the Object Inspector, enter &File for the Caption property and press RETURN.
The first blank action under the File command displays. Select the blank action.
4 Double-click ActionList1 on the form.
dialog.
6 Scroll to the File category, and click the TFileOpen action.
7 Click OK to close the dialog.
category.
672
2 In the Object Inspector, enter &Open for the Caption property and select FileOpen1 from the Action property
Run.
The application executes, displaying a form with the main menu bar and the File menu.
2 Choose File
673
New
Other
The Code Editor displays, with the cursor in the Button1Click event handler block.
3 Place the cursor before the begin reserved word; then press Enter.
var s: string;
5 Insert the cursor within the code block, and type the following code:
s:= 'Hello world!';
ShowMessage(s);
674
New
Other
Tip:
Run.
The application executes, displaying a form with the main menu bar and the File menu.
2 Choose File
Open.
675
676
New
Other
The Code displays with the cursor in the TForm1.Button1Click event handler block.
2 Enter the following code to display the stock price for the first child node when the Borland button is clicked:
BorlandStock:=XMLDocument1.DocumentElement.ChildNodes[0];
Price:= BorlandStock.ChildNodes['price'].Text;
Memo1.Text := Price;
3 Add a var section just above the code block in the event handler, and enter the following local variable
declarations:
var
BorlandStock: IXMLNode;
Price: string;
4 In the Object Inspector with Button2 selected, double-click the OnClick event on the Events tab.
The Code displays with the cursor in the TForm1.Button2Click event handler block.
5 Enter the following code to display the stock price for the second child node when the MyCompany button is
clicked:
MyCompany:=XMLDocument1.DocumentElement.ChildNodes[1];
Price:= MyCompany.ChildNodes['price'].Text;
Memo1.Text := Price;
6 Add a var section just above the code block in the event handler, and enter the following local variable
declarations:
678
var
MyCompany: IXMLNode;
Price: string;
679
New
Other
The Code Editor displays, with the cursor in the TForm1.CopyFileClick event handler block.
3 Place the cursor before the begin reserved word; then press return.
680
681
New
Other
the form.
The Code Editor displays, with the cursor in the TForm1.Button1Click event handler block.
3 Place the cursor before the begin reserved word; then press return.
682
StringList.free;
end;
The Code Editor displays, with the cursor in the TForm1.Button2Click event handler block. At the cursor, enter
the following code:
Memo1.Lines.AddStrings(ComboBox1.Items);
The strings display in the TComboBox in the order listed in the event handler code for Button1.
4 Click Button2.
In the Memo1 window, the strings from ComboBox1 are appended to the 'Memo1' string.
Note:
Try replacing the code in the Button2 event handler with the following code; then compile and
run the application again.
Memo1.Lines.Assign(ComboBox1.Items);
683
Creating Strings
Creating this VCL application consists of the following steps:
1 Create a VCL Form with TButton and TComboBox controls.
2 Write the code to create strings to the TButton OnClick handler.
3 Run the application.
New
Other
The Code Editor displays, with the cursor in the TForm1.Button1Click event handler block.
3 Place the cursor before the begin reserved word; then press return.
684
The strings 'Animals', 'Cars', and 'Flowers' display alphabetically in a list in the ListBox. The Label caption displays
the message string: 'Flowers has an index value of 2.'
3 In the ComboBox, click the arrow to expand the drop-down list.
685
New
Other
New
Other
Delphi Projects
Options.
Use Unit.
A uses clause containing the unit name Unit2 is placed in the implementation section of Unit1.
The Code Editor displays with the cursor in the TForm1.Button1Click event handler block.
686
3
var
FM: TForm2;
4 Insert the cursor in the event handler block, and enter the following code:
FM := TForm2.Create(self);
FM.ShowModal;
FM.Free;
Run.
Form2 displays.
3 With Form2 displayed, attempt to click on Form1 to activate it.
687
Deleting Strings
Creating this VCL application consists of the following steps:
1 Create a VCL Form with Buttons and ListBox controls.
2 Write the code to add strings to a list.
3 Write the code to delete a string from the list.
4 Run the application.
New
Other
The Code Editor displays, with the cursor in the TForm1.AddClick event handler block.
3 Place the cursor before the begin reserved word; then press return.
5 Insert the cursor within the code block, and type the following code:
688
MyList := TStringList.Create;
try
with MyList do
begin
Add('Mice');
Add('Goats');
Add('Elephants');
Add('Birds');
ListBox1.Items.AddStrings(MyList);
end;
finally
MyList.Free;
end;
The Code Editor displays, with the cursor in the TForm1.DeleteClick event handler block.
3 Place the cursor before the begin reserved word; then press return.
The strings 'Mice', 'Goats', 'Elephants', and 'Birds' display in the order listed.
3 Click the Delete button.
689
New
Other
New
Other
Delphi Projects
Use Unit.
A uses clause containing the unit name Unit2 is placed in the implementation section of Unit1.
The Code Editor displays with the cursor in the TForm1.Button1Click event handler block.
3 Enter the following event handling code:
Form2.ShowModal;
Run.
Form2 displays.
690
691
New
Other
The Code Editor displays with the cursor in the TForm1.Button1Click event handler block.
2 Enter the following event handling code, replacing MyFile.bmp with the name of the bitmap image in your project
directory:
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile('MyFile.bmp');
Form1.Canvas.Brush.Bitmap := Bitmap;
Form1.Canvas.FillRect(Rect(0,0,100,100));
finally
Form1.Canvas.Brush.Bitmap := nil;
Bitmap.Free;
end;
Tip:
You can change the size of the rectangle to be displayed by adjusting the Rect parameter values.
692
Run.
2 Click the button to display the image bitmap in a 100 x 100-pixel rectangle in the upper left corner of the form.
693
New
Other
The Code Editor displays with the cursor in the TForm1.FullViewClick event handler block.
2 Enter the following event handling code, replacing MyFile.bmp with the name of the bitmap image in your project
directory:
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile('MyFile.bmp');
Form1.Canvas.Brush.Bitmap := Bitmap;
Form1.Canvas.FillRect(Rect(0,0,Bitmap.Width,Bitmap.Height));
finally
Form1.Canvas.Brush.Bitmap := nil;
Bitmap.Free;
end;
3 In the var section of the code, add this variable declaration:
Bitmap : TBitmap;
694
Run.
2 Click the button to display the image bitmap in a 100 x 100-pixel rectangle in the upper left corner of the form.
695
New
Other
The Code Editor displays with the cursor in the TForm1.FormPaint event handler block.
3 Enter the following event handling code:
Canvas.Polygon ([Point(0,0), Point(0, ClientHeight),
Point(ClientWidth, ClientHeight)]);
Run.
2 The applications executes, displaying a right triangle in the lower left half of the form.
696
New
Other
The Code Editor displays with the cursor in the TForm1.FormPaint event handler block.
2 Enter the following event handling code:
Canvas.Rectangle (0, 0, ClientWidth div 2, ClientHeight div 2);
Canvas.Ellipse (0, 0, ClientWidth div 2, ClientHeight div 2);
Run.
2 The applications executes, displaying a rectangle in the upper left quadrant, and an ellipse in the same area of
the form.
697
New
Other
The Code Editor displays with the cursor in the TForm1.FormPaint event handler block.
4 Enter the following event handling code:
Canvas.RoundRect(0, 0, ClientWidth div 2,
ClientHeight div 2, 10, 10);
Run.
2 The application executes, displaying a rounded rectangle in the upper left quadrant of the form.
698
New
Other
The Code Editor displays with the cursor in the TForm1.FormPaint event handler block.
2 Enter the following event handling code:
with Canvas do
begin
MoveTo(0,0);
LineTo(ClientWidth, ClientHeight);
MoveTo(0, ClientHeight);
LineTo(ClientWidth, 0);
end;
Run.
2 The applications executes, displaying a form with two diagonal crossing lines.
Tip:
To change the color of the pen to green, insert this statement following the first MoveTo()
statement in the event handler code: Pen.Color := clGreen; Experiment using other canvas
and pen object properties. See "Using the properties of the Canvas object" in the Delphi 7
Developer's Guide.
699
New
Other
New
Other
Delphi Projects
Options.
Use Unit.
A uses clause containing the unit name Unit2 is placed in the implementation section of Unit1.
700
The Code Editor displays with the cursor in the TForm1.Button1Click event handler block.
3 Enter the following event handling code:
Form2 := TForm2.Create(self);
try
Form2.ShowModal;
finally
Form2.Free;
end;
Run.
Form2 displays.
3 Click the X in the upper right corner of the form.
701
New
Other
New
Other
Delphi Projects
Options.
Use Unit.
A uses clause containing the unit name Unit2 is placed in the implementation section of Unit1.
The Code Editor displays with the cursor in the TForm1.Button1Click event handler block.
3 Enter the following event handling code:
702
Form2 := TForm2.Create(self);
Form2.Show;
Note:
If your application requires additional instances of the modeless form, declare a separate global
variable for each instance. In most cases you use the global reference that was created when
you made the form (the variable name that matches the Name property of the form).
Run.
Form2 displays.
3 Click Form1.
Form1 becomes the active form. Form2 displays until you minimize or close it.
703
New
Other
The Code Editor displays, with the cursor in the TForm1.AddClick event handler block.
3 Place the cursor before the begin reserved word; then press return.
5 Insert the cursor within the code block, and type the following code:
704
MyList := TStringList.Create;
try
with MyList do
begin
Add('Mice');
Add('Goats');
Add('Elephants');
Add('Birds');
ListBox1.Items.AddStrings(MyList);
end;
finally
MyList.Free;
end;
The Code Editor displays, with the cursor in the TForm1.ToUpperClick event handler block.
3 Place the cursor before the begin reserved word; then press return.
5 Insert the cursor within the code block, and type the following code:
for Index := 0 to ListBox1.Items.Count - 1 do
ListBox1.Items[Index] := UpperCase(ListBox1.Items[Index]);
The strings 'Mice', 'Goats', 'Elephants', and 'Birds' display in the order listed.
3 Click the ToUpper button.
705
706
707
To lock objects
1 For objects such as canvas that have a Lock method, call the Lock method, as necessary, to prevent other
objects from accessing the object, and call Unlock when locking is no longer required.
2 Call TThreadList.LockList to block threads from using the list object TThreadList, and call
The following code has a global critical section variable LockXY that blocks access to the global variables X and
Y. To use X or Y, a thread must surround that use with calls to the critical section such as shown here:
LockXY.Acquire;
try
Y := sin(X);
finally
LockXY.Release
end;
Warning: Critical sections only work if every thread uses them to access global memory. Otherwise, problems of
simultaneous access can occur.
708
Warning: The multi-read exclusive-write synchronizer only works if every thread uses it to access the associated
global memory. Otherwise, problems of simultaneous access can occur.
709
New
Other
Delphi Projects
Tip:
Entering a name for Named Thread makes it easier to track the thread while debugging.
4 Press OK.
The Code Editor displays the skeleton code for the thread object.
The code generated for the new unit will look like this if you named your thread class TMyThread.
unit Unit1;
interface
uses
Classes;
type
TMyThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
{ Important: Methods and properties of objects in visual components can only be
used in a method called using Synchronize, for example,
Synchronize(UpdateCaption);
and UpdateCaption could look like,
procedure TMyThread.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end; }
{ TMyThread }
procedure TMyThread.Execute;
begin
{ Place thread code here }
end;
end.
710
Adding a name for the thread generates additional code for the unit. It includes the Windows unit, adds the
procedure SetName, and adds the record TThreadNameInfo. The name is assigned to the ThreadNameInfo.
FName field in the record, as shown here:
unit Unit1;
interface
uses
Classes {$IFDEF MSWINDOWS} , Windows {$ENDIF};
type
TMyThread = class(TThread)
private
procedure SetName;
protected
procedure Execute; override;
end;
implementation
{ Important: Methods and properties of objects in visual components can only be
used in a method called using Synchronize, for example,
Synchronize(UpdateCaption);
and UpdateCaption could look like,
procedure TMyThread.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end; }
{$IFDEF MSWINDOWS}
type
TThreadNameInfo = record
FType: LongWord;
//
FName: PChar;
//
FThreadID: LongWord; //
FFlags: LongWord;
//
end;
{$ENDIF}
must be 0x1000
pointer to name (in user address space)
thread ID (-1 indicates caller thread)
reserved for future use, must be zero
{ 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 );
711
except
end;
{$ENDIF}
end;
procedure TMyThread.Execute;
begin
SetName;
{ Place thread code here }
end;
end.
712
Handling Exceptions
To handle exceptions in the thread function
1 Add a try...except block to the implementation of your Execute method.
2 Code the logic such as shown here:
procedure TMyThreadExecute;
begin
try
while not Terminated do
PerformSomeTask;
except
{do something with exceptions}
end;
end;
713
Initializing a Thread
To initialize a thread object
1 Assign a default thread priority.
2 Indicate when the thread is freed.
Use a high-priority to handle time critical tasks, and a low priority to perform other tasks.
Value
Priority
tpIdle
The thread executes only when the system is idle. Windows won't interrupt the other threads to
execute a thread with tpIdle priority.
tpLowest
tpLower
tpNormal
tpHigher
tpHighest
tpTimeCritical
2 Override the Create method of the thread class by adding a new constructor to the declaration.
3
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
Priority := tpIdle;
end;
4 Indicate whether the thread should be freed automatically when it finishes executing.
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.
714
in your application.
2 Call the routine from your thread's Synchronize method.
This is an example.
procedure TMyThread.PushTheButton
begin
Button1.Click;
end;
procedure TMyThread.Execute;
begin
...
Synchronize(PushThebutton);
...
end;
Synchronize waits for the main thread to enter the message loop and then executes the passed method.
Note:
Because Synchronize uses a message loop, it does not work in console applications. For
console applications, use other mechanisms, such as critical sections, to protect access to VCL
objects.
To call CheckSynchronize
1 Call CheckSynchronize periodically within the main thread to enable background threads to synchronize
715
Note: Use the threadvar section for global variables only. Do not use it for Pointer and Function variables or types
that use copy-on-write semantics, such as long strings.
716
The following example is an OnTerminate event handler that uses a global counter in a critical section to keep
track of the number of terminating threads. When Counter reaches 0, the handler calls the SetEvent method
to signal that all processes have terminated:
procedure TDataModule.TaskTerminateThread(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}
CounterRelease; {release the lock on the counter}
...
end;
The main thread initializes Counter, launches the task threads, and waits for the signal that they are all done
by calling the WaitFor method. WaitFor waits a specified time period for the signal to be set and returns one
of the values in the table below.
The following code example shows how the main thread launches the task threads and resumes when they have
completed.
717
Note: If you do not want to stop waiting for an event handler 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.
Terminated property.
2 This is one way to code the logic:
procedure TMyThread.Execute;
begin
while not Terminated do
PerformSomeTask;
end;
Meaning
wrSignaled
wrTimeout
wrAbandoned The event object was destroyed before the timeout period elapsed.
wrError
718
719
New
Other
To locate an image, you can search for *.bmp images on your local drive. Select a very small image such as an
icon. Copy it to your project directory, and click Open.
The image displays in the ImageList editor.
8 Click OK to close the editor.
The Code Editor displays with cursor in the code block of the ComboBox1DrawItem event handler.
3 Enter the following code for the event handler:
Combobox1.Canvas.FillRect(rect);
ImageList1.Draw(ComboBox1.Canvas, Rect.Left, Rect.Top, Index);
Combobox1.Canvas.TextOut(Rect.Left+ImageList1.Width+2,
720
Rect.Top, ComboBox1.Items[Index]);
Run.
The bitmap image and the text string display as a list item.
721
Renaming Files
Creating this VCL application consists of the following steps:
1 Create a project directory containing a file to rename.
2 Create a VCL Form with button and label controls.
3 Write the code to rename the file.
4 Run the application.
New
Other
The Code Editor displays, with the cursor in the TForm1.Button1Click event handler block.
3 At the cursor, type the following code:
if not RenameFile('MyFile.txt', 'YourFile.txt') then
Label1.Caption := 'Error renaming file!';
Note:
You cannot rename (move) a file across drives using RenameFile. You would need to first copy
the file and then delete the old one. In the runtime library, RenameFile is a wrapper around the
Windows API MoveFile function, so MoveFile will not work across drives either.
722
New
Other
The Code Editor displays, with the cursor in the TForm1.Button1Click event handler block.
3 Place the cursor before the begin reserved word; then press return.
723
New
Other
The Code Editor displays, with the cursor in the TForm1.Button1Click event handler block.
3 Place the cursor before the begin reserved word; then press return.
Note:
724
The strings 'Animals', 'Cars', and 'Flowers' display alphabetically in a list in the ListBox. The Label caption displays
the message string: 'Flowers has an index value of 2.'
725
New
Other and select the ActiveX page in the New Items dialog.
Modifications you make to the name update the Implementation Unit and Project Name. Leave
the remaining fields with default values.
6 Click OK.
The wizard generates the code needed to implement the ActiveX control and adds the code to the project. If the
project is already an ActiveX library, the wizard adds the control to the current project.
Note:
If the project is not already an ActiveX library, a Warning dialog displays and asks you if you
want to start a new ActiveX library project.
Dismiss the warning about debugging. The project builds and creates an OCX file in your project directory.
2 Choose Run
A dialog box displays a message indicating that registration was successful and it shows the path to the resulting
OCX file.
3 Click OK.
726
New
The Package - package.dpk dialog displays showing the files in the package and two required files: rtl.dcp and
vcl.dcp.
A dialog displays, indicating that the package has been installed. Click OK.
6 Click the X in the upper right corner of the Package - package.dpk dialog to close it.
New Application.
2 From the ActiveX page of the Tool Palette, locate your button and place it on the form.
727
your Microsoft Internet Explorer Web browser. Name this directory ActiveX_Deploy.
3 Choose File
New
Other and select the ActiveX page in the New Items dialog.
The wizard generates the code needed to implement the ActiveX control and adds the code to the project. If the
project is already an ActiveX library, the wizard adds the control to the current project.
Note:
If the project is not already an ActiveX library, a Warning dialog displays and asks you if you
want to start a new ActiveX library project.
The Code Editor opens with the cursor in place in the TActiveFormX.Button1Click event handler block.
Enter the following code at the cursor:
ShowMessage(Edit1.text);
4 Save the project files to your ActiveX directory.
728
Web Deploy.
729
New
Other
window. Leave some space on the form above the TWebBrowser to add a URL entry window.
If the window is not large enough to display a browser page in its entirety, the TWebBrowser component adds
scrollbars when you run the application and launch the browser window.
4 From the Standard page of the Tool Palette, place a TMemo component on the form.
With the TMemo component selected on the form, drag the handles to adjust the size to accommodate a userentered URL.
5 From the Standard page of the Tool Palette, place a Label component on the form.
6 Select the Label, and in the Object Inspector, enter URL: as the Label caption.
7 From the Standard page of the Tool Palette, place a TButton component on the form.
8 Select the Button, and in the Object Inspector, enter OK as the TButton caption.
The Code Editor displays, with the cursor in the Button1Click event handler block.
3 Type the following code:
WebBrowser1.Navigate(WideString(Memo1.Text));
2 Enter a URL to a Web page in the memo window; then click the button.
730
731
New
Other.
WebSnap folder.
3 Click OK.
Tip:
3 Click OK.
732
WebSnap folder.
733
New
Other.
Tip:
3 Click OK.
734
WebSnap folder.
2 Below the line <h2><%= Page.Title %></h2>, insert a line saying This is my first WebSnap
application.
3 Save the application.
Run.
An application window opens, and the COM server registers your WebSnap application with the Web Application
Debugger.
2 Close the application window.
3 Choose Tools
Your application appears in the browser with the text Hello World! This is my first WebSnap
application.
7 Close the Web browser to return to the IDE.
735
To register the server information application for the Web Application Debugger
1 Navigate to the bin directory of your Delphi 2005 installation.
2 Run serverinfo.exe.
3 Close the blank application window that opens.
This step only needs to be performed the first time you use the Web Application Debugger.
Run.
This displays the console window of the COM server that is your Web server application.
2 Close the blank application window that opens.
Your COM server is now registered so that the Web App debugger can access it.
736
Developer's Guide
Win32
737
738
739
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
740
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 fileall of the VCL objects, CLX objects, RTL sources, and project
filescan 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.
741
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.
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 Deploying Applications for specific information on deployment.
742
Description
VCL/RTL
Low-level classes and routines available for all VCL applications. VCL/RTL includes the runtime library (RTL) up
to and including the Classes unit.
DataCLX
Client data-access components. The components in DataCLX are a subset of the total available set of components
for working with databases. These components are used in cross-platform applications that access databases.
They can access data from a file on disk or from a database server using dbExpress.
NetCLX
Components for building Web Server applications. These include support for applications that use Apache or CGI
Web Servers.
VisualCLX Cross-platform GUI components and graphics classes. VisualCLX classes make use of an underlying crossplatform widget library (Qt).
WinCLX
Classes that are available only on the Windows platform. These include controls that are wrappers for native
Windows controls, database access components that use mechanisms (such as the Borland Database Engine
or ADO) that are not available on Linux, and components that support Windows-only technologies (such as COM,
NT Services, or control panel applets).
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 crossplatform application or use controls that are available in CLX applications, such as TLCDNumber.
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 Tool 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
743
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 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. Welldesigned 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 private properties and fields of the class and are commonly referred
to as member functions. Although most methods belong to an instance of a class, some methods belong instead to
the class type. These are called class methods.
744
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.
Types of Events
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.
745
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:
Class
Description
TObject
Signifies the base class and ultimate ancestor of everything in the VCL or CLX. TObject
encapsulates the fundamental behavior common to all VCL/CLX objects by introducing
methods that perform basic functions such as creating, maintaining, and destroying an
instance of an object.
Exception
Specifies the base class of all classes that relate to VCL exceptions. Exception provides a
consistent interface for error conditions, and enables applications to handle error conditions
gracefully.
TPersistent
Specifies the base class for all objects that implement publishable properties. Classes under
TPersistent deal with sending data to streams and allow for the assignment of classes.
TComponent
Specifies the base class for all components. Components can be added to the Tool
palette and manipulated at design time. Components can own other components.
TControl
Represents the base class for all controls that are visible at runtime. TControl is the common
ancestor of all visual components and provides standard visual controls like position and
cursor. This class also provides events that respond to mouse actions.
TWinControl or TWidgetControl Specifies the base class of all controls that can have keyboard focus. Controls under
TWinControl are called windowed controls while those under TWidgetControl are called
widgets.
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:
746
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.
747
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 Tool palette and be changed on the form.
Own and manage other components.
Load and save themselves.
Several methods introduced by TComponent dictate how components act during design time and what information
gets saved with the component. Streaming (the saving and loading of form files, which store information about the
property values of objects on a form) is introduced in this branch. Properties are persistent if they are published and
published properties are automatically streamed.
The TComponent branch also introduces the concept of ownership that is propagated throughout the component
library. Two properties support ownership: Owner and Components. Every component has an Owner property that
references another component as its owner. A component may own other components. In this case, all owned
components are referenced in the component's Components property.
The constructor for every component takes a parameter that specifies the new component's owner. If the passedin owner exists, the new component is added to that owner's Components list. Aside from using the Components
list to reference owned components, this property also provides for the automatic destruction of owned components.
As long as the component has an owner, it will be destroyed when the owner is destroyed. For example, since
TForm is a descendant of TComponent, all components owned by a form are destroyed and their memory freed
when the form is destroyed. (Assuming, of course, that the components have properly designed destructors that
clean them up correctly.)
If a property type is a TComponent or a descendant, the streaming system creates an instance of that type when
reading it in. If a property type is TPersistent but not TComponent, the streaming system uses the existing instance
available through the property and reads values for that instance's properties.
Some of the classes in the TComponent branch include:
TActionList, a class that maintains a list of actions, which provides an abstraction of the responses your program
can make to user input.
TMainMenu, a class that provides a menu bar and its accompanying drop-down menus for a form.
TOpenDialog, TSaveDialog, TFontDialog, TFindDialog, TColorDialog, and so on, classes that display and
gather information from commonly used dialog boxes.
TScreen, a class that keeps track of the forms and data modules that an application creates, the active form,
the active control within that form, the size and resolution of the screen, and the cursors and fonts available for
the application to use.
Components that do not need a visual interface can be derived directly from TComponent. To make a tool such as
a TTimer device, you can derive from TComponent. This type of component resides on the Tool palette but performs
internal functions that are accessed through code rather than appearing in the user interface at runtime.
See Working with components for details on setting properties, calling methods, and working with events for
components.
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,
748
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 publishedand hence
appear in the Object Inspectorfor 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 graphics controls.
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 Windowsonly 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.
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.
749
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 objectsunlike recordscontain 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.
750
The combination of data and functionality in a single unit is called encapsulation. In addition to encapsulation, objectoriented 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.
The new class type is TForm1, and it is derived from type TForm, which is also a class.
A class is like a record in that they both contain data fields, but a class also contains methodscode that acts on
the object's data. So far, TForm1 appears to contain no fields or methods, because you haven't added any
components (the fields of the new object) to the form and you haven't created any event handlers (the methods of
the new object). TForm1 does contain inherited fields and methods, even though you don't see them in the type
declaration.
This variable declaration declares a variable named Form1 of the new type TForm1.
var
Form1: TForm1;
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:
A simple form
751
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.
752
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;
753
TForm1 = class(TForm)
A derived class inherits all the properties, events, and methods of the class from which it derives. The derived class
is called a descendant and the class from which it derives is called an ancestor. If you look up TForm in the online
Help, you'll see lists of its properties, events, and methods, including the ones that TForm inherits from its ancestors.
A Delphi class can have only one immediate ancestor, but it can have many direct descendants.
You don't need to qualify Color with Form1 because the Button1Click method is part of TForm1; identifiers in the
method body therefore fall within the scope of the TForm1 instance where the method is called. The second
statement, in contrast, refers to the color of the button object (not of the form where the event handler is declared),
so it requires qualification.
The IDE creates a separate unit (source code) file for each form. If you want to access one form's components from
another form's unit file, you need to qualify the component names, like this:
Form2.Edit1.Color := clLime;
In the same way, you can access a component's methods from another form. For example,
Form2.Edit1.Clear;
To access Form2's components from Form1's unit file, you must also add Form2's unit to the uses clause of
Form1's unit.
The scope of a class extends to its descendants. You can, however, redeclare a field, property, or method in a
descendant class. Such redeclarations either hide or override the inherited member.
For more information about scope, see Blocks and scope. For more information about the uses clause, see Unit
references and the uses clause. For more information about hiding and overriding inherited members, see Classes
and Objects.
754
The public section declares fields and methods with no access restrictions. Class instances and descendant
classes can access these fields and methods. A public member is accessible from wherever the class it belongs
to is accessiblethat is, from the unit where the class is declared and from any unit that uses that unit.
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 Visibility of class members.
755
AForm is of type TForm, and SimpleForm is of type TSimpleForm. Because TSimpleForm is a descendant of
TForm, this assignment statement is legal:
AForm := SimpleForm;
Suppose you write an event handler for the OnClick event of a button. When the button is clicked, the event handler
for the OnClick event is called. Each event handler has a Sender parameter of type TObject:
procedure TForm1.Button1Click(Sender: TObject);
begin
.
.
.
end;
Because Sender is of type TObject, any object can be assigned to Sender. The value of Sender is always the control
or component that responds to the event. You can test Sender to find the type of component or control that called
the event handler using the reserved word is. For example,
if Sender is TEdit then
DoSomething
else
DoSomethingElse;
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
756
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;
If no parent class name is specified, the class inherits directly from TObject. TObject defines only a handful of
methods, including a basic constructor and destructor.
757
To define a class:
1 In the IDE, start with a project open and choose File
New
and events.
TMyClass = class; {This implicitly descends from TObject}
public
.
.
.
private
.
.
.
published {If descended from TPersistent or below}
.
.
.
If you want the class to descend from a specific class, you need to indicate that class in the definition:
TMyClass = class(TParentClass); {This descends from TParentClass}
For example:
type TMyButton = class(TButton)
property Size: Integer;
procedure DoSomething;
end;
4 Some editions of the IDE include a feature called class completion that simplifies the work of defining and
implementing new classes by generating skeleton code for the class members you declare. If you have code
completion, invoke it to finish the class declaration: place the cursor within a method definition in the interface
section and press Ctrl+Shift+C (or right-click and select Complete Class at Cursor). Any unfinished property
declarations are completed, and for any methods that require an implementation, empty methods are added to
the implementation section.
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.
758
{ 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
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 Class types..
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;
759
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.
Whether or not the two classes share a common ancestor, they are still assignment compatible with a variable of
IPaint as in
var
Painter: IPaint;
begin
Painter := TSquare.Create;
Painter.Paint;
Painter := TCircle.Create;
Painter.Paint;
end;
This could have been accomplished by having TCircle and TSquare descend from a common ancestor (say,
TFigure), which declares a virtual method Paint. Both TCircle and TSquare would then have overridden the Paint
method. In the previous example, IPaint could be replaced by TFigure. However, consider the following interface:
760
IRotate = interface
procedure Rotate(Degrees: Integer);
end;
IRotate makes sense for the rectangle but not the circle. The classes would look like
TSquare = class(TRectangularObject, IPaint, IRotate)
procedure Paint;
procedure Rotate(Degrees: Integer);
end;
TCircle = class(TCustomShape, IPaint)
procedure Paint;
end;
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 and Memory management of interface objects.
RotateObjects does not require that the objects know how to paint themselves and PaintObjects does not require
the objects know how to rotate. This allows the generic procedures to be used more often than if they were written
to only work against a TFigure class.
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.
761
_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,
which also discusses writing your own classes that implement interfaces but that do not follow the reference-counting
mechanism inherent in TInterfacedObject.
762
begin
X := P as IPaint;
{ statements }
end;
the variable P of type TInterfacedObject, can be assigned to the variable X, which is an IPaint interface reference.
Dynamic binding makes this assignment possible. For this assignment, the compiler generates code to call the
QueryInterface method of P's IInterface interface. This is because the compiler cannot tell from P's declared type
whether P's instance actually supports IPaint. At runtime, P either resolves to an IPaint reference or an exception is
raised. In either case, assigning P to X will not generate a compile-time error as it would if P was of a class type that
did not implement IInterface.
When you use the as operator for dynamic binding on an interface, you should be aware of the following
requirements:
Explicitly declaring IInterface: Although all interfaces derive from IInterface, it is not sufficient, if you want to use
the as operator, for a class to simply implement the methods of IInterface. This is true even if it also implements
the interfaces it explicitly declares. The class must explicitly declare IInterface in its interface list.
Using an IID: Interfaces can use an identifier that is based on a GUID (globally unique identifier). GUIDs that
are used to identify interfaces are referred to as interface identifiers (IIDs). If you are using the as operator with
an interface, it must have an associated IID. To create a new GUID in your source code you can use the Ctrl
+Shift+G editor shortcut key.
763
For more information about the syntax, implementation details, and language rules of the implements keyword, see
Object interfaces.
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."
764
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.
This is the cleanest and safest approach to memory management; and if you use TInterfacedObject it is handled
automatically. If you do not follow this rule, your object can unexpectedly disappear, as demonstrated in the following
code:
function test_func()
var
x: TTest;
begin
x := TTest.Create; // no count on the object yet
beep(x as ITest); // count is incremented by the act of calling beep
// and decremented when it returns
x.something; // surprise, the object is gone
end;
Note: In the examples above, the beep procedure, as it is declared, increments the reference count (call
_AddRef) on the parameter, whereas either of the following declarations do not:
procedure beep(const x: ITest);
or
765
TMyObject._AddRef: Integer;
:= -1;
TMyObject._Release: Integer;
:= -1;
You would still implement QueryInterface as usual to provide dynamic querying on your object.
Note that, because you implement QueryInterface, you can still use the as operator for interfaces, as long as you
create an interface identifier (IID). You can also use aggregation. If the outer object is a component, the inner object
implements reference counting as usual, by delegating to the "controlling Unknown." It is at the level of the outer
object that the decision is made to circumvent the _AddRef and _Release methods, and to handle memory
management via another approach. In fact, you can use TInterfacedObject as a base class for an inner object of an
aggregation that has a as its containing outer object one that does not follow the interface lifetime model.
Note: The "controlling Unknown" is the IUnknown implemented by the outer object and the one for which the
reference count of the entire object is maintained. IUnknown is the same as IInterface, but is used instead in
COM-based applications (Windows only). For more information distinguishing the various implementations
of the IUnknown or IInterface interface by the inner and outer objects, see Aggregation and the Microsoft
online Help topics on the "controlling Unknown."
766
New versions of old interfaces, as well as any new interfaces or features of an object, can become immediately
available to new clients. At the same time, objects retain complete compatibility with existing client code; no
recompilation is necessary because interface implementations are hidden (while the methods and parameters
remain constant). In COM applications, developers can change the implementation to improve performance, or for
any internal reason, without breaking any client code that relies on that interface. For more information about COM
interfaces, see Overview of COM technologies.
When distributing an application using SOAP, interfaces are required to carry their own runtime type information
(RTTI). The compiler only adds RTTI to an interface when it is compiled using the {$M+} switch. Such interfaces are
called invokable interfaces. The descendant of any invokable interface is also invokable. However, if an invokable
interface descends from another interface that is not invokable, client applications can only call the methods defined
in the invokable interface and its descendants. Methods inherited from the non-invokable ancestors are not compiled
with type information and so can't be called by clients.
The easiest way to define invokable interfaces is to define your interface so that it descends from IInvokable.
IInvokable is the same as IInterface, except that it is compiled using the {$M+} switch. For more information about
Web Service applications that are distributed using SOAP, and about invokable interfaces, see Using Web Services.
767
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.
768
The following topics describe the methods common to all stream classes:
Using streams to read or write data
Copying data from one stream to another
Specifying the stream position and size
Read is useful when the number of bytes in the file is not known. Read returns the number of bytes actually
transferred, which may be less than Count if the stream did not contain Count bytes of data past the current position.
The Write method writes Count bytes from a buffer to the stream, starting at the current Position. The prototype for
Write is:
function Write(const Buffer; Count: Longint): Longint;
After writing to the file, Write advances the current position by the number bytes written, and returns the number of
bytes actually written, which may be less than Count if the end of the buffer is encountered or the stream can't accept
any more bytes.
The counterpart procedures are ReadBuffer and WriteBuffer which, unlike Read and Write, do not return the number
of bytes read or written. These procedures are useful in cases where the number of bytes is known and required,
for example when reading in structures. ReadBuffer and WriteBuffer raise an exception (EReadError and
EWriteError) if the byte count can not be matched exactly. This is in contrast to the 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.
ReadComponent and WriteComponent are the methods that the IDE uses to read components from or write them
to form files. When streaming components to or from a form file, stream classes work with the TFiler classes,
TReader and TWriter, to read objects from the form file or write them out to disk. For more information about using
the component streaming system, see TStream, TFiler, TReader, TWriter, and TComponent classes.
770
Both overloads work the same way. The difference is that one version uses a 32-bit integer to represent positions
and offsets, while the other uses a 64-bit integer.
The Origin parameter indicates how to interpret the Offset parameter. Origin should be one of the following values:
Values for the Origin parameter
Value
Meaning
soFromBeginning Offset is from the beginning of the resource. Seek moves to the position Offset. Offset must be >= 0.
soFromCurrent
Offset is from the current position in the resource. Seek moves to Position + Offset.
soFromEnd
Offset is from the end of the resource. Offset must be <= 0 to indicate a number of bytes before the end
of the file.
Seek resets the current stream position, moving it by the indicated offset. Seek returns the new current position in
the stream.
The Size property indicates the size of the stream in bytes. It can be used to determine the number of bytes available
for reading, or to truncate the data in the stream. The declaration for Size is:
property Size: Int64;
Size is used internally by routines that read and write to and from the stream.
Setting the Size property changes the size of the data in the stream. For example, on a file stream, setting Size
inserts an end of file marker to truncate the file. If the Size of the stream cannot be changed, an exception is raised.
For example, trying to change the Size of a read-only file stream raises an exception.
771
772
The Mode parameter specifies how the file should be opened when creating the file stream. The Mode parameter
consists of an open mode and a share mode OR'ed together. The open mode must be one of the following values:
Open modes
Value
Meaning
fmCreate
TFileStream a file with the given name. If a file with the given name exists, open the file in write mode.
fmOpenRead
fmOpenWrite
Open the file for writing only. Writing to the file completely replaces the current contents.
fmOpenReadWrite Open the file to modify the current contents rather than replace them.
The share mode can be one of the following values with the restrictions listed below:
Share modes
Value
Meaning
fmShareCompat
Sharing is compatible with the way FCBs are opened (VCL applications only).
fmShareExclusive
Other applications can not open the file for any reason.
fmShareDenyWrite Other applications can open the file for reading but not for writing.
fmShareDenyRead Other applications can open the file for writing but not for reading (VCL applications only).
fmShareDenyNone No attempt is made to prevent other applications from reading from or writing to the file.
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.
Shared modes available for each open mode
Open Mode
fmShareCompat
(VCL)
fmShareDenyNone
fmOpenRead
Can't use
Can't use
Available
Can't use
Available
fmOpenWrite
Available
Available
Can't use
Available
Available
fmOpenReadWrite Available
Available
Available
Available
Available
The file open and share mode constants are defined in the SysUtils unit.
773
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.
Warning: 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.
The following topics describe how to use runtime library routines to perform file manipulation tasks:
Deleting a file
Finding a file
Renaming a file
File date-time routines
Copying a file
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 readonly). 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;
774
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:
Attribute constants and values
Constant
Value
Description
faSysFile
faArchive
faAnyFile
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
Options
Environment Options) to represent them.
775
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.
Copying a File
The 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.
776
classes for working with the system Registry. Two of these classes, TRegistryIniFile and TRegistry, are discussed
here because of their similarity to the classes for working with ini files.
TRegistryIniFile is useful for cross-platform applications, because it shares a common ancestor (TCustomIniFile)
with the classes that work with ini files. If you confine yourself to the methods of the common ancestor
(TCustomIniFile) your application can work on both applications with a minimum of conditional code.
TRegistryIniFile is discussed in Using TRegistryIniFile.
For applications that are not cross-platform, you can use the TRegistry class. The properties and methods of
TRegistry have names that correspond more directly to the way the system Registry is organized, because it does
not need to be compatible with the classes for ini files. TRegistry is discussed in Using TRegistry.
777
TIniFile.Free;
end;
end;
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.
The following example retrieves a value from a registry entry:
function GetRegistryValue(KeyName: string): string;
var
Registry: TRegistry;
begin
Registry := TRegistry.Create(KEY_READ);
778
try
Registry.RootKey = HKEY_LOCAL_MACHINE;
// False because we do not want to create it if it doesn't exist
Registry.OpenKey(KeyName, False);
Result := Registry.ReadString('VALUE1');
finally
Registry.Free;
end;
end;
Maintains
TList
A list of pointers
TThreadList
TBucketList
TComponentList
A memory-managed list of components (that is, instances of classes descended from TComponent)
TClassList
TInterfaceList
TQueue
TStack
TObjectQueue
TObjectStack
TCollection
TStringList
A list of strings
THashedStringList A list of strings with the form Name=Value, hashed for performance.
779
case of collections, Add takes no parameters, but creates a new item that it adds. The Add method on collections
returns the item it added, so that you can assign values to the new item's properties.
Some list classes have an Insert method in addition to the Add method. Insert works the same way as the Add
method, but has an additional parameter that lets you specify the position in the list where you want the new item
to appear. If a class has an Add method, it also has an Insert method unless the position of items is predetermined
For example, you can't use Insert with sorted lists because items must go in sort order, and you can't use Insert with
bucket lists because the hash algorithm determines the item position.
The only classes that do not have an Add method are the ordered lists. Ordered lists are queues and stacks. To add
items to an ordered list, use the Push method instead. Push, like Add, takes an item as a parameter and inserts it
in the correct position.
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
780
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.
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.
781
procedure EditWinIni;
var FileName: string;{ storage for file name }
begin FileName := 'c:\Program Files\MyProgram\MyFile.ini'{ set the file name }
with Form1.Memo1.Lines do
begin
LoadFromFile(FileName);{ load from file }
SaveToFile(ChangeFileExt(FileName, '.bak'));{ save into backup file }
end;
end;
The following event handler responds to a button click by constructing a string list, using it, and then destroying it.
procedure TForm1.Button1Click(Sender: TObject);
var TempList: TStrings;{ declare the list }
begin
TempList := TStringList.Create;{ construct the list object }
try
{ use the string list }
finally
TempList.Free;{ destroy the list object }
end;
end;
782
2 Write an event handler for the main form's OnCreate event that executes before the form appears. It should
create a string list and assign it to the field you declared in the first step.
3 Write an event handler that frees the string list for the form's OnClose event.
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}
procedure TForm1.FormCreate(Sender: TObject);begin ClickList := TStringList.Create;
{ construct the list }end;
procedure TForm1.FormDestroy(Sender: TObject);begin ClickList.SaveToFile(ChangeFileExt
(Application.ExeName, '.log'));{ save the list }
ClickList.Free;{ destroy the list object }end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
begin ClickList.Add(Format('Click at (%d, %d)', [X, Y]));{ add a string to the list }end;
end.
783
is equivalent to
StringList1[0] := 'This is the first string.';
Index: Integer;
ListBox1.Items[Index] := UpperCase
To append the strings from one list onto another, call AddStrings:
StringList1.AddStrings(StringList2);
784
Delete(BIndex);
copies the lines from a combo box into a memo (overwriting the memo), while
Memo1.Lines.AddStrings(ComboBox1.Items);
the original string-list object will be lost, often with unpredictable results.
785
The following topics provide an overview of many of the string-handling routines in the runtime library:
Wide character routines
Commonly used long string routines
Commonly used routines for null-terminated strings
786
Finally, some routines include overloads for working with wide strings:
UniqueString
Length
Trim
TrimLeft
TrimRight
Case-sensitive
AnsiCompareStr
yes
yes
yes
AnsiCompareText
no
yes
yes
yes
AnsiMatchStr
yes
yes
yes
AnsiMatchText
no
yes
yes
AnsiContainsStr
yes
yes
yes
AnsiContainsText
no
yes
yes
787
AnsiStartsStr
yes
yes
yes
AnsiStartsText
no
yes
yes
AnsiEndsStr
yes
yes
yes
AnsiEndsText
no
yes
yes
AnsiIndexStr
yes
yes
yes
AnsiIndexText
no
yes
yes
CompareStr
yes
no
no
CompareText
no
no
no
AnsiResemblesText
no
no
no
AnsiLowerCase
yes
yes
AnsiLowerCaseFileName yes
yes
AnsiUpperCaseFileName yes
yes
AnsiUpperCase
yes
yes
LowerCase
no
no
UpperCase
no
no
Note: The routines used for string file names: AnsiCompareFileName, AnsiLowerCaseFileName, and
AnsiUpperCaseFileName all use the system locale. You should always use file names that are portable
because the locale (character set) used for file names can and might differ from the default user interface.
String modification routines:
Routine
AdjustLineBreaks NA
yes
AnsiQuotedStr
NA
yes
AnsiReplaceStr
yes
yes
AnsiReplaceText no
yes
StringReplace
ReverseString
NA
no
StuffString
NA
no
Trim
NA
yes
TrimLeft
NA
yes
TrimRight
NA
yes
WrapText
NA
yes
Sub-string routines:
Routine
AnsiExtractQuotedStr NA
yes
788
AnsiPos
yes
yes
IsDelimiter
yes
yes
IsPathDelimiter
yes
yes
LastDelimiter
yes
yes
LeftStr
NA
no
RightStr
NA
no
MidStr
NA
no
QuotedStr
no
no
AnsiStrComp
yes
yes
yes
AnsiStrIComp
no
yes
yes
AnsiStrLComp yes
yes
yes
AnsiStrLIComp no
yes
yes
StrComp
yes
no
no
StrIComp
no
no
no
StrLComp
yes
no
no
StrLIComp
no
no
no
AnsiStrLower yes
yes
AnsiStrUpper yes
yes
StrLower
no
no
StrUpper
no
no
789
Routine
StrCat
StrLCat
Sub-string routines
Routine
AnsiStrPos
yes
yes
AnsiStrScan
yes
yes
AnsiStrRScan yes
yes
StrPos
yes
no
StrScan
yes
no
StrRScan
yes
no
StrCopy
StrLCopy
StrECopy
StrMove
StrPCopy
StrPLCopy
you do not need to initialize it. Long strings are automatically initialized to empty. To test a string for empty you can
either use the EmptyStr variable:
S = EmptyStr;
An empty string has no valid data. Therefore, trying to index an empty string is like trying to access nil and will result
in an access violation:
var
S: string;
begin
S[i];
// this will cause an access violation
790
// statements
end;
Similarly, if you cast an empty string to a PChar, the result is a nil pointer. So, if you are passing such a PChar to a
routine that needs to read or write to it, be sure that the routine can handle nil:
var
S: string;
// empty string
begin
proc(PChar(S)); // be sure that proc can handle nil
// statements
end;
When you use SetLength, existing characters in the string are preserved, but the contents of any newly allocated
space is undefined. Following a call to SetLength, S is guaranteed to reference a unique string, that is a string with
a reference count of one. To obtain the length of a string, use the Length function.
Remember when declaring a string that:
S: string[n];
implicitly declares a short string, not a long string of n length. To declare a long string of specifically n length, declare
a variable of type string and use the SetLength procedure.
S: string;
SetLength(S, n);
791
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;
This example returns a pointer to string data that is freed when the title function returns.
792
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;
{$H+/-}
A compiler directive, $H, controls whether the reserved word string represents a short string or a long string. In the
default state, {$H+}, string represents a long string. You can change it to a ShortString by using the {$H-} directive.
{$P+/-}
The $P directive is meaningful only for code compiled in the {$H-} state, and is provided for backwards compatibility.
$P controls the meaning of variable parameters declared using the string keyword in the {$H-} state.
In the {$P-} state, variable parameters declared using the string keyword are normal variable parameters, but in
the {$P+} state, they are open string parameters. Regardless of the setting of the $P directive, the OpenString
identifier can always be used to declare open string parameters.
{$V+/-}
The $V directive controls type checking on short strings passed as variable parameters. In the {$V+} state, strict
type checking is performed, requiring the formal and actual parameters to be of identical string types.
793
In the {$V-} (relaxed) state, any short string type variable is allowed as an actual parameter, even if the declared
maximum length is not the same as that of the formal parameter. Be aware that this could lead to memory corruption.
For example:
var S: string[3];
procedure Test(var T: string);
begin
T := '1234';
end;
begin
Test(S);
end.
{$X+/-}
The {$X+} compiler directive enables support for null-terminated strings by activating the special rules that apply to
the built-in PChar type and zero-based character arrays. (These rules allow zero-based arrays and character
pointers to be used with Write, Writeln, Val, Assign, and Rename from the System unit.)
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 failsafe and easy.
Printing
The VCL TPrinter object encapsulates details of Windows printers. 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.
794
Converting Measurements
The ConvUtils unit declares a general-purpose Conversion 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.
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 RegisterConversionTypeand
RegisterConversionFamily functions. For information on extending conversion and conversion units, see Adding
new measurement types.
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.
795
The units you're converting must be in the same conversion family (they must measure the same thing). If the units
are not compatible, Convert raises an EConversionError exception. You can check whether two TConvType values
are in the same conversion family by calling CompatibleConversionTypes.
The StdConvs unit defines several families of TConvType values. See Conversion family variables 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:
796
var
cbLongTime: TConvFamily;
ltMonths: TConvType;
ltYears: TConvType;
ltDecades: TConvType;
ltCenturies: TConvType;
ltMillennia: TConvType;
Although an UnregisterConversionFamily procedure is provided, you don't need to unregister conversion families
unless the unit that defines them is removed at runtime. They are automatically cleaned up when your application
shuts down.
797
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.
798
begin
Result := AValue - 273.15;
end;
function CelsiusToKelvin(const AValue: Double): Double;
begin
Result := AValue + 273.15;
end;
799
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.
800
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;
To register each conversion type, create an instance of the conversion class that reflects the factor and rounding
properties of that currency, and call the RegisterConversionType method:
var
LInfo: TConvTypeInfo;
begin
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euEUR)
LInfo.Free;
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euBEF)
LInfo.Free;
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euDEM)
LInfo.Free;
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euGRD)
LInfo.Free;
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euESP)
LInfo.Free;
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euFFR)
LInfo.Free;
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euIEP)
LInfo.Free;
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euITL)
LInfo.Free;
801
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euLUF)
LInfo.Free;
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euNLG)
LInfo.Free;
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euATS)
LInfo.Free;
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euPTE)
LInfo.Free;
LInfo := TConvTypeEuroFactor.Create(cbEuro,
if not RegisterConversionType(LInfo, euFIM)
LInfo.Free;
end;
Note: The ConvertIt demo provides an expanded version of this example that includes other currencies (that do
not have fixed conversion rates) and more error checking.
The above steps extend the Variant type so that the standard operators work with your new type and the new Variant
type can be cast to other data types. You can further enhance your new Variant type so that it supports properties
and methods that you define. When creating a Variant type that supports properties or methods, you use
TInvokeableVariantType or TPublishableVariantType as a base class rather than TCustomVariantType.
802
This type is exactly the same size as the TVarData record. When working with a custom variant of the new type, the
variant (or its TVarData record) can be cast to TConvertVarData, and the custom Variant type simply works with the
TVarData record as if it were a TConvertVarData type.
Note: When defining a record that maps onto the TVarData record in this way, be sure to define it as a packed
record.
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.
803
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):
804
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.
805
TComplexVarData(Left).VComplex.DoDivide(TComplexVarData(Right).VComplex);
else
RaiseInvalidOp;
end
else
RaiseInvalidOp;
end
else
RaiseInvalidOp;
end;
The addition operator is implemented for a string and a complex number (by casting the complex value to a string
and concatenating), and the addition, subtraction, multiplication, and division operators are implemented for two
complex numbers using the methods of the TComplexData object that is stored in the complex variant's data. This
is accessed by casting the TVarData record to a TComplexVarData record and using its VComplex member.
Attempting any other operator or combination of types causes the method to call the RaiseInvalidOp method, which
causes a runtime error. The TCustomVariantType class includes a number of utility methods such as
RaiseInvalidOp that can be used in the implementation of custom variant types.
BinaryOp only deals with a limited number of types: strings and other complex variants. It is possible, however, to
perform operations between complex numbers and other numeric types. For the BinaryOp method to work, the
operands must be cast to complex variants before the values are passed to this method. We have already seen
(above) how to use the RightPromotion method to force the right-hand operand to be a complex variant if the lefthand operand is complex. A similar method, LeftPromotion, forces a cast of the left-hand operand when the righthand operand is complex:
function TComplexVariantType.LeftPromotion(const V: TVarData;
const Operator: TVarOp; out RequiredVarType: TVarType): Boolean;
begin
{ TypeX Op Complex }
if (Operator = opAdd) and VarDataIsStr(V) then
RequiredVarType := varString
else
RequiredVarType := VarType;
Result := True;
end;
This LeftPromotion method forces the left-hand operand to be cast to another complex variant, unless it is a string
and the operation is addition, in which case LeftPromotion allows the operand to remain a string.
806
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.
807
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.
Note that for the logical not operator, which does not make sense for complex values, this method calls
RaiseInvalidOp to cause a runtime error.
808
Note: The Indirect parameter in the Copy method signals that the copy must take into account the case when the
variant holds only an indirect reference to its data.
Tip: If your custom variant type does not allocate any memory to hold its data (if the data fits entirely in the
TVarData record), your implementation of the Copy method can simply call the SimplisticCopy method.
To indicate how to clear the variant's value, implement the Clear method. As with the Copy method, the only tricky
thing about doing this is ensuring that you free any resources allocated to store the variant's data:
procedure TComplexVariantType.Clear(var V: TVarData);
begin
V.VType := varEmpty;
FreeAndNil(TComplexVarData(V).VComplex);
end;
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;
809
begin
VComplex := TComplexData.Create;
VComplex.Real := ReadFloat;
VComplex.Imaginary := ReadFloat;
end;
finally
Free;
end;
end;
procedure TComplexVariantType.StreamOut(const Source: TVarData; const Stream: TStream);
begin
with TWriter.Create(Stream, 1024) do
try
with TComplexVarData(Source).VComplex do
begin
WriteFloat(Real);
WriteFloat(Imaginary);
end;
finally
Free;
end;
end;
Note how these methods create a Reader or Writer object for the Stream parameter to handle the details of reading
or writing values.
In the finalization section of the unit that defines your TCustomVariantType descendant, free the instance of your
class. This automatically unregisters the variant type. Here is the finalization section of the VarCmplx unit:
finalization
FreeAndNil(ComplexVariantType);
810
This function does not actually exist in the VarCmplx unit, but is a synthesis of methods that do exist, provided to
simplify the example. Note that the returned variant is cast to the record that was defined to map onto the
TVarData structure (TComplexVarData), and then filled out.
Another useful utility to create is one that returns the variant type code for your new Variant type. This type code is
not a constant. It is automatically generated when you instantiate your TCustomVariantType descendant. It is
therefore useful to provide a way to easily determine the type code for your custom variant type. The following
function from the VarCmplx unit illustrates how to write one, by simply returning the VarType property of the
TCustomVariantType descendant:
function VarComplex: TVarType;
begin
Result := ComplexVariantType.VarType;
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
811
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
812
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, 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.
813
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;
4 At the cursor, type the code that you want to execute when the event occurs.
(The list includes only event handlers written for events of the same name on the same form.) Select from the
list by clicking an event-handler name.
The previous procedure is an easy way to reuse event handlers. Action lists and in the VCL, action bands, however,
provide powerful tools for centrally organizing the code that responds to user commands. Action lists can be used
in cross-platform applications, whereas action bands cannot.
depending on which component calls it. You can do this by using the Sender parameter in an if...then...else
statement. For example, the following code displays the title of the application in the caption of a dialog box only if
the OnClick event was received by Button1.
procedure TMainForm.Button1Click(Sender: TObject);
begin
if Sender = Button1 then
AboutBox.Caption := 'About ' + Application.Title
else
AboutBox.Caption := '';
AboutBox.ShowModal;
end;
a list of previously written event handlers. (The list includes only event handlers written for OnClick events on
this form.) Select from the list by clicking an event handler name.
817
Description
Cross-platform?
ActiveX
No
Additional
Specialized controls.
ADO
BDE
COM+
Data Access
Data Controls
dbExpress
Yes
DataSnap
No
Decision Cube
No
818
No
Dialogs
Indy Clients
Indy Servers
Indy Misc
Indy Intercepts
Indy I/O Handlers
InterBase
Yes
InterBaseAdmin
Yes
Internet
Yes
InternetExpress
Office2K
IW Client Side
IW Control
No
IW Data
IW Standard
Rave
Samples
Servers
Standard
System
Components and controls for system- The components are different between a VCL
level access, including timers,
and CLX application.
multimedia, and DDE (VCL
applications).
No
Yes
Yes
WebSnap
Yes
Win 3.1
No
819
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 Tool palette, see online Help. You can press F1 on the Tool
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 Tool 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.
820
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
821
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 dragover 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;
822
True. When AutoSize is True, the dock site is sized to 0 until it accepts a child control for docking. Then it resizes
to fit around the child control.
Note: Drag-and-dock properties are not available in CLX applications.
docking site or undocks the control so that it becomes a floating window. When DragKind is dkDrag (the default),
dragging the control starts a drag-and-drop operation which must be implemented using the OnDragOver,
OnEndDrag, and OnDragDrop events.
2 Set its DragMode to dmAutomatic. When DragMode is dmAutomatic, dragging (for drag-and-drop or docking,
depending on DragKind) is initiated automatically when the user starts dragging the control with the mouse. When
DragMode is dmManual, you can still begin a drag-and-dock (or drag-and-drop) operation by calling the
BeginDrag method.
3 Set its FloatingDockSiteClass property to indicate the TWinControl descendant that should host the control when
it is undocked and left as a floating window. When the control is released and not over a docking site, a windowed
control of this class is created dynamically, and becomes the parent of the dockable child. If the dockable child
control is a descendant of TWinControl, it is not necessary to create a separate floating dock site to host the
control, although you may want to specify a form in order to get a border and title bar. To omit a dynamic container
window, set FloatingDockSiteClass to the same class as the control, and it will become a floating window with
no parent.
Note: Drag-and-dock properties are not available in CLX applications.
824
InfluenceRect:
OnGetSiteInfo occurs on the docking site when the user drags a dockable child over the control. It allows the site to
indicate whether it will accept the control specified by the DockClient parameter as a child, and if so, where the child
must be to be considered for docking. When OnGetSiteInfo occurs, InfluenceRect is initialized to the screen
coordinates of the docking site, and CanDock is initialized to True. A more limited docking region can be created by
changing InfluenceRect and the child can be rejected by setting CanDock to False.
property OnDockOver: TDockOverEvent;
TDockOverEvent = procedure(Sender: TObject; Source: TDragDockObject; X, Y:
TDragState; var Accept: Boolean) of object;
Integer; State:
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:
object;
Integer) of
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.
Note: Drag-and-dock properties are not available in CLX applications.
of object;
The Client parameter indicates the child control that is trying to undock, and the Allow parameter lets the docking
site (Sender) reject the undocking. When implementing an OnUnDock event handler, it can be useful to know what
other children (if any) are currently docked. This information is available in the read-only DockClients property, which
is an indexed array of TControl. The number of dock clients is given by the read-only DockClientCount property.
Note: Drag-and-dock properties are not available in CLX applications.
825
You can also use the HMargin property to adjust the left and right margins in a memo control.
826
wrapping is enabled. You might also check whether any text lines actually exceed the width of the control.
2 Set the rich edit or memo component's ScrollBars property to include or exclude scroll bars.
The rich edit and memo components handle their scroll bars in a slightly different way. The rich edit component can
hide its scroll bars if the text fits inside the bounds of the component. The memo always shows scroll bars if they
are enabled.
If there is already a uses clause in the implementation part, add Clipbrd to the end of it.
If there is not already a uses clause, add one that says
uses Clipbrd;
For example, in an application with a child window, the uses clause in the unit's implementation part might look like
this:
uses
MDIFrame, Clipbrd;
827
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.
The table below lists properties commonly used to handle selected text.
Properties of selected text
Property
Description
SelText
Contains the starting position of a string relative to the beginning of an edit control's text buffer.
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;
828
Cut, Edit
Copy,
829
Delete1.Enabled := HasSelection;
end;
The HasFormat method (Provides method in CLX applications) of the clipboard returns a Boolean value based on
whether the clipboard contains objects, text, or images of a particular format. By calling HasFormat with the
parameter CF_TEXT, you can determine whether the clipboard contains any text, and enable or disable the Paste
item as appropriate.
Note: In CLX applications, use the Provides method. In this case, the text is generic. You can specify the type of
text using a subtype such as text/plain for plain text or text/html for html.
component.
4 Attach handlers to the OnClick events of the pop-up menu items.
830
In the following code, the Edit1Click event handler described previously in Disabling menu items is attached to the
pop-up menu component's OnPopup event. A line of code is added to Edit1Click for each item in the pop-up menu.
procedure TEditForm.Edit1Click(Sender: TObject);
var
HasSelection: Boolean;
begin
Paste1.Enabled := Clipboard.HasFormat(CF_TEXT);
Paste2.Enabled := Paste1.Enabled;{Add this line}
HasSelection := Editor.SelLength <> 0;
Cut1.Enabled := HasSelection;
Cut2.Enabled := HasSelection;{Add this line}
Copy1.Enabled := HasSelection;
Copy2.Enabled := HasSelection;{Add this line}
Delete1.Enabled := HasSelection;
end;
831
Examples
Fixed
Each item is the same height, with that height determined lbOwnerDrawFixed,csOwnerDrawFixed
by the ItemHeight property.
Variable
Each item might have a different height, determined by the lbOwnerDrawVariable, csOwnerDrawVariable
data at runtime.
The image controls are invisible when you run the application. The image is stored with the form so it doesn't have
to be loaded from a file at runtime.
832
To draw the items in an owner-draw control, do the following for each visible item in the
control. Use a single event handler for all items.
1 Size the item, if needed.
Items of the same size (for example, with a list box style of lsOwnerDrawFixed), do not require sizing.
2 Draw the item.
Note: You must typecast the items from the Objects property in the string list. Objects is a property of type
TObject so that it can hold any kind of object. When you retrieve objects from the array, you need to typecast
them back to the actual type of the items.
834
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.
window can be opened within a single parent window. This is common in applications such as spreadsheets or word
processors.
For more information on developing the UI for an application, see Developing the application user interface.
SDI Applications
To create a new SDI application:
1 Choose File
New
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
New
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.
Open, File
836
Code Templates
Code 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 :=
begin
end;
to
do
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
Options
Editor Options.
2 Click the Source Options tab and then the Edit Code Templates button.
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}
New
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
837
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.
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.
1 Choose File
2 A Service window appears that corresponds to a service (TService). Implement the service by setting its
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).
838
Note: 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.
New
Other and select Service Application from the New Items dialog box. The Service1 window
appears.
2 From the Internet category of the Tool 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
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
839
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:
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.
Note: 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.
New
Other and double-click Service Application in the New Items dialog. The Service1 window
appears.
840
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
841
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.
842
To debug:
1 First create a key called Image File Execution Options in the following registry location:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
2 Create a subkey with the same name as your service (for example, MYSERV.EXE). To this subkey, add a value
of type REG_SZ, named Debugger. Use the full path to bds.exe as the string value.
3 In the Services control panel applet, select your service, click Startup and check Allow Service to Interact with
Desktop.
On Windows NT systems, you can use another approach for debugging service applications. However, this approach
can be tricky, because it requires short time intervals:
You must launch the service quickly (within 15-30 seconds of application startup) because the application will
terminate if no service is launched.
Description
{$LIBPREFIX 'string'}
Adds a specified prefix to the output file name. For example, you could specify {$LIBPREFIX 'dcl'}
for a design-time package, or use {$LIBPREFIX''} to eliminate the prefix entirely.
{$LIBSUFFIX 'string'}
Adds a specified suffix to the output file name before the extension. For example, use {$LIBSUFFIX
'-2.1.3'} in something.pas to generate something-2.1.3.bpl.
843
{$LIBVERSION 'string'} Adds a second extension to the output file name after the .bpl extension. For example, use
{$LIBVERSION '2.1.3'} in something.pas to generate something.bpl.2.1.3.
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 Working with packages and components.
Contents
BDE
Components that use the Borland Database Engine (BDE), a large API for interacting with databases. The
BDE supports the broadest range of functions and comes with the most supporting utilities including Database
Desktop and Database Explorer. See Using the Borland Database Engine for details.
ADO
Components that use ActiveX Data Objects (ADO), developed by Microsoft, to access database information.
Many ADO drivers are available for connecting to different database servers. ADO-based components let you
integrate your application into an ADO-based environment. See Working with ADO Components for details.
dbExpress
Cross-platform components that use dbExpress to access database information. dbExpress drivers provide
fast access to databases but need to be used with TClientDataSet and TDataSetProvider to perform updates.
See Using Unidirectional Datasets for details.
InterBase
Components that access InterBase databases directly, without going through a separate engine layer. For
more information about using the InterBase components, see Getting started with InterBase Express.
Data Access
Components that can be used with any data access mechanism such as TClientDataSet and
TDataSetProvider. See Using Client Datasets: Overview for information about client datasets. See Using
Provider Components for information about providers.
844
Data Controls Data-aware controls that can access information from a data source. See Using Data Controls for details.
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.
Refer to Designing database applications for details on what type of database support is available and considerations
when designing database client applications and application servers.
Note: Not all editions of Delphi include database support.
Description
ISAPI and NSAPI Web server applications are DLLs that are loaded by the Web
server. Client request information is passed to the DLL as a structure and
evaluated by TISAPIApplication. Each request message is handled in a
separate execution thread.
Selecting this type of application adds the library header of the project files and
required entries to the uses list and exports clause of the project file.
845
CGI Web server applications are console applications that receive requests from
clients on standard input, process those requests, and sends back the results
to the server on standard output to be sent to the client.
Selecting this type of application adds the required entries to the uses clause of
the project file and adds the appropriate $APPTYPE directive to the source.
Selecting this type of application sets up your project as a DLL. Apache Web
server applications are DLLs loaded by the Web server. Information is passed
to the DLL, processed, and returned to the client by the Web server.
Web App Debugger stand-alone executable Selecting this type of application sets up an environment for developing and
testing Web server applications. Web App Debugger applications are
executable files loaded by the Web server. This type of application is not
intended for deployment.
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 Creating Internet Server Applications.
846
a different process or, if you use DCOM, on a separate machine. You can also use COM+, ActiveX and Active Server
Pages.
COM is a language-independent software component model that enables interaction between software components
and applications running on a Windows platform. The key aspect of COM is that it enables communication between
components, between applications, and between clients and servers through clearly defined interfaces. Interfaces
provide a way for clients to ask a COM component which features it supports at runtime. To provide additional
features for your component, you simply add an additional interface for those features.
the control panel and include the events that are called when users execute the applet. For more information
about applet modules, see Control Panel Application wizard.
Services encapsulate individual services in an NT service application. In addition to holding any nonvisual
controls used to implement a service, services include the events that are called when the service is started or
stopped. For more information about services, see Service Applications.
Purpose
Edit
Displays a context menu with which you can cut, copy, paste, delete, and select the components in the
data module.
Position
Aligns nonvisual components to the module's invisible grid (Align To Grid) or according to criteria you
supply in the Alignment dialog box (Align).
Tab Order
Enables you to change the order that the focus jumps from component to component when you press the
tab key.
Creation Order
Enables you to change the order that data access components are created at start-up.
Revert to Inherited Discards changes made to a module inherited from another module in the Object Repository, and reverts
to the originally inherited module.
Add to Repository Stores a link to the data module in the Object Repository.
View as Text
Text DFM
Toggles between the formats (binary or text) in which this particular form file is saved.
848
2 Edit the Name property for the module in the Object Inspector.
The new name for the module appears in the title bar when the Name property in the Object Inspector no longer
has focus.
Changing the name of a data module at design time changes its variable name in the interface section of code. It
also changes any use of the type name in procedure declarations. You must manually change any references to the
data module in code you write.
To rename a unit file for a data module, select the unit file.
The new name for the component appears under its icon in the data module as soon as the Name property in the
Object Inspector no longer has focus.
For example, suppose your database application uses the CUSTOMER table. To access the table, you need a
minimum of two data access components: a data source component (TDataSource) and a table component
(TClientDataSet). When you place these components in your data module, the module assigns them the names
DataSource1 and ClientDataSet1. To reflect the type of component and the database they access, CUSTOMER,
you could change these names to CustomerSource and CustomerTable.
849
The procedures and functions you write should follow in the implementation section of the code for the module.
Use Unit, and enter the name of the module or pick it from the list box
For database components, in the data module click a dataset or query component to open the Fields editor and
drag any existing fields from the editor onto the form. The IDE prompts you to confirm that you want to add the
module to the form's uses clause, then creates controls (such as edit boxes) for the fields.
For example, if you've added the TClientDataSet component to your data module, double-click it to open the Fields
editor. Select a field and drag it to the form. An edit box component appears.
Because the data source is not yet defined, Delphi adds a new data source component, DataSource1, to the form
and sets the edit box's DataSource property to DataSource1. The data source automatically sets its DataSet property
to the dataset component, ClientDataSet1, in the data module.
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.
850
New
Other.
Once you add a remote data module to a project, use it just like a standard data module.
For more information about multi-tiered database applications, see Creating multi-tiered applications.
Add To Repository. For a form or data module, right-click the item and choose
Add To Repository.
3 Type a description, title, and author.
4 Decide which page you want the item to appear on in the New Items dialog box, then type the name of the page
or select it from the Page combo box. If you type the name of a page that doesn't exist, the Object Repository
creates a new page.
5 Choose Browse to select an icon to represent the object in the Object Repository.
6 Choose OK.
851
To use a shared repository, all team members must select the same Shared Repository
directory in the Environment Options dialog:
1 Choose Tools
Options
Environment Options.
2 On the Preferences page, locate the Shared Repository panel. In the Directory edit box, enter the directory where
you want to locate the shared repository. Be sure to specify a directory that's accessible to all team members.
The first time an item is added to the Repository, a DELPHI32.DRO file is created in the Shared Repository directory
if one doesn't exist already.
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 and add the new class to your project. When you
recompile your project, any changes that have been made to the item 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.
852
New
The template files are copied to the specified directory, where you can modify them. The original project template
is unaffected by your changes.
To add projects and project templates to the Object Repository, see Adding items to the Object Repository.
New
Option
2 If you want to specify a default project, select the Projects page and choose an item under Objects. Then select
VCL applications provide an instance of TWinHelpViewer, which implements all of these interfaces and provides a
link between applications and WinHelp. CLX applications require that you provide your own implementation. On
Windows, CLX applications can use the WinHelpViewer unit provided as part of the VCL if they bind to it statically
that is, by including that unit as part of your project instead of linking it to the VCL package.
The Help Manager maintains a list of registered viewers and passes requests to them in a two-phase process: it first
asks each viewer if it can provide support for a particular Help keyword or context, and then it passes the Help
request on to the viewer which says it can provide such support.
If more than one viewer supports the keyword, as would be the case in an application that had registered viewers
for both WinHelp and HyperHelp on Windows or Man and Info on Linux, the Help Manager can display a selection
box through which the user of the application can determine which Help viewer to invoke. Otherwise, it displays the
first responding Help system encountered.
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.
For information on ICustomHelpViewer methods, see
Communicating with the Help Manager
Displaying keyword-based Help
Asking the Help Manager for information
854
855
is the first of the three methods called by the Help Manager, which will call each registered Help viewer with the
same string to ask if the viewer provides help for that string; the viewer is expected to respond with an integer
indicating how many different Help pages it can display in response to that Help request. The viewer can use any
method it wants to determine thisinside the IDE, the HyperHelp viewer maintains its own index and searches it. If
the viewer does not support help on this keyword, it should return zero. Negative numbers are currently interpreted
as meaning zero, but this behavior is not guaranteed in future releases.
ICustomHelpViewer.GetHelpStrings(const HelpString: String): TStringList
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.
It is reasonable for a particular viewer to refuse to allow requests to support a table of contents. The Man page viewer
does this, for example, because the concept of a table of contents does not map well to the way Man pages work;
the HyperHelp viewer supports a table of contents, on the other hand, by passing the request to display a table of
contents directly to WinHelp on Windows and HyperHelp on Linux. It is not reasonable, however, for an
implementation of ICustomHelpViewer to respond to queries through CanShowTableOfContents with the answer
True, and then ignore requests through ShowTableOfContents.
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.
Applications can talk to systems requiring context-based Help by extending the object that implements
ICustomHelpViewer to also implement IExtendedHelpViewer. IExtendedHelpViewer also provides support for
talking to Help systems that allow you to jump directly to high-level topics instead of using keyword searches. The
built-in WinHelp viewer does this for you automatically.
IExtendedHelpViewer exposes four functions. Two of themUnderstandsContext and DisplayHelpByContextare
used to support context-based Help; the other twoUnderstandsTopic and DisplayTopicare used to support
topics.
When an application user presses F1, the Help Manager calls
IExtendedHelpViewer.UnderstandsContext(const ContextID: Integer;
const HelpFileName: String): Boolean
and the currently activated control supports context-based, rather than keyword-based Help. As with
ICustomHelpViewer.UnderstandsKeyword, the Help Manager queries all registered Help viewers iteratively. Unlike
the case with ICustomHelpViewer.UnderstandsKeyword, however, if more than one viewer supports a specified
context, the first registered viewer with support for a given context is invoked.
The Help Manager calls
IExtendedHelpViewer.DisplayHelpByContext(const ContextID: Integer;
const HelpFileName: String)
is used to invoke the first registered viewer which reports that it is able to provide help for that topic.
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
857
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.
Using IHelpSystem
Description
HelpCommand Takes a Windows Help style HELP_COMMAND and passes it off to WinHelp. Help requests forwarded
through this mechanism are passed only to implementations of IspecialWinHelpViewer.
HelpContext
HelpKeyword
HelpJump
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 systemindependent and so is not able to ascertain the location of Help files. If an application expects Help to be provided
859
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 topic Implementing
IHelpSelector.
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 topicbased 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.
turned on. (This is necessary to ensure that the Help Manager instance used by the unit is the same as the Help
Manager instance used by the IDE.)
3 Make sure that the Help viewer exists as a global instance within the unit.
4 In the initialization section of the unit, make sure that the instance is passed to the RegisterHelpViewer function.
860
861
2 In the Main Form combo box, select the form you want to use as the project's main form and choose OK.
Now if you run the application, the form you selected as the main form is displayed.
2 Add the following code after the call to Application.CreateForm and before the call to Application.Run.
Application.ShowMainForm := False;
Form1.Visible := False; { the name of your main form may differ }
Note: You can set the form's Visible property to False using the Object Inspector at design time rather than setting
it at runtime as in the previous example.
862
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.
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.
863
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.
This function creates a global variable with the same name as the form. So, every form in an application has an
associated global variable. This variable is a pointer to an instance of the form's class and is used to reference the
form while the application is running. Any unit that includes the form's unit in its uses clause can access the form
via this variable.
All forms created in this way in the project unit appear when the program is invoked and exist in memory for the
duration of the application.
864
In this case, since the form is already in memory, there is no need to create another instance or destroy that instance.
New
2 Remove the form from the Auto-create forms list of the Project
Options
Forms page.
This removes the form's invocation at startup. As an alternative, you can manually remove the following line from
program's main entry point:
Application.CreateForm(TResultsForm, ResultsForm);
3 Invoke the form when desired by using the form's Show method, if the form is modeless, or ShowModal method,
In the above example, note the use of try..finally. Putting in the line ResultsForm.Free; in the finally clause ensures
that the memory for the form is freed even if the form raises an exception.
The event handler in the example deletes the form after it is closed, so the form would need to be recreated if you
needed to use ResultsForm elsewhere in the application. If the form were displayed using Show you could not delete
the form within the event handler because Show returns while the form is still open.
Note: If you create a form using its constructor, be sure to check that the form is not in the Auto-create forms list
on the Project
Options Forms page. Specifically, if you create the new form without deleting the form
865
of the same name from the list, Delphi creates the form at startup and this event-handler creates a new
instance of the form, overwriting the reference to the auto-created instance. The auto-created instance still
exists, but the application can no longer access it. After the event-handler terminates, the global variable no
longer points to a valid form. Any attempt to use the global variable will likely crash the application.
Notice how the global instance of the form is never used in this version of the event handler.
Typically, applications use the global instances of forms. However, if you need a new instance of a modal form, and
you use that form in a limited, discrete section of the application, such as a single function, a local instance is usually
the safest and most efficient way of working with the form.
Of course, you cannot use local variables in event handlers for modeless forms because they must have global
scope to ensure that the forms exist for as long as the form is in use. Show returns as soon as the form opens, so
if you used a local variable, the local variable would go out of scope immediately.
866
TResultsForm = class(TForm)
ResultsLabel: TLabel;
OKButton: TButton;
procedure OKButtonClick(Sender: TObject);
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;
867
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];
However, using a property makes the interface to ColorForm very straightforward and simple. All a form needs to
know about ColorForm is to check the value of CurrentColor.
where form B is deleted from memory upon closing? Since a form does not have an explicit return value, you must
preserve important information from the form before it is destroyed.
To illustrate, consider a modified version of the ColorForm form that is designed to be a modal form. The class
declaration is as follows:
TColorForm = class(TForm)
ColorListBox:TListBox;
SelectButton: TButton;
CancelButton: TButton;
procedure CancelButtonClick(Sender: TObject);
procedure SelectButtonClick(Sender: TObject);
private
FColor: Pointer;
public
constructor CreateWithColor(Value: Pointer; Owner: TComponent);
end;
The form has a listbox called ColorListBox with a list of names of colors. When pressed, the button called
SelectButton makes note of the currently selected color name in ColorListBox then closes the form. CancelButton
is a button that simply closes the form.
Note that a user-defined constructor was added to the class that takes a Pointer argument. Presumably, this
Pointer points to a string that the form launching ColorForm knows about. The implementation of this constructor is
as follows:
constructor TColorForm(Value: Pointer; Owner: TComponent);
begin
FColor := Value;
String(FColor^) := '';
end;
The constructor saves the pointer to a private data member FColor and initializes the string to an empty string.
Note: To use the above user-defined constructor, the form must be explicitly created. It cannot be auto-created
when the application is started. For details, see Controlling when forms reside in memory.
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
869
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;
procedure GetColor(PColor: Pointer);
begin
ColorForm := TColorForm.CreateWithColor(PColor, Self);
ColorForm.ShowModal;
ColorForm.Free;
end;
UpdateButtonClick creates a String called MainColor. The address of MainColor is passed to the GetColor function
which creates ColorForm, passing the pointer to MainColor as an argument to the constructor. As soon as
ColorForm is closed it is deleted, but the color name that was selected is still preserved in MainColor, assuming that
a color was selected. Otherwise, MainColor contains an empty string which is a clear indication that the user exited
ColorForm without selecting a color.
This example uses one string variable to hold information from the modal form. Of course, more complex objects
can be used depending on the need. Keep in mind that you should always provide a way to let the calling form know
if the modal form was closed without making any changes or selections (such as having MainColor default to an
empty string).
4 Specify a name for the component template in the Component Template Information edit box. The default
proposal is the component type of the first component selected in step 2 followed by the word "Template." For
example, if you select a label and then an edit box, the proposed name will be "TLabelTemplate." You can change
this name, but be careful not to duplicate existing component names.
5 In the Palette page edit box, specify the Tool 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.
Configure Palette.
Creating Frames
To create an empty frame, choose File New Frame, or choose File New
You can then drop components (including other frames) onto your new frame.
It is usually bestthough not necessaryto 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
New
Frame and save the project.
them, then choose File
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.
frame, and choose Add to Palette. When the Component Template Information dialog opens, select a name, palette
page, and icon for the new template.
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.
872
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.
If you send a frame's unit and form files to other developers, they can open them and add them to the Tool
palette. If the frame has other frames embedded in it, they will have to open it as part of a project.
This code will show the dialog box and if the user presses the OK button, it will copy the name of the file into a
previously declared AnsiString variable named filename.
873
Often a set of actions is used in more than one user interface element. For example, the Cut, Copy, and Paste
commands often appear on both an Edit menu and on a toolbar. You only need to add the action once to use it in
multiple UI elements in your application.
On the Windows platform, tools are provided to make it easy to define and group actions, create different layouts,
and customize menus at design time or runtime. These tools are known collectively as ActionBand tools, and the
menus and toolbars you create with them are known as action bands. In general, you can create an ActionBand
user interface as follows:
Build the action list to create a set of actions that will be available for your application (use the Action Manager,
TActionManager)
Add the user interface elements to the application (use ActionBand components such as
TActionMainMenuBarand TActionToolBar)
Drag-and-drop actions from the Action Manager onto the user interface elements
The following table defines the terminology related to setting up menus and toolbars:
Action setup terminology
Term
Definition
Action
A response to something a user does, such as clicking a menu item. Many standard actions that are
frequently required are provided for you to use in your applications as is. For example, file operations such
as File Open, File SaveAs, File Run, and File Exit are included along with many others for editing, formatting,
searches, help, dialogs, and window actions. You can also program custom actions and access them using
action lists and the Action Manager.
Action band
A container for a set of actions associated with a customizable menu or toolbar. The ActionBand components
for main menus and toolbars (TActionMainMenuBar and TActionToolBar) are examples of action bands.
Action category Lets you group actions and drop them as a group onto a menu or toolbar. For example, one of the standard
action categories is Search which includes Find, FindFirst, FindNext, and Replace actions all at once.
Action classes
Classes that perform the actions used in your application. All of the standard actions are defined in action
classes such as TEditCopy, TEditCut, and TEditUndo. You can use these classes by dragging and dropping
them from the Customize dialog onto an action band.
Action client
Most often represents a menu item or a button that receives a notification to initiate an action. When the
client receives a user command (such as a mouse click), it initiates an associated action.
Action list
Maintains a list of actions that your application can take in response to something a user does.
Action Manager Groups and organizes logical sets of actions that can be reused on ActionBand components. See
TActionManager.
Menu
Lists commands that the user of the application can execute by clicking on them. You can create menus by
using the ActionBand menu class TActionMainMenuBar, or by using cross-platform components such as
TMainMenu or TPopupMenu.
Target
Represents the item an action does something to. The target is usually a control, such as a memo or a data
control. Not all actions require a target. For example, the standard help actions ignore the target and simply
launch the help system.
Toolbar
Displays a visible row of button icons which, when clicked, cause the program to perform some action, such
as printing the current document. You can create toolbars by using the ActionBand toolbar component
TActionToolBar, or by using the cross-platform component TToolBar.
If you are doing cross-platform development, refer to Using action lists for details.
874
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 actionthe action responds when a client fires the action. The action also influences the
clientaction 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 doubleclicking an action list object, TActionList). These actions are later connected to client controls. See Creating toolbars
and menus and, for cross-platform development, Setting up action lists for details.
875
palette onto a form. (You need to add the images you want to use to the ImageList or use the one provided.)
3 From the Additional category of the Tool palette, drop one or more of the following action bands onto the form:
You assign backgrounds and layouts to subitems from their action client objects. If you want to set the background
of the items in a menu, in the form designer click on the menu item that contains the items. For example, selecting
File lets you change the background of items appearing on the File menu. You can assign a color, pattern, or bitmap
in the Background property in the Object Inspector.
Use the BackgroundLayout property to describe how to place the background on the element. Colors or images can
be placed behind the caption normally, stretched to fit the item area, or tiled in small squares to cover the area.
Items with normal (blNormal), stretched (blStretch), or tiled (blTile) backgrounds are rendered with a transparent
background. If you create a banner, the full image is placed on the left (blLeftBanner) or the right (blRightBanner)
of the item. You need to make sure it is the correct size because it is not stretched or shrunk to fit.
To change the background of an action band (that is, on a main menu or toolbar), select the action band and choose
the TActionClientBar through the action band collection editor. You can set Background and BackgroundLayout
properties to specify a color, pattern, or bitmap to use on the entire toolbar or menu.
images you want to use and click OK when done. Some sample images are included in Program Files\Common
Files\Borland Shared\Images. (The buttons images include two views of each for active and inactive buttons.)
3 From the Additional category of the Tool palette, drop one or more of the following action bands onto the form:
Inspector, select the name of the image list from the Images property, such as ImageList1.
5 Use the Action Manager editor to add actions to the Action Manager. You can associate an image with an action
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.
877
edges or menu shadows. The XP colormap, for example, has numerous subtle refinements for menu and toolbar
classes. The IDE handles the details for you, automatically using the appropriate colormaps.
By default, the component library uses the XP style. To centrally select an alternate style for all your application's
menus and toolbars, use the Style property on the ActionManager component.
You can customize the look and feel of a style using colormap components.
example, XPColorMap or StandardColorMap). In the Object Inspector, you will see numerous properties to
adjust appearance, many with drop downs from which you can select alternate values.
2 Change each ToolBar or menu's ColorMap property to point to the colormap object that you dropped on the form.
3 In the Object Inspector, adjust the colormap's properties to change the appearance of your toolbars and menus
as desired.
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.
To allow the user of your application to customize an action band in your application:
1 Drop an Action Manager component onto a form.
2 Drop your action band components (TCustomActionMainMenuBar,TActionToolBar).
3 Double-click the Action Manager to display the Action Manager editor:
Add the actions you want to use in your application. Also add the Customize action, which appears at the bottom
of the standard actions list.
Drop a TCustomizeDlg component from the Additional tab onto the form, and connect it to the Action Manager
using its ActionManager property. You specify a filename for where to stream customizations made by users.
878
Drag and drop the actions onto the action band components. (Make sure you add the Customize action to the
toolbar or menu.)
4 Complete your application.
When you compile and run the application, users can access a Customize command that displays a customization
dialog box similar to the Action Manager editor. They can drag and drop menu items and create toolbars using the
same actions you supplied in the Action Manager.
0, 1
4, 5
12
6-8
17
9-13
23
14-24
29
25 or more
31
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.
879
them from the action bands, which in turn will change the numerical ordering of the index. Instead of referring to
index numbering, TActionManager includes methods that facilitate finding items by action or by caption.
For more information about MRU lists, sample code, and methods for finding actions in lists, see FindItemByAction
and FindItemByCaption in the online Help.
palette.)
2 Double-click the TActionList object to display the Action List editor.
Use one of the predefined actions listed in the editor: right-click and choose New Standard Action.
The predefined actions are organized into categories (such as Dataset, Edit, Help, and Window) in the Standard
Action Classes dialog box. Select all the standard actions you want to add to the action list and click OK.
Or, create a new action of your own: right-click and choose New Action.
3 Set the properties of each action in the Object Inspector. (The properties you set affect every client of the action.)
The Name property identifies the action, and the other properties and events (Caption, Checked, Enabled,
HelpContext, Hint, ImageIndex, ShortCut, Visible, and Execute) correspond to the properties and events of its
client controls. The client's corresponding properties are typically, but not necessarily, the same name as the
corresponding client property. For example, an action's Enabled property corresponds to a TToolButton's
Enabled property. However, an action's Checked property corresponds to a TToolButton's Down property.
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 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.
880
Note: For general information about events and event handlers, see Working with Events and Event Handlers.
You can supply an event handler that responds at one of three different levels: the action, the action list, or the
application. This is only a concern if you are using a new generic action rather than a predefined standard action.
You do not have to worry about this if using the standard actions because standard actions have built-in behavior
that executes when these events occur.
The order in which the event handlers will respond to events is as follows:
Action list
Application
Action
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:
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.
881
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;
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 how actions find their targets 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;
882
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.
Description
Edit
Standard edit actions: Used with an edit control target. TEditAction is the base class for descendants that each
override the ExecuteTarget method to implement copy, cut, and paste tasks by using the clipboard.
Format
Standard formatting actions: Used with rich text to apply text formatting options such as bold, italic, underline,
strikeout, and so on. TRichEditAction is the base class for descendants that each override the ExecuteTarget
and UpdateTarget methods to implement formatting of the target.
Help
Standard Help actions: Used with any target. THelpAction is the base class for descendants that each override the
ExecuteTarget method to pass the command onto a Help system.
Window Standard window actions: Used with forms as targets in an MDI application. TWindowAction is the base class for
descendants that each override the ExecuteTarget method to implement arranging, cascading, closing, tiling, and
minimizing MDI child forms.
File
File actions: Used with operations on files such as File Open, File Run, or File Exit.
Search
Search actions: Used with search options. TSearchAction implements the common behavior for actions that display
a modeless dialog where the user can enter a search string for searching an edit control.
Tab
Tab control actions: Used to move between tabs on a tab control such as the Prev and Next buttons on a wizard.
List
Dialog
Dialog actions: Used with dialog components. TDialogAction implements the common behavior for actions that
display a dialog when executed. Each descendant class represents a specific dialog.
Internet Internet actions: Used for functions such as Internet browsing, downloading, and sending mail.
DataSet DataSet actions: Used with a dataset component target. TDataSetAction is the base class for descendants that each
override the ExecuteTarget and UpdateTarget methods to implement navigation and editing of the target.
TDataSetAction introduces a DataSource property that ensures actions are performed on that dataset. If
DataSource is nil, the currently focused data-aware control is used.
Tools
Tools: Additional tools such as TCustomizeActionBars for automatically displaying the customization dialog for action
bands.
All of the action objects are described under the action object names in the online Help.
other methods to limit the target for the action to a specific class of objects. The descendant classes typically override
ExecuteTarget to perform a specialized task. These methods are described here:
Methods overriden by base classes of specific actions
Method
Description
HandlesTarget Called automatically when the user invokes an object (such as a tool button or menu item) that is linked to
the action. The HandlesTarget method lets the action object indicate whether it is appropriate to execute at
this time with the object specified by the Target parameter as a "target". See How actions find their targets
for details.
UpdateTarget
Called automatically when the application is idle so that actions can update themselves according to current
conditions. Use in place of OnUpdateAction. See Updating actions for details.
ExecuteTarget Called automatically when the action fires in response to a user action in place of OnExecute (for example,
when the user selects a menu item or presses a tool button that is linked to this action). See What happens
when an action fires for details.
When you write your own action classes, it is important to understand the following:
How actions find their targets
Registering actions
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.
884
This topic explains how to use the Menu Designer to design menu bars and pop-up (local) menus. It discusses the
following ways to work with menus at design time and runtime:
Opening the Menu Designer.
Building menus.
Editing menu items in the Object Inspector.
Using the Menu Designer context menu.
Using menu templates.
Saving a menu as a template.
Adding images to menu items.
For information about hooking up menu items to the code that executes when they are selected, see .
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.
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.
For more information about menu templates, see Using menu templates.
For more information about creating a menu using the menu designer see
Naming menus
Naming the menu items
Adding, inserting, and deleting menu items
Creating submenus
Adding images to menu items
885
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.
The menu name is added to the form's type declaration, and the menu name then appears in the Component list.
&File
File1
Removes ampersand
1234
N12341
$@@@#
N1
Removes all non-standard characters, adding preceding letter and numerical order
- (hyphen)
N2
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.
886
Begin typing to enter the caption. Or enter the Name property first by specifically placing your cursor in the Object
Inspector and entering a value. In this case, you then need to reselect the Caption property and enter a value.
3 Press Enter.
Use the arrow keys to move from the menu bar into the menu, and to then move between items in the list; press
Enter to complete an action. To return to the menu bar, press Esc .
Menu items are inserted to the left of the selected item on the menu bar, and above the selected item in the menu
list.
Note: You cannot delete the default placeholder that appears below the item last entered in a menu list, or next to
the last item on the menu bar. This placeholder does not appear in your menu at runtime.
Separator bars insert a line between menu items and items on a toolbar. You can use separator bars to indicate
groupings within the menu list or toolbar, or simply to provide a visual break in a list.
To add a separator bar to a menu:
Add a menu item as described above and type a hyphen (-) for the caption.
Or press the hyphen (-) key while the cursor is positioned on the menu where you want a separator to appear.
To add a separator bar onto a TActionToolBar, press the insert key and set the new item's caption to a separtor bar
(|) or hyphen (-).
To add accelerators or shortcuts to menu items, see Specifying accelerator keys and keyboard shortcuts.
To specify an accelerator, add an ampersand in front of the appropriate letter. For example, to add a Save menu
command with the S as an accelerator key, type &Save.
Keyboard shortcuts enable the user to perform the action without using the menu directly, by typing in the shortcut
key combination.
To specify a keyboard shortcut, use the Object Inspector to enter a value for the ShortCut property, or select a key
combination from the drop-down list. This list is only a subset of the valid combinations you can type in.
When you add a shortcut, it appears next to the menu item caption.
Warning: 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.)
To create a submenu:
1 Select the menu item under which you want to create a submenu.
2 Press Ctrl+RIGHT ARROW 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 DOWN ARROW, 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.
888
This causes the menu to open, enabling you to drag the item to its new location.
2 Drag the menu item into the list, releasing the mouse button to drop the menu item at the new location.
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.
889
2 If the form has more than one menu, select the menu you want to view from the form's Menu property drop-down
list.
The menu appears in the form exactly as it will when you run the program.
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.
Action
Insert
Delete
Deletes the selected menu item (and all its sub-items, if any).
Create Submenu
Creates a placeholder at a nested level and adds an arrow to the right of the selected menu item.
Select Menu
Opens a list of menus in the current form. Double-clicking a menu name opens the designer window
for the menu.
Save As Template
Opens the Save Template dialog box, where you can save a menu for future reuse.
Insert From Template Opens the Insert Template dialog box, where you can select a template to reuse.
Delete Templates
Opens the Delete Templates dialog box, where you can choose to delete any existing templates.
890
Insert From Resource Opens the Insert Menu from Resource file dialog box, where you can choose a .rc or .mnu file to open
in the current form.
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.
891
(If there are no templates, the Insert From Template option appears dimmed in the context menu.)
The Insert Template dialog box opens, displaying a list of available menu templates.
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.
(If there are no templates, the Delete Templates option appears dimmed in the context menu.)
892
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.
This menu can contain as many items, commands, and submenus as you like; everything in the active Menu
Designer window will be saved as one reusable menu.
2 Right-click in the Menu Designer and choose Save As Template.
3 In the Template Description edit box, type a brief description for this menu, and then choose OK.
893
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
894
Form1.Menu := SecondMenu;
Description
To insert items without replacing items in the main menu, leave For example, number the items in the main menu 0 and 5, and
room in the numeric range of the main menu's items and "plug insert items from the child menu by numbering them 1, 2, 3,
in" numbers from the child form.
and 4.
The imported menu can be part of a menu you are designing, or an entire menu in itself.
2 Right-click and choose Insert From Resource.
895
matches its width to the full width of the form's client area, even if the window changes size.
3 Add speed buttons or other controls to the panel.
Speed buttons are designed to work on toolbar panels. A speed button usually has no caption, only a small graphic
(called a glyph), which represents the button's function.
Speed buttons have three possible modes of operation. They can
Act like regular pushbuttons
896
Appear pressed
GroupIndex property to a value other than zero and its Down property to True.
Appear disabled
897
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.
Tool buttons are designed to work on toolbar components. Like speed buttons, tool buttons can:
Act like regular pushbuttons.
Toggle on and off when clicked.
Act like a set of radio buttons.
To implement tool buttons on a toolbar, do the following:
Adding a tool button
Assigning images to tool buttons
Setting tool button appearance and initial conditions
Creating groups of tool buttons
Allowing toggled tool buttons
898
Appear pressed
Appear disabled
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.
Any unbroken sequence of adjacent tool buttons with Style set to tbsCheck and Grouped set to True forms a single
group. To break up a group of tool buttons, separate the buttons with any of the following:
A tool button whose Grouped property is False.
A tool button whose Style property is not set to tbsCheck. To create spaces or dividers on the toolbar, add a
tool button whose Style is tbsSeparator or tbsDivider.
Another control besides a tool button.
Only VCL components that descend from TWinControl are windowed controls. You can add graphic controlssuch
as labels or speed buttonsto a cool bar, but they will not appear on separate bands.
ShowText property to False. Each band in a cool bar has its own
Text property.
BandBorderStyle to bsNone.
Keep users from changing the bands' order at runtime. (The FixedOrder to True.
user can still move and resize the bands.)
900
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.
Note: The cool bar component is not available in CLX applications.
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 and Generating a handler for a component's default event.
If the menu's AutoPopup property is set to True, it will appear automatically when the button is pressed.
Although the toolbar remains visible at design time so you can modify it, it remains hidden at runtime until the
application specifically makes it visible.
You can also provide a means of toggling the toolbar. In the following example, a toolbar of pens is toggled from a
button on the main toolbar. Since each click presses or releases the button, an OnClick event handler can show or
hide the Pen toolbar depending on whether the button is up or down.
procedure TForm1.PenButtonClick(Sender: TObject);
begin
PenBar.Visible := PenButton.Down;
end;
902
Use the example above to create a manifest file for your application. If you place your manifest file in the same
directory as your application, its controls will be rendered using the common controls version 6 theme engine. Your
application now supports Windows XP themes.
For more information on Windows XP common controls, themes, and manifest files, consult Microsoft's online
documentation.
903
Types of 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.
Use this component: When you want users to do this:
TEdit
TMemo
TMaskEdit
TRichEdit
Edit multiple lines of text using rich text format (VCL only).
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 TRichEditcontrols allow the user to add several lines of text.
Edit controls have some of the following important properties:
Edit control properties
904
Property
Description
Text
Determines the text that appears in the edit box or memo control.
Font
Controls the attributes of text written in the edit box or memo control.
AutoSize
Enables the edit box to dynamically change its height depending on the currently selected font.
ReadOnly
MaxLength
SelText
SelStart, SelLength Indicate the position and length of the selected part of the text.
TTextBrowser
Display a text file or simple HTML page that users can scroll through.
TTextViewer
Display a text file or simple HTML page. Users can scroll through the page or click links to view other
pages and images.
905
TLCDNumber
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 extensionssuch as .
txt, .html, and .xmlwith 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.
Use this component: When you want users to do this:
TLabel
TStaticText
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.
TScrollBar
TTrackBar
Select values on a continuous range (more visually effective than a scroll bar)
906
TUpDown
Select a value from a spinner attached to an edit component (VCL applications only)
THotKey
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 example, TForm has VertScrollBar and HorzScrollBar properties that
automatically configure scroll bars on the form. To create a scrollable region within a form, use TScrollBox.
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.
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.
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.
907
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.
TButton
TBitBtn
TSpeedButton
TCheckBox
TRadioButton
TToolBar
Arrange tool buttons and other controls in rows and automatically adjust their sizes and positions
TCoolBar
Display a collection of windowed controls within movable, resizable bands (VCL only)
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 for details on how to use action lists with buttons, toolbars, and
menus.
You can custom draw buttons individually or application wide. See Developing the user interface.
908
Button Controls
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.
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.
909
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 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.
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 this component: To display:
TListBox
TCheckListBox
TComboBox
TTreeView
A hierarchical list
TListView
TDateTimePicker
910
TMonthCalendar
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.
To let users select multiple items in the list box, you can use the ExtendedSelect and MultiSelect properties.
Combo Boxes
A combo box (TComboBox) combines an edit box with a scrollable list. When users enter data into the controlby
typing or selecting from the listthe 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.
Use csDropDown to create an edit box with a drop-down list. Use csDropDownList to make the edit box readonly (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.
911
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.
912
Grouping Controls
A graphical interface is easier to use when related controls and information are presented in groups. Components
for grouping components include:
Use this component: When you want this:
TGroupBox
TRadioGroup
TPanel
TScrollBox
TTabControl
TPageControl
A set of mutually exclusive notebook-style tabs with corresponding pages, each of which may contain
other controls
THeaderControl
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 controlssuch as list boxes, memos, and forms themselvescan 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.
913
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
componentsincluding TFormhave 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.
Use this component or property: To do this:
TStatusBar
TProgressBar
914
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.
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 TDBCtrlGrid or
TDBCtrlGrid component. 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.
915
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.
Use this component: To display:
TImage
Graphics files
TShape
Geometric shapes
TBevel
TPaintBox
TAnimate
AVI files (VCL applications only); GIF files (CLX applications only)
Notice that these include common paint routines (Repaint, Invalidate, and so on) that never need to receive focus.
916
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.
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.
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 set
ting 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.
917
918
Drawing is the creation of a single, specific graphic element, such as a line or a shape, with code. In your code,
you tell an object to draw a specific graphic in a specific place on its canvas by calling a drawing method of the
canvas.
Painting is the creation of the entire appearance of an object. Painting usually involves drawing. That is, in
response to OnPaint events, an object generally draws some graphics. An edit box, for example, paints itself
by drawing a rectangle and then drawing some text inside. A shape control, on the other hand, paints itself by
drawing a single graphic.
The following topics describe how to use graphics components to simplify your coding.
Refreshing the screen
Types of graphic objects
Common properties and methods of canvases
Handling multiple drawing objects in an application
Drawing on a bitmap
Loading and saving graphics files
Using the Clipboard with Graphics
Rubber banding example
919
Object
Description
Picture
Used to hold any graphic image. To add additional graphic file formats, use the Picture
Register method. Use this to handle arbitrary files such as displaying images in an image
control.
Bitmap
A powerful graphics object used to create, manipulate (scale, scroll, rotate, and paint), and
store images as files on a disk. Creating copies of a bitmap is fast since the handle is copied,
not the image.
Clipboard
Represents the container for any text or graphics that are cut, copied, or pasted from or to
an application. With the clipboard, you can get and retrieve data according to the appropriate
format; handle reference counting, and opening and closing the clipboard; manage and
manipulate formats for objects in the clipboard.
Icon
Metafile (VCL applications only) Contains a file that records the operations required to construct an image, rather than
contain the actual bitmap pixels of the image. Metafiles or drawings are extremely scalable
Drawing (CLX applications only)
without the loss of image detail and often require much less memory than bitmaps,
particularly for high-resolution devices, such as printers. However, metafiles and drawings
do not display as fast as bitmaps. Use a metafile or drawing when versatility or precision is
more important than performance.
Font
Specifies the font to use when writing text on the image. Set the properties of the TFont object to specify the font
face, color, size, and style of the font.
Brush
Determines the color and pattern the canvas uses for filling graphical shapes and backgrounds. Set the properties
of the TBrush object to specify the color and pattern or bitmap to use when filling in spaces on the canvas.
Pen
Specifies the kind of pen the canvas uses for drawing lines and outlining shapes. Set the properties of the TPen
object to specify the color, style, width, and mode of the pen.
PenPos
Pixels
Specifies the color of the area of pixels within the current ClipRect.
These properties are described in more detail in Using the properties of the Canvas object.
Here is a list of several methods you can use:
Common methods of the Canvas object
Method
Descriptions
Arc
Draws an arc on the image along the perimeter of the ellipse bounded by the specified rectangle.
Chord
CopyRect
Draw
Renders the graphic object specified by the Graphic parameter on the canvas at the location given
by the coordinates (X, Y).
Ellipse
FillRect
Fills the specified rectangle on the canvas using the current brush.
920
FrameRect (VCL only) Draws a rectangle using the Brush of the canvas to draw the border.
LineTo
Draws a line on the canvas from PenPos to the point specified by X and Y, and sets the pen position
to (X, Y).
MoveTo
Pie
Draws a pie-shaped the section of the ellipse bounded by the rectangle (X1, Y1) and (X2, Y2) on the
canvas.
Polygon
Draws a series of lines on the canvas connecting the points passed in and closing the shape by
drawing a line from the last point to the first point.
Polyline
Draws a series of lines on the canvas with the current pen, connecting each of the points passed to
it in Points.
Rectangle
Draws a rectangle on the canvas with its upper left corner at the point (X1, Y1) and its lower right
corner at the point (X2, Y2). Use Rectangle to draw a box using Pen and fill it using Brush.
RoundRect
StretchDraw
Draws a graphic on the canvas so that the image fits in the specified rectangle. The graphic image
may need to change its magnitude or aspect ratio to fit.
TextHeight, TextWidth Returns the height and width, respectively, of a string in the current font. Height includes leading
between lines.
TextOut
Writes a string on the canvas, starting at the point (X,Y), and then updates the PenPos to the end of
the string.
TextRect
Writes a string inside a region; any portions of the string that fall outside the region do not appear.
These methods are described in more detail in Using Canvas methods to draw graphic objects.
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.
921
You can use TPenRecall for quick saving off and restoring the properties of pens.
922
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
Note: Drawing a line with the LineTo method also moves the current position to the endpoint of the line.
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.
923
924
The following example loads a bitmap from a file and assigns it to the Brush of the Canvas of Form1:
var
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile('MyBitmap.bmp');
Form1.Canvas.Brush.Bitmap := Bitmap;
Form1.Canvas.FillRect(Rect(0,0,100,100));
finally
Form1.Canvas.Brush.Bitmap := nil;
Bitmap.Free;
end;
end;
Note: The brush does not assume ownership of a bitmap object assigned to its Bitmap property. You must ensure
that the Bitmap object remains valid for the lifetime of the Brush, and you must free the Bitmap object yourself
afterwards.
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.
925
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.
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 topic describes:
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.
926
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.
By convention, type identifiers begin with the letter T, and groups of similar constants (such as those making up an
enumerated type) begin with a 2-letter prefix (such as dt for "drawing tool").
The declaration of the TDrawingTool type is equivalent to declaring a group of constants:
const
dtLine = 0;
dtRectangle = 1;
dtEllipse = 2;
dtRoundRect = 3;
The main difference is that by declaring the enumerated type, you give the constants not just a value, but also a
type, which enables you to use the Delphi language's type-checking to prevent many errors. A variable of type
TDrawingTool can be assigned only one of the constants dtLine..dtRoundRect. Attempting to assign some other
number (even one in the range 0..3) generates a compile-time error.
In the following code, a field added to a form keeps track of the form's drawing tool:
type
TDrawingTool = (dtLine, dtRectangle, dtEllipse, dtRoundRect);
TForm1 = class(TForm)
927
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
928
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 topic shows
all the shape-drawing code in a single routine that all mouse-event handlers can call.
929
You can add the declaration in either the public or private parts at the end of the form object's declaration. If
the code is just sharing the details of handling some events, it's probably safest to make the shared method
private.
2 Write the method implementation in the implementation part of the form unit.
The header for the method implementation must match the declaration exactly, with the same parameters in the
same order.
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.
931
begin
P := Bitmap.ScanLine[y];
for x := 0 to Bitmap.width -1 do
P[x] := y;
end;
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 porting your application.
To save the contents of an image control in a file, call the SaveToFile method of the image control's Picture object.
The SaveToFile method requires the name of a file in which to save. If the picture is newly created, it might not have
a file name, or a user might want to save an existing picture in a different file. In either case, the application needs
to get a file name from the user before saving, as shown in the next topic.
The following pair of event handlers, attached to the File
Save and File
Save As menu items, respectively,
handle the resaving of named files, saving of unnamed files, and saving existing files under new names.
procedure TForm1.Save1Click(Sender: TObject);
begin
if CurrentFile <> '' then
Image.Picture.SaveToFile(CurrentFile){ save if already named }
else SaveAs1Click(Sender);{ otherwise get a name }
end;
procedure TForm1.Saveas1Click(Sender: TObject);
begin
if SaveDialog1.Execute then{ get a file name }
begin
CurrentFile := SaveDialog1.FileName;{ save the user-specified name }
Save1Click(Sender);{ then save normally }
end;
end;
933
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.
CLX constant
'image/delphi.bitmap'
SDelphiBitmap
'image/delphi.component' SDelphiComponent
'image/delphi.picture'
SDelphiPicture
'image/delphi.drawing'
SDelphiDrawing
934
HasFormat (or Provides in CLX applications) is a Boolean function. It returns True if the clipboard contains an
item of the type specified in the parameter. To test for graphics on the Windows platform, you pass
CF_BITMAP. In CLX applications, you pass SDelphiBitmap.
2 Assign the clipboard to the destination.
Note: The following VCL code shows how to paste a picture from the clipboard into an image control in response
to a click on an Edit
Paste menu item:
procedure TForm1.PasteButtonClick(Sender: TObject);
var
Bitmap: TBitmap;
begin
if Clipboard.HasFormat(CF_BITMAP) then { is there a bitmap on the Windows clipboard? )
begin
Image1.Picture.Bitmap.Assign(Clipboard);
end;
end;
The graphic on the clipboard could come from this application, or it could have been copied from another application,
such as Microsoft Paint. You do not need to check the clipboard format in this case because the paste menu should
be disabled when the clipboard does not contain a supported format.
935
Description
OnMouseDown event Occurs when the user presses a mouse button with the mouse pointer over a control.
OnMouseMove event Occurs when the user moves the mouse while the mouse pointer is over a control.
OnMouseUp event
Occurs when the user releases a mouse button that was pressed with the mouse pointer over a
component.
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:
Mouse-event parameters
Parameter Meaning
Sender
Button
936
Shift
Indicates the state of the Alt, Ctrl, and Shift keys at the time of the mouse action
X, Y
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.
With this code, moving the mouse over the form causes drawing to follow the mouse, even before the mouse button
is pressed.
937
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.
Those changes get the application to draw the final line again, but they do not draw any intermediate actionsthe
application does not yet support "rubber banding."
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:
938
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.
939
palette. This automatically puts an animation control on the form window in which you want to display the video
clip.
2 Using the Object Inspector, select the Name property and enter a new name for your animation control. You
will use this name when you call the animation control. (Follow the standard rules for naming Delphi identifiers).
Always work directly with the Object Inspector when setting design time properties and creating event handlers.
3 Do one of the following:
Select the Common AVI property and choose one of the AVIs available from the drop-down list; or
Select the resource of an AVI using the ResName or ResID properties. Use ResHandle to indicate the module
that contains the resource identified by ResName or ResID; or
Select the FileName property and click the ellipsis (...) button, choose an AVI file (GIF in CLX applications) from
any available local or network directories and click Open in the Open AVI or Open GIF dialog (Windows and
cross-platform applications).
This loads the AVI or GIF file into memory. If you want to display the first frame of the AVI or GIF clip on-screen
until it is played using the Active property or the Play method, then set the Open property to True.
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
displayed when animation control opens, then set the StartFrameproperty 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.
For more information on using the animation control, see the topic called Example of adding silent video clips.
To run this example, create a new project and save the Unit1.pas file as Frmlogo.pas and
save the Project1.dpr file as Logo.dpr. Then:
1 Double-click the animate icon from the Win32 category of the Tool palette.
940
Window. Now decrease the height of the form to right-center the animation control on it.
7 Double-click the form's OnActivate event and write the following code to run the AVI clip when the form is in focus
at runtime:
Logo1.Active := True;
8 Double-click the Label icon on the Standard category of the Tool palette. Select its Caption property and enter
Welcome to Cool Images 4.0. Now select its Font property, click the ellipsis (...) button and choose Font Style:
Bold, Size: 18, Color: Navy from the Font dialog and click OK. Click and drag the label control to center it on the
form.
9 Click the animation control to bring focus back to it. Double-click its OnStop event and write the following code
Select Run
player control on the form window in which you want the media feature.
2 Using the Object Inspector, select the Nameproperty and enter a new name for your media player control. You
will use this when you call the media player control. (Follow the standard rules for naming Delphi identifiers.)
Always work directly with the Object Inspector when setting design time properties and creating event handlers.
3 Select the DeviceType property and choose the appropriate device type to open using the AutoOpen property
or the Open method. (If DeviceType is dtAutoSelect the device type is selected based on the file extension of
the media file specified by the FileName property.) For more information on device types and their functions, see
the table below.
4 If the device stores its media in a file, specify the name of the media file using the FileName property. Select the
FileName property, click the ellipsis (...) button, and choose a media file from any available local or network
directories and click Open in the Open dialog. Otherwise, insert the hardware the media is stored in (disk,
cassette, and so on) for the selected media device, at runtime.
941
5 Set the AutoOpen property to True. This way the media player automatically opens the specified device when
the form containing the media player control is created at runtime. If AutoOpen is False, the device must be
opened with a call to the Open method.
6 Set the AutoEnable property to True to automatically enable or disable the media player buttons as required at
runtime; or, double-click the EnabledButtons property to set each button to True or False depending on which
ones you want to enable or disable.
The multimedia device is played, paused, stopped, and so on when the user clicks the corresponding button on
the media player component. The device can also be controlled by the methods that correspond to the buttons
(Play, Pause, Stop, Next, Previous, and so on).
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.
Multimedia device types and their functions
Device Type
Software/Hardware used
Plays
dtAVIVideo
No
Yes
dtCDAudio
Yes
No
dtDAT
Yes
No
No
Yes
dtMMMovie
MM Movie Player
MM film
No
Yes
dtOverlay
Overlay device
Analog Video
No
Yes
dtScanner
Image Scanner
No
No
dtSequencer
MIDI files
Yes
No
dtVCR
Video Cassettes
No
Yes
WAV files
No
No
For more information on using the media player control, see the topic called Example of Adding Audio and/or Video
Clips.
To run this example, create a new project and save the Unit1.pas file to FrmAd.pas and
save the Project1.dpr file to DelphiAd.dpr. Then:
1 Double-click the media player icon on the System category of the Tool palette.
2 Using the Object Inspector, set the Name property of the media player to VideoPlayer1.
3 Select its DeviceType property and choose dtAVIVideo from the drop-down list.
942
4 Select its FileName property, click the ellipsis (...) button, locate and choose an AVI file. Click Open in the Open
dialog.
5 Set its AutoOpen property to True and its Visible property to False.
6 Double-click the Animate icon from the Win32 category of the Tool 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
focus:
VideoPlayer1.Play;
Choose Run
943
944
Note: Thread objects do not allow you to control the security attributes or stack size of your threads. If you need to
control these, you must use the BeginThread function. Even when using BeginThread, you can still benefit
from some of the thread synchronization objects and methods described in Coordinating Threads.
New
2 In the New Items dialog box under Delphi Files, double-click Thread Object and enter a class name, such as
TMyThread.
3 Check the Named Thread check box and enter a thread name (VCL applications only).
4 Click OK, the Code Editor creates a new unit file to implement the thread.
945
Priority
tpIdle
The thread executes only when the system is idle. Windows won't interrupt other threads to execute a thread
with tpIdle priority.
tpLowest
tpLower
tpNormal
tpHigher
tpHighest
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 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 thread to enter its message loop. You do not need to use the Synchronize method for
the following objects:
Object
Description
Data access component 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.
Control
947
Graphic
Graphics objects are thread-safe. You do not need to use the main VCL thread to access TFont,
TPen, TBrush, TBitmap, TMetafile (VCL only), or TTIcon. Canvas objects can be used outside
the Synchronize method by locking them.
List
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.
declares an integer type variable that is private to each thread in the application, but global within each thread.
The threadvar section can only be used for global variables. Pointer and Function variables can't be thread variables.
Types that use copy-on-write semantics, such as long strings don't work as thread variables either.
948
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.
Whether using thread objects or generating threads using BeginThread, the following topics describe techniques for
coordinating threads:
Avoiding Simultaneous Access
Waiting for Other Threads
Using the Main VCL Thread
When global memory does not need to be shared by multiple threads, consider using thread-local variables instead
of global variables. By using thread-local variables, your thread does not need to wait for or lock out any 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 applications also include a thread-safe list object, TThreadList. Calling 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.
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.
In the previous example, the list items were only accessed when the WaitFor method indicated that the list was
successfully filled. This return value must be assigned by the Execute method of the thread that was waited for.
However, because threads that call WaitFor want to know the result of thread execution, not code that calls
Execute, the Execute method does not return any value. Instead, the Execute method sets the ReturnValue property.
ReturnValue is then returned by the WaitFor method when it is called by other threads. Return values are integers.
Your application determines their meaning.
951
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 the following table:
WaitFor return values
Value
Meaning
wrSignaled
wrTimeout
wrAbandoned The event object was destroyed before the time-out period elapsed.
wrError
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.
952
Warning: Do not create too many threads in your application. The overhead in managing multiple threads can
impact performance. The recommended limit is 16 threads per process on single processor systems.
This limit assumes that most of those threads are waiting for external events. If all threads are active,
you will want to use fewer.
You can create multiple instances of the same thread type to execute parallel code. For example, you can launch a
new instance of a thread in response to some user action, allowing each thread to perform the expected response.
The following topics discuss how to use the threads in your application:
Overriding the Default Priority.
Starting and Stopping Threads
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.
953
954
ThreadNameInfo.FName := 'MyThreadName';
ThreadNameInfo.FThreadID := $FFFFFFFF;
ThreadNameInfo.FFlags := 0;
try
RaiseException( $406D1388, 0, sizeof(ThreadNameInfo) div sizeof(LongWord),
@ThreadNameInfo );
except
end;
{$ENDIF}
end;
//---------------------------------------------------------------------------
Note:
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;
//---------------------------------------------------------------------------
to:
ThreadNameInfo.FName := ThreadName;
955
956
Exception handling
Exception Handling
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.
See Defining Protected Blocks for details on how to create and handle exceptions.
For information on using exceptions with the routines and classes in VCL, see Handling Exceptions in VCL
Applications.
957
try
SetFieldValue(dataField, userValue);
except
on E: EIntegerRange do
ShowMessage(Format('Expected value between %d and %d, but got %d',
E.Min, E.Max, E.Value));
end;
. { execution resumes here, outside the protected block }
.
.
You must have an exception handling block (described in Writing Exception Handlers) or a finally block (described
in Writing Finally Blocks) immediately after the try block. An exception handling block should include a handler for
each exception that the statements in the try block can generate.
Using a try block makes your code easier to read. Instead of sprinkling error-handling code throughout your program,
you isolate it in exception handlers so that the flow of your algorithms is more obvious.
This is especially true when performing complex calculations involving hundreds of steps, any one of which could
fail if one of dozens of inputs were invalid. By using exceptions, you can spell out the normal expression of your
algorithm, then provide for those exceptional cases when it doesn't apply. Without exceptions, you have to test every
time to make sure you can proceed with each step in the calculation.
For details on raising exceptions from the code in your try block, see Raising an Exception.
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.
958
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.
959
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.
Warning: 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.
960
You can combine error handlers for the base class with specific handlers for more specific (derived) exceptions. You
do this by placing the catch statements in the order that you want them to be searched when an exception is thrown.
For example, this block provides special handling for range errors, and other handling for all other integer math errors:
try
{ statements performing integer math }
except
on ERangeError do { out-of-range handling };
on EIntError do { handling for other integer math errors };
end;
Note that if the handler for EIntError came before the handler for ERangeError, execution would never reach the
specific handler for ERangeError.
961
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) 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.
Although most errors are not that obvious, the example illustrates an important point: When an exception occurs,
execution jumps out of the block, so the statement that frees the memory never gets called.
To ensure that the memory is freed, you can use a try block with a finally block.
For details on writing finally blocks, see Writing a Finally Block.
In a try..finally statement, the application always executes any statements in the finally part, even if an exception
occurs in the try block. When any code in the try block (or any routine called by code in the try block) raises an
exception, execution halts at that point. Once an exception handler is found, execution jumps to the finally part, which
is called the cleanup code. After the finally part executes, the exception handler is called. If no exception occurs, the
cleanup code is executed in the normal order, after all the statements in the try block.
The following code illustrates an event handler that uses a finally block so that when it allocates memory and
generates an error, it still frees the allocated memory:
procedure TForm1.Button1Click(Sender: TObject);
var
APointer: Pointer;
AnInteger, ADividend: Integer;
begin
ADividend := 0;
GetMem(APointer, 1024);{ allocate 1K of memory }
try
AnInteger := 10 div ADividend;{ this generates an exception }
finally
FreeMem(APointer, 1024);{ execution resumes here, despite the exception }
end;
end;
963
The statements in the finally block do not depend on an exception occurring. If no statement in the try part raises an
exception, execution continues through the finally block.
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.
In addition to handling the exceptions that VCL raises, you can define and raise your own VCL-based exception
classes. This is discussed in Defining Your Own VCL Exceptions.
Description
EAbort
EAccessViolation
964
EBitsError
EComponentError
EConvertError
EDatabaseError
EDBEditError
EDivByZero
EIntOverflow
Specifies integer calculations whose results are too large for the allocated register.
EInvalidCast
EInvalidGraphic
EInvalidOperation
EInvalidPointer
EMenuError
EOleCtrlError
EOleError
EPrinterError
EPropertyError
ERangeError
Indicates an integer value that is too large for the declared type to which it is assigned.
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).
965
end;
end;
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.
The following code shows a simple example of aborting an operation. On a form containing an empty list box and a
button, attach the following code to the button's OnClick event:
procedure TForm1.Button1Click(Sender: TObject);
var
I, J: Integer;
begin
for I := 1 to 10 do{ loop ten times }
for J := 1 to 10 do {loop ten times }
begin
ListBox1.Items.Add(IntToStr(I) + IntToStr(J));
if I = 7 then Abort;{ abort after the 7th iteration of outer loop}
end;
end;
Note that in this example, Abort causes the flow of execution to break out of both the inner and outer loops, not just
the inner loop.
type
EMyException = class(Exception);
If you raise EMyException but don't provide a specific handler for it, a handler for Exception (or a default exception
handler) will still handle it. Because the standard handling for Exception displays the name of the exception raised,
you can see that it is your new exception that is raised.
967
.bpf
bpi
A Borland package import library. A .bpi is created for each package. The bpis for bpls is analogous to import
libraries for dlls. This file is passed to the linker by applications using the package to resolve references to
functions in the package. The base name for the bpi is the base name for the package source file.
bpk and bpkw The project options source file. This file is the XML portion of the package project. The ProjectName.bpk and
ProjectName.cpp combined are used to manage settings, options, and files used by the package project. .bpk
and .bpkw packages are identical, but use the .bpkw extension for packages that you want to use in crossplatform applications.
969
bpl
The runtime package. This file is a Windows .dll with special -specific features. The base name for the .bpl is
the base name of the .bpk or .bpkwsource file.
cpp
ProjectName.cpp contains the entry point for the package. Additionally, each component contained within the
package generally resides within a .cpp file.
The header file or interface for the component. ComponentName.h is the companion to ComponentName.cpp.
lib
A static library, or collection of .objs, used in place of a .bpi when the application does not use runtime
packages. Generated only if the -Gl (Generate .lib file) linker option is selected.
obj
A binary image for a unit file contained in a package. One .obj is created, when necessary, for each .cpp file.
Note: Packages share their global data with other modules in an application.
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.
Loading packages in an application
Deciding which runtime packages to use
Custom packages
Options.
underneath. Each package is loaded implicitly only when it is needed (that is, when you refer to an object defined
in one of the units in that package). (Runtime packages associated with installed design-time packages are
already listed in the edit box.)
5 To add a package to an existing list, click the Add button and enter the name of the new package in the Add
Runtime Package dialog. To browse from a list of available packages, click the Add button, then click the Browse
button next to the Package Name edit box in the Add Runtime Package dialog.
If you edit the Search Path edit box in the Add Runtime Package dialog, you can change the global Library Path.
You do not need to include file extensions with package names (or the version number representing the Delphi
release); that is, vcl90.bpl in a VCL application is written as vcl. If you type directly into the Runtime Package edit
box, be sure to separate multiple names with semicolons. For example:
970
rtl;vcl;vcldb;vclado;vclbde;
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;
The units referenced in this VCL 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 unload a package dynamically, call UnloadPackage. Be careful to destroy any instances of classes defined in
the package and to unregister classes that were registered by it.
971
Note: In VCL applications, you don't have to manually include vcl and rtl, because they are referenced in the
Requires clause of vcldb. 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 vcl90.bpl, you would see a line similar to the
following in a VCL application:
Module Load: vcl90.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 //In VCL applications
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 Tool 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
Installed .NET Components.
The design-time packages work by calling runtime packages, which they reference in their Requires clause. For
example, dclstd references vcl. The dclstd itself contains additional functionality that makes many of the standard
components available on the Tool 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. See Installing Component Packages
with .bpl, .dcp, and .dcu files, be sure to copy all of them.
The directory where you store the .dcp fileand the .dcu files, if they are included with the distributionmust be
in the Delphi Library Path.
972
If the package is shipped as a .dpc (package collection) file, only the one file needs to be copied; the .dpc file
contains the other files. (For more information about package collection files, see Package collection files.)
2 Choose Component
3 Click OK.
The components in the package are installed on the Tool palette pages specified in the components'
RegisterComponents procedure, with the names they were assigned in the same procedure.
New projects are created with all available packages installed, unless you change the ddefault settings. To make
the current installation choices into the automatic default for new projects, check the Default check box at the bottom
of the Packages tab of the Project Options dialog box.
To remove components from the Tool palette without uninstalling a package, right-click the component to invoke
the context menu and choose Hide "<ComponentName>" Button.
Creating a Package
Refer to Understanding the structure of a package for more information about the steps outlined here.
973
To create a package
1 Choose File
New
Other, select the Package icon under Delphi Projects, and click OK. The generated
package appears in the Project Manager. The Project Manager displays a Requires node and a Contains node
for the new package.
2 To add a unit to the contains clause, right-click the contains node in the Project Manager and select Add. 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 Project Manager. You can add
additional units by repeating this step.
3 To add a package to the requires clause, right-click the requires node in the Project Manager and select Add
Reference. 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 Project
Manager. You can add additional packages by repeating this step.
4 In the Project Manager, right-click your package and select Compile.
2 In the Project Manager, select one of the packages in the Requires node, right-click, and choose Open.
The Project Options dialog has a Default check box in the lower left corner. If you click OK when this box is checked,
the options you've chosen are saved as default settings for new projects. To restore the original defaults, delete or
rename the defproj.dof file.
Naming packages
Package names must be unique within a project. If you name a package Stats, Delphi 2005 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.
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.
974
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.
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.
The requires clause lists other, external packages used by the current package. If a package does not contain
any units that use units in another package, then it doesn't need a requires clause.
The contains clause identifies the unit files to be compiled and bound into the package. All units used by
contained units which do not exist in required packages will also be bound into the package, although they won't
be listed in the contains clause (the compiler will give a warning).
For example, the following VCL code declares the vcldb package (in the source file vcldb90.bpl):
package MyPack;
{$R *.res}
...{compiler directives omitted}
requires
rtl,
vcl;
contains
Db,
NewComponent1 in 'NewComponent1.pas';
end.
Compiling Packages
You can compile a package from the IDE or from the command line.
2 Click Open.
3 When the package opens:
Build.
Note: Right-click the package project nodes for options to compile or build.
You can insert compiler directives into your package source code.
If you compile from the command line, you can use several package-specific switches.
Package-specific Compiler Directives
Weak Packaging
Using the Command-line Compiler and Linker
Package Files Created by a Successful Compilation
976
Directive
Purpose
#pragma package(smart_init)
Assures that packaged units are initialized in the order determined by their
dependencies. (Included by default in package source file.)
#pragma package(smart_init, weak) Packages unit "weakly." See (Put directive in unit source file.)
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 for information on compiler
directives not discussed here.
See Package-specific Compiler Directives.
Refer to Creating Packages and DLLs for additional directives that can be used in all libraries.
Weak Packaging
The $WEAKPACKAGEUNIT directive 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.) 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.
977
Purpose
-$G-
Disables creation of imported data references. Using this switch increases memory-access efficiency, but
prevents packages compiled with it from referencing variables in other packages.
-LEpath
Specifies the directory where the package file (.bpl) will be placed.
-LNpath
Specifies the directory where the package file (.dcp) will be placed.
Prevents a package from being implicitly recompiled later. Use when compiling packages that provide low-level
functionality, that change infrequently between builds, or whose source code will not be distributed.
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 Commandline Compiler for information on command-line options not discussed here.
Package-specific command-line compiler and linker switches
Switch
Purpose
tP
-Gi
Saves the generated .bpi file. Included by default in package project files.
-Gpd
-Gpr
-Gl
-Tpp
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.
Files deployed with a package
978
File
Description
ComponentName.h
Add Package or click the Add a package button, then select a bpl in the Select Package
dialog and click Open. To add more bpls to the collection, click the Add a package button again. A tree diagram
on the left side of the Package editor displays the bpls as you add them. To remove a package, select it and
either choose Edit
Remove Package or click the Remove the selected package button.
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
979
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 dropdown 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
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
want to work with.
Open in the Package Collection editor and locate the file you
980
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.
981
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 setsespecially
double-byte character sets (DBCS)are widely used for Asian languages.
byte char. Instead, a multibyte string can contain one or more bytes per character. AnsiStrings can contain a mix of
single-byte and multibyte characters.
The lead byte of every multibyte character code is taken from a reserved range that depends on the specific character
set. The second and subsequent bytes can sometimes be the same as the character code for a separate one-byte
character, or it can fall in the range reserved for the first byte of multibyte characters. Thus, the only way to tell
whether a particular byte in a string represents a single character or is part of a multibyte character is to read the
string, starting at the beginning, parsing it into two or more byte characters when a lead byte from the reserved range
is encountered.
When writing code for Asian locales, you must be sure to handle all string manipulation using functions that are
enabled to parse strings into multibyte characters. See MBCS utilities for a list of the RTL functions that are enabled
to work with multibyte characters.
Delphi provides you with many of these runtime library functions, as listed in the following table:
Runtime library functions
AdjustLineBreaks
AnsiStrLower
ExtractFileDir
AnsiCompareFileName
AnsiStrPos
ExtractFileExt
AnsiExtractQuotedStr
AnsiStrRScan
ExtractFileName
AnsiLastChar
AnsiStrScan
ExtractFilePath
AnsiLowerCase
AnsiStrUpper
ExtractRelativePath
AnsiLowerCaseFileName AnsiUpperCase
FileSearch
AnsiPos
AnsiUpperCaseFileName IsDelimiter
AnsiQuotedStr
ByteToCharIndex
IsPathDelimiter
AnsiStrComp
ByteToCharLen
LastDelimiter
AnsiStrIComp
ByteType
StrByteType
AnsiStrLastChar
ChangeFileExt
StringReplace
AnsiStrLComp
CharToByteIndex
WrapText
AnsiStrLIComp
CharToByteLen
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
One 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
983
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.
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.
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.
bdRightToLeftNoAlign
bdRightToLeftNoAlign draws text using right to left reading order, the alignment is not changed, and the scroll bar
is moved.
bdRightToLeftReadingOnly
bdRightToLeftReadingOnly draws text using right to left reading order, and the alignment and scroll bars are not
changed.
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
984
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.
VCL methods that support BiDi
Method
Description
OkToChangeFieldAlignment
Used with database controls. Checks to see if the alignment of a control can be
changed.
DBUseRightToLeftAlignment
ChangeBiDiModeAlignment
Changes the alignment parameter passed to it. No check is done for BiDiMode setting,
it just converts left alignment into right alignment and vice versa, leaving centeraligned controls alone.
IsRightToLeft
Returns True if any of the right to left options are selected. If it returns False the control
is in left to right mode.
UseRightToLeftReading
UseRightToLeftAlignment
Returns True if the control is using right to left alignment. It can be overridden for
customization.
UseRightToLeftScrollBar
DrawTextBiDiModeFlags
Returns the correct draw text flags for the BiDiMode of the control.
DrawTextBiDiModeFlagsReadingOnly Returns the correct draw text flags for the BiDiMode of the control, limiting the flag to
its reading order.
AddBiDiModeExStyle
Adds the appropriate ExStyle flags to the control that is being created.
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.
985
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. ImeName
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 abbreviationsthey do not exist in languages that use ideographic characters.
Short strings tend to grow in translation more than long phrases. The following table provides a rough estimate of
how much expansion you should plan for given the length of your English strings:
Estimating string lengths
Length of English String (in characters) Expected Increase
1-5
100%
6-12
80%
13-20
60%
21-30
40%
31-50
20%
over 50
10%
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
986
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 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.
For information on using resource DLLs in your applications see Creating Resource DLLs and Using Resource DLLs.
987
988
the language. If there is no resource module that matches the language, your application will use the resources
compiled with the executable, DLL, or package.
If you want your application to use a different resource module than the one that matches the locale of the local
system, you can set a locale override entry in the Windows registry. Under the HKEY_CURRENT_USER\Software
\Borland\Locales key, add your application's path and file name as a string value and set the data value to the
extension of your resource DLLs. At startup, the application will look for resource DLLs with this extension before
trying the system locale. Setting this registry entry allows you to test localized versions of your application without
changing the locale on your system.
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.
The advantage of this technique is that the current instance of the application and all of its forms are used. It is not
necessary to update the registry settings and restart the application or re-acquire resources required by the
application, such as logging in to database servers.
When you switch resource DLLs the properties specified in the new DLL overwrite the properties in the running
instances of the forms.
989
Note: Any changes made to the form properties at runtime will be lost. Once the new DLL is loaded, default values
are not reset. Avoid code that assumes that the form objects are reinitialized to the their startup state, apart
from differences due to localization.
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)
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.
990
Deploying applications
Deploying Applications: Overview
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 Database Applications
Deploying Web Applications
Programming for Varying Host Environments
Software License Requirements
991
Program files
Package files
Help files
.hlp, .cnt, and .toc (if used) or any other Help files your application supports
ActiveX files
Local table files .dbf, .mdx, .dbt, .ndx, .db, .px, .y*, .x*, .mb, .val, .qbe, .gd*
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 Merge modules 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
992
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.
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.
Merge modules and their dependencies
Merge Module
BPLs Included
Dependencies
ADO
adortl90.bpl
DatabaseRTL, BaseRTL
BaseRTL
rtl90.bpl
No dependencies
BaseVCL
vcl90.bpl, vclx90.bpl
BaseRTL
BDEInternet
inetdbbde90.bpl
BDERTL
bdertl90.bpl
DatabaseRTL, BaseRTL
DatabaseRTL
dbrtl90.bpl
BaseRTL
DatabaseVCL
vcldb90.bpl
DataSnap
dsnap90.bpl
DatabaseRTL, BaseRTL
DataSnapConnection
dsnapcon90.bpl
DataSnapEntera
dsnapent90.bpl
DBCompatVCL
vcldbx90.bpl
dbExpress
dbexpress90.bpl
DatabaseRTL, BaseRTL
dbExpressClientDataSet dbxcds90.bpl
DBXInternet
inetdbxpress90.bpl
DecisionCube
dss90.bpl
InterbaseVCL
ibxpress90.bpl
Internet
inet90.bpl, inetdb90.bpl
DatabaseRTL, BaseRTL
InternetDirect
indy90.bpl
BaseVCL, BaseRTL
Office2000Components
dcloffice2k90.bpl
993
OfficeXPComponents
dclofficexp90.bpl
SOAPRTL
soaprtl90.bpl
TeeChart
BaseVCL, BaseRTL
VCLActionBands
vclactnband90.bpl
BaseVCL, BaseRTL
VCLIE
vclie90.bpl
BaseVCL, BaseRTL
WebDataSnap
webdsnap90.bpl
WebSnap
websnap90.bpl, vcljpg90.bpl
XMLRTL
xmlrtl90.bpl
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 \Bin 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
994
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.
dbExpINT
dbExpORA
995
dbExpDB2
dbExpMYS
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:
dbExpress deployment with driver DLLs
Database DLL
When to Deploy
dbexpinf.dll
dbexpint.dll
dbexpora.dll
dbexpdb2.dll.
dbexpmss.dll
dbexpmys.dll
See Using Unidirectional Datasets for more information about using the dbExpress components.
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.
Enabling modules
Your DLLs should be physically located in the Apache Modules subdirectory.
Two modifications to httpd.conf are required to enable a module.
The first modification is to add a LoadModule entry to let Apache locate and load your DLL. For example:
LoadModule MyApache_module modules/Project1.dll
997
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';
The second modification is to 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>
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.
998
See the Apache LICENSE file, included with your Apache distribution, for additional deployment information. For
additional Apache configuration information, see http://www.apache.org.
999
1000
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)
1001
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.
1002
1003
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 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 integrate your
application into an ADO-based environment (for example, making use of ADO-based application servers). For
more information about using the ADO components, see Working with ADO Components
The dbExpress page of the Component palette contains components that use dbExpress to access database
information. dbExpress is a lightweight set of drivers that provide the fastest access to database information.
However, dbExpress database components also support the narrowest range of data manipulation functions.
For more information about using the dbExpress components, see Using unidirectional datasets
1004
The InterBase page of the Component palette contains components that access InterBase databases directly,
without going through a separate engine layer. For more information about using the InterBase components,
see Getting started with InterBase Express.
The Data Access page of the Component palette contains components that can be used with any data access
mechanism. This page includes TClientDataset, which can work with data stored on disk or, using the
TDataSetProvider component also on this page, with components from one of the other groups. For more
information about using client datasets, see Using client datasets For more information about
TDataSetProvider, see Using provider components
Note: Different versions of Delphi include different drivers for accessing database servers using the BDE, ADO, or
dbExpress.
When designing a database application, you must decide which set of components 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.
In addition to choosing a data access mechanism, you must choose a database server. There are different types of
databases and you will want to consider the advantages and disadvantages of each type before settling on a
particular database server.
All types of databases contain tables which store information. In addition, most (but not all) servers support additional
features such as
Database security
Transactions
Referential integrity, stored procedures, and triggers
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
1005
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.
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 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.
1006
Concurrent transactions do not see each other's partial or uncommitted results, which might create
inconsistencies in the application state. This is referred to as isolation.
Committed updates to records survive failures, including communication failures, process failures, and server
system failures. This is referred to as durability.
Thus, transactions protect against hardware failures that occur in the middle of a database command or set of
commands. Transactional logging allows you to recover the durable state after disk media failures. Transactions
also form the basis of multi-user concurrency control on SQL servers. When each user interacts with the database
only through transactions, one user's commands can't disrupt the unity of another user's transaction. Instead, the
SQL server schedules incoming transactions, which either succeed as a whole or fail as a whole.
Transaction support is not part of most local databases, although it is provided by local InterBase. In addition, the
BDE drivers provide limited transaction support for some local databases. Database transaction support is provided
by the component that represents the database connection. For details on managing transactions using a database
connection component, see Managing transactions.
In multi-tiered applications, you can create transactions that include actions other than database operations or that
span multiple databases. For details on using transactions in multi-tiered applications, see Managing transactions
in multi-tiered applications.
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.
While there are many distinct ways to organize the components in a database application, most follow the general
scheme illustrated in the following figure:
1007
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
Understanding datasets
1008
Each type of dataset uses its own type of connection component, which represents a single data access mechanism:
1009
If the dataset is a BDE dataset such as TTable, TQuery, or TStoredProc, the connection component is a
TDataBaseobject. You connect the dataset to the database component by setting its Databaseproperty. 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 TADOConnectionobject. You connect the dataset to the ADO connection component
by setting its Connectionproperty. As with BDE datasets, you do not need to explicitly add the connection
component: instead you can set the dataset's ConnectionStringproperty.
If the dataset is a dbExpress dataset such as TSQLDataSet, TSQLTable, TSQLQuery, or TSQLStoredProc,
the connection component is a TSQLConnection object. You connect the dataset to the SQL connection
component by setting its SQLConnection property. When using dbExpress datasets, you must explicitly add
the connection component. Another difference between dbExpress datasets and the other datasets is that
dbExpress datasets are always read-only and unidirectional: This means you can only navigate by iterating
through the records in order, and you can't use the dataset methods that support editing.
If the dataset is an InterBase Express dataset such as TIBDataSet, TIBTable, TIBQuery, or TIBStoredProc, the
connection component is a TIBDatabaseobject. You connect the dataset to the IB database component by
setting its Database_Database">Databaseproperty. As with dbExpress datasets, you must explicitly add the
connection component.
In addition to the components listed above, you can use a specialized client dataset such as TBDEClientDataSet,
TSimpleDataSet, or TIBClientDataSet with a database connection component. When using one of these client
datasets, specify the appropriate type of connection component as the value of the DBConnection property.
Although each type of dataset uses a different connection component, they all perform many of the same tasks and
surface many of the same properties, methods, and events. For more information on the commonalities among the
various database connection components, see Connecting to databases
This architecture represents either a single-tiered or two-tiered application, depending on whether the database
server is a local database such or a remote database server. The logic that manipulates database information is in
the same application that implements the user interface, although isolated into a data module.
Note: The connection components or drivers needed to create two-tiered applications are not available in all version
of Delphi.
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.
1010
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
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.
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.)
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.
1012
This architecture represents either a single-tiered or two-tiered application, depending on whether the database
server is a local database or a remote database server. The logic that manipulates database information is in the
same application that implements the user interface, although isolated into a data module.
To link the client dataset to the provider, set its ProviderName property to the name of the provider component. The
provider must be in the same data module as the client dataset. To link the provider to the source dataset, set its
DataSet property.
Once the client dataset is linked to the provider and the provider is linked to the source dataset, these components
automatically handle all the details necessary for fetching, displaying, and navigating through the database records
(assuming the source dataset is connected to a database). To apply user edits back to the database, you need only
call the client dataset's ApplyUpdates method.
For more information on using a client dataset with a provider, see Using a client dataset with a provider.
1013
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 offloaded onto the middle tier. These smaller client applications are easier to install, configure, and maintain. Multitiered applications can also improve performance by spreading data-processing over several systems.
The multi-tiered architecture is very similar to the model described in Connecting a client dataset to another dataset
in the same application. 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, or SOAP). 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 Creating multi-tiered applications
1014
Combining Approaches
There is no reason 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 with another
approach such as those described in Connecting to another dataset or Using a multi-tiered architecture. 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 category of the Tool 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 category on the Tool 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 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.
1015
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 Creating reports with Rave Reports.
1016
1017
Description
TDBGrid
Displays information from a data source in a tabular format. Columns in the grid correspond to
columns in the underlying table or query's dataset. Rows in the grid correspond to records.
TDBNavigator
Navigates through data records in a dataset. updating records, posting records, deleting records,
canceling edits to records, and refreshing data display.
TDBText
TDBEdit
TDBMemo
Displays data from a memo or BLOB field in a scrollable, multi-line edit box.
TDBImage
TDBListBox
Displays a list of items from which to update a field in the current data record.
TDBComboBox
Displays a list of items from which to update a field, and also permits direct text entry like a standard
data-aware edit box.
TDBCheckBox
TDBRadioGroup
TDBLookupListBox
Displays a list of items looked up from another dataset based on the value of a field.
TDBLookupComboBox Displays a list of items looked up from another dataset based on the value of a field, and also permits
direct text entry like a standard data-aware edit box.
TDBCtrlGrid
TDBRichEdit
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.
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.
3 Place a data control from the Data Access category of the Tool palette onto a form.
4 Using the Object Inspector, set the DataSource property of the control to the data source component you placed
in step 2.
5 Set the DataField property of the control to the name of a field to display, or select a field name from the drop-
down list for the property. This step does not apply to TDBGrid, TDBCtrlGrid, and TDBNavigator because they
access all available fields in the dataset.
6 Set the Active property of the dataset to True to display data in the control.
For more information about managing the link between the data control and its dataset, see
Changing the Associated Dataset at Runtime
Enabling and Disabling the Data Source
Responding to Changes Mediated by the Data Source
You can also set the DataSet property to a dataset on another form to synchronize the data controls on two forms.
For example:
procedure TForm2.FormCreate (Sender : TObject);
begin
DataSource1.Dataset := Form1.Table1;
end;
1019
in the dataset. However, if your user interface is using controls that are not data-aware, you can use the events of
a data source component to manually provide the same sort of response.
The OnDataChange event occurs whenever the data in a record may have changed, including field edits or when
the cursor moves to a new record. This event is useful for making sure the control reflects the current field values
in the dataset, because it is triggered by all changes. Typically, an OnDataChange event handler refreshes the value
of a non-data-aware control that displays field data.
The UpdateData event occurs when the data in the current record is about to be posted. For instance, an
OnUpdateData event occurs after Post is called, but before the data is actually posted to the underlying database
server or local cache.
The StateChange 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.
1020
1021
CustTable.Next; { EOF False on success; EOF True when Next fails on last record }
end;
finally
CustTable.EnableControls;
end;
1023
property determines how the text is aligned within the control. Possible choices are taLeftJustify (the default),
taCenter, and taRightJustify. To change the font of the text, use the Font property.
At runtime, users can cut, copy, and paste text to and from a database memo control. You can accomplish the same
task programmatically by using the CutToClipboard, CopyToClipboard, and PasteFromClipboard methods.
Because the TDBMemo 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, TDBMemo has an AutoDisplay property that controls whether
the accessed data should be displayed automatically. If you set AutoDisplay to False, TDBMemo displays the field
name rather than actual data. Double-click inside the control to view the actual data.
1024
TDBComboBox, which combines the functionality of a data-aware edit control and a drop-down list. At runtime
it can display a drop-down list from which a user can pick from a predefined set of values, and it can permit a
user to enter an entirely different value.
TDBLookupListBox, which behaves like TDBListBox except the list of display items is looked up in another
dataset.
TDBLookupComboBox, which behaves like TDBComboBox except the list of display items is looked up in
another dataset.
The following topics describe these components in more detail:
Using TDBListBox and TDBComboBox
Displaying and Editing Data in Lookup List and Combo Boxes
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.
1025
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.
5 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.
1026
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.
If the field for the current record contains values of "True," "Yes," or "On," then the check box is checked. Comparison
of the field to ValueChecked strings is case-insensitive. If a user checks a box for which there are multiple
ValueChecked strings, the first string is the value that is posted to the database.
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.
1027
Note: If the field does not match any strings in Items, a radio button may still be selected if the field matches a string
in the Values property. If the field for the current record does not match any strings in Items or Values, no
radio button is selected.
The Values property can contain an optional list of strings that can be returned to the dataset when a user selects
a radio button and posts a record. Strings are associated with buttons in numeric sequence. The first string is
associated with the first button, the second string with the second button, and so on. For example, suppose Items
contains "Red," "Yellow," and "Blue," and Values contains "Magenta," "Yellow," and "Cyan." If a user selects the
button labeled "Red," "Magenta" is posted to the database.
If strings for Values are not provided, the Item string for a selected radio button is returned to the database when a
record is posted.
1028
The dataset's ObjectView property setting for grids displaying ADT and array fields. See Displaying ADT and
array fields.
A grid control has a Columns property that is itself a wrapper on a TDBGridColumns object. TDBGridColumns is a
collection of TColumn objects representing all of the columns in a grid control. You can use the Columns editor to
set up column attributes at design time, or use the Columns property of the grid to access the properties, events,
and methods of TDBGridColumns at runtime.
The following topics describe how to use the TDBGrid component:
Using a Grid Control in Its Default State
Creating a Customized Grid
Displaying ADT and Array Fields
Setting Grid Options
Editing in the Grid
Controlling Grid Drawing
Responding to User Actions at Runtime
1029
1030
The Columns list box displays the persistent columns that have been defined for the selected grid. When you first
bring up the Columns editor, this list is empty because the grid is in its default state, containing only dynamic columns.
You can create persistent columns for all fields in a dataset at once, or you can create persistent columns on an
individual basis.
associated with a data source, Add All Fields is disabled. Associate the grid with a data source that has an active
dataset before choosing Add All Fields.
2 If the grid already contains persistent columns, a dialog box asks if you want to delete the existing columns, or
append to the column set. If you choose Yes, any existing persistent column information is removed, and all fields
in the current dataset are inserted by field name according to their order in the dataset. If you choose No, any
existing persistent column information is retained, and new column information, based on any additional fields
in the dataset, are appended to the dataset.
3 Click Close to apply the persistent columns to the grid and close the dialog box.
At runtime, you can switch to persistent columns by assigning csCustomized to the Columns.State property. Any
existing columns in the grid are destroyed and new persistent columns are built for each field in the grid's dataset.
You can then add a persistent column at runtime by calling the Add method for the column list:
DBGrid1.Columns.Add;
1031
Note: If you delete all the columns from a grid, the Columns.State property reverts to its csDefault state and
automatically build dynamic columns for each field in the dataset.
You can delete a persistent column at runtime by simply freeing the column object:
DBGrid1.Columns[5].Free;
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.
Warning: 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.
Purpose
Alignment
Left justifies, right justifies, or centers the field data in the column. Default source: TField.Alignment.
1032
ButtonStyle
cbsAuto: (default) Displays a drop-down list if the associated field is a lookup field, or if the column's
PickList property contains data.
cbsEllipsis: Displays an ellipsis (...) button to the right of the cell. Clicking on the button fires the grid's
OnEditButtonClick event.
cbsNone: The column uses only the normal edit control to edit data in the column.
Color
Specifies the background color of the cells of the column. Default source: TDBGrid.Color. (For text
foreground color, see the Font property.)
DropDownRows The number of lines of text displayed by the drop-down list. Default: 7.
Expanded
Specifies whether the column is expanded. Only applies to columns representing ADT or array fields.
FieldName
Specifies the field name associated with this column. This can be blank.
ReadOnly
Width
Specifies the width of the column in screen pixels. Default source: TField.DisplayWidth.
Font
Specifies the font type, size, and color used to draw text in the column. Default source: TDBGrid.Font.
PickList
Title
The following table summarizes the options you can specify for the Title property.
Expanded TColumn Title properties
Property
Purpose
Alignment Left justifies (default), right justifies, or centers the caption text in the column title.
Caption
Specifies the text to display in the column title. Default source: TField.DisplayLabel.
Color
Specifies the background color used to draw the column title cell. Default source: TDBGrid.FixedColor.
Font
Specifies the font type, size, and color used to draw text in the column title. Default source: TDBGrid.TitleFont.
1033
Note: To restore a column with an explicit pick list to its normal behavior, delete all the text from the pick list using
the String List editor.
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
The following figure 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.
Object
Purpose
Expandable
TColumn Indicates whether the column can be expanded to show child fields in separate, editable columns.
(read-only)
1035
Expanded
MaxTitleRows TDBGrid
ObjectView
Specifies the maximum number of title rows that can appear in the grid
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.
Purpose
dgEditing
True: (Default). Enables editing, inserting, and deleting records in the grid.
False: Disables editing, inserting, and deleting records in the grid.
dgAlwaysShowEditor
dgTitles
True: (Default). Displays field names across the top of the grid.
False: Field name display is turned off.
dgIndicator
True: (Default). The indicator column is displayed at the left of the grid, and the current record
indicator (an arrow at the left of the grid) is activated to show the current record. On insert, the arrow
becomes an asterisk. On edit, the arrow becomes an I-beam.
False: The indicator column is turned off.
dgColumnResize
True: (Default). Columns can be resized by dragging the column rulers in the title area. Resizing
changes the corresponding width of the underlying TField component.
False: Columns cannot be resized in the grid.
dgColLines
dgRowLines
dgTabs
dgRowSelect
True: The selection bar spans the entire width of the grid.
1036
dgCancelOnExit
True: (Default). Cancels a pending insert when focus leaves the grid. This option prevents
inadvertent posting of partial or blank records.
False: Permits pending inserts.
dgMultiSelect
True: Allows user to select noncontiguous rows in the grid using Ctrl+Shift or Shift+ arrow keys.
False: (Default). Does not allow user to multi-select rows.
changes to individual columns. For example, you might want to activate and deactivate a button elsewhere on the
form every time a user enters and exits a specific column.
The following table lists the grid events available in the Object Inspector.
Grid control events
Event
Purpose
OnCellClick
OnColEnter
OnColExit
OnColumnMoved
OnDblClick
OnDragDrop
OnDragOver
(obsolete) Occurs when application needs to draw individual cells if State is csDefault.
OnEditButtonClick
OnEndDrag
OnEnter
OnExit
OnKeyDown
Occurs when a user presses any key or key combination on the keyboard when in the grid.
OnKeyPress
Occurs when a user presses a single alphanumeric key on the keyboard when in the grid.
OnKeyUp
OnStartDrag
OnTitleClick
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.
cell in the grid, and is the only cell into which you can place other controls.
4 Set the DataField property for each data control to the name of a field. The data source for these data controls
1038
When you compile and run an application containing a database control grid, the arrangement of data controls you
set in the design cell at runtime is replicated in each cell of the grid. Each cell displays a different record in a dataset.
The following table summarizes some of the unique properties for database control grids that you can set at design
time:
Selected database control grid properties
Property
Purpose
ColCount
Orientation
RowCount
ShowFocus True (default): Displays a focus rectangle around the current record's panel at runtime.
False: Does not display a focus rectangle.
1039
Purpose
First
Calls the dataset's First method to set the current record to the first record.
Prior
Calls the dataset's Prior method to set the current record to the previous record.
Next
Calls the dataset's Next method to set the current record to the next record.
Last
Calls the dataset's Last method to set the current record to the last record.
Insert
Calls the dataset's Insert method to insert a new record before the current record, and set the dataset in Insert state.
Delete
Deletes the current record. If the ConfirmDelete property is True it prompts for confirmation before deleting.
Edit
Puts the dataset in Edit state so that the current record can be modified.
Post
Cancel
Cancels edits to the current record, and returns the dataset to Browse state.
Refresh Clears data control display buffers, then refreshes its buffers from the physical table or query. Useful if the underlying
data may have been changed by another application.
See Displaying fly-over Help for information on associating help hints with each button. See Using a Single Navigator
for Multiple Datasets for information about associating a navigator with multiple datasets.
1040
1041
DBNavigatorAll.DataSource := OrderNum.DataSource;
end;
1042
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:
5 From the Rave category of the Tool 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
1043
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.
To design your report and create a report project file (.rav file) using the Rave Visual
Designer
1 Choose Tools
2 Choose File
New Data Object to display the Data Connections dialog box, and in the Data Object Type list,
select Direct Data View and click Next.
3 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.
4 Choose Tools
Report Wizards
Simple Table to display the Simple Table wizard, and select DataView1
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.
8 Choose File
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.
Rave Designer.
VCL components
The VCL components for Rave Reports are non-visual components that you add to a form in your VCL application.
They are available on the Rave category of the Tool palette. There are four categories of components: engine,
render, data connection and Rave project.
1044
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:
TRvRenderPreview
TRvRenderPrinter
TRvRenderPDF
TRvRenderHTML
TRvRenderRTF
TRvRenderText
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:
TRaveProjectManager
TRaveReport
1045
TRavePage
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:
TRaveDatabase
TRaveDriverDataView
TRaveDirectDataView
TRaveSimpleSecurity
TRaveLookupSecurity
Standard components
The Standard toolbar provides components that are frequently used when designing reports. The standard
components are:
TRaveText
TRaveMemo
TRaveSection
TRaveBitmap
TRaveMetaFile
TRaveFontMaster
TRavePageNumInit
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:
TRaveLine
TRaveHLine
TRaveVLine
TRaveSquare
TRaveRectangle
TRaveCircle
TRaveEllipse
Report components
The Report toolbar provides components that are used most often in data-aware reports. The report components are:
TRaveRegion
TRaveDataBand
TRaveBand
1046
Description
Rave Visual Designer Manual for Reference and Learning Provides detailed information about using the Rave Visual
Designer to create reports.
Rave Tutorial and Reference
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.
1047
1048
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.
The following topics are discussed in this section:
One-Dimensional Crosstabs
Multidimensional Crosstabs
1049
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 the following
figure shows the amount paid using each method.
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 the
following figure.
1050
A dataset, usually TDecisionQuery (for details, see Creating Decision Datasets with The Decision Query Editor)
or TQuery
A decision cube, TDecisionCube, bound to the dataset by setting its DataSet property to the dataset's name
A decision source, TDecisionSource, bound to the decision cube by setting its DecisionCube property to the
decision cube's name
3 Add a decision pivot, TDecisionPivot, and bind it to the decision source with the Object Inspector by setting its
DecisionSource property to the appropriate decision source name. The decision pivot is optional but useful; it
lets the form developer and end users change the dimensions displayed in decision grids or decision graphs by
pushing buttons.
In its default orientation, horizontal, buttons on the left side of the decision pivot apply to fields on the left side of
the decision grid (rows); buttons on the right side apply to fields at the top of the decision grid (columns).
You can determine where the decision pivot's buttons appear by setting its GroupLayout property to xtVertical,
xtLeftTop, or xtHorizontal (the default). For more information on decision pivot properties, see Using decision
pivots.
4 Add one or more decision grids and graphs, bound to the decision source. For details, see Creating and using
summaries to display in the grid or graph. The last field of the SQL SELECT should be the summary field. The
other fields in the SELECT must be GROUP BY fields. For instructions, see Creating decision datasets with the
Decision Query editor.
6 Set the Active property of the decision query (or alternate dataset component) to True.
7 Use the decision grid and graph to show and chart different data dimensions. See Using decision grids and Using
1051
The Decision Cube can pivot, subtotal, and drill-in correctly only for summaries whose cells are additive. (SUM and
COUNT are additive, while AVERAGE, MAX, and MIN are not.) Build pivoting crosstab displays only for grids that
contain only additive aggregators. If you are using non-additive aggregators, use a static decision grid that does not
pivot, drill, or subtotal.
Since averages can be calculated using SUM divided by COUNT, a pivoting average is added automatically when
SUM and COUNT dimensions for a field are included in the dataset. Use this type of average in preference to an
average calculated using an AVERAGE statement.
Averages can also be calculated using COUNT(*). To use COUNT(*) to calculate averages, include a "COUNT(*)
COUNTALL" selector in the query. If you use COUNT(*) to calculate averages, the single aggregator can be used
for all fields. Use COUNT(*) only in cases where none of the fields being summarized include blank values, or where
a COUNT aggregator is not available for every field.
The ordering of the SELECT fields should match the ordering of the GROUP BY fields. Queries are described in
more detail in Using TQuery.
With TTable, you must supply information to the decision cube about which of the fields in the query are grouping
fields, and which are summaries. To do this, Fill in the Dimension Type for each field in the DimensionMap of the
Decision Cube. You must indicate whether each field is a dimension or a summary, and if a summary, you must
provide the summary type. Since pivoting averages depend on SUM/COUNT calculations, you must also provide
the base field name to allow the decision cube to match pairs of SUM and COUNT aggregators.
For more complex queries involving multi-table joins, click the Query Builder button to display the SQL Builder
or type the SQL statement into the edit box on the SQL tab page.
4 Return to the Decision Query editor dialog box.
1052
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 Using decision cubes,Using
decision sources, and Using decision pivots.
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.
Memory Control, used to set the maximum number of dimensions and summaries that can be active at one
time, to display information about memory usage, and to determine the names and data that appear at design
time.
1054
The SparseCols and SparseRows properties of TDecisionSource indicate whether to display columns or rows
with no values; if True, sparse columns or rows are displayed.
TDecisionSource has the following events:
OnLayoutChange occurs when the user performs pivots or drill-downs that reorganize the data.
OnNewDimensions occurs when the data is completely altered, such as when the summary or dimension fields
are altered.
OnSummaryChange occurs when the current summary is changed.
OnStateChange occurs when the Decision Cube activates or deactivates.
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.
1055
TDecisionSource, with the Object Inspector by setting their DecisionSource property to the appropriate decision
source component.
3 Continue with steps 57 listed under Guidelines for using decision support components.
For a description of what appears in the decision grid and how to use it, see Using decision grids..
To add a graph to the form, follow the instructions in Creating decision graphs.
1056
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.
TDecisionSource, with the Object Inspector by setting their DecisionSource property to the appropriate decision
source component.
3 Continue with steps 57 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.
For a description of what appears in the decision graph and how to use it, see Using decision graphs.
To add a decision gridor crosstab tableto the form, follow the instructions in Creating and using decision grids.
You can use decision graphs instead of or in addition to decision grids, which present cross-tabulated data in tabular
form. Decision grids and decision graphs that are bound to the same decision source present the same data
dimensions. To show different summary data for the same dimensions, you can bind more than one decision graph
to the same decision source. To show different dimensions, bind decision graphs to different decision sources.
For example, in the following figure the first decision pivot and graph are bound to the first decision source and the
second decision pivot and graph are bound to the second. So, each graph can show different dimensions.
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.
1059
If you used a decision pivot, you can push its buttons to determine which decision cube fields (dimensions) are
graphed. To exchange graph axes, drag the decision pivot dimension buttons from one side of the separator space
to the other. If you have a one-dimensional graph with all buttons on one side of the separator space, you can use
the Row or Column icon as a drop target for adding buttons to the other side of the separator and making the graph
multidimensional.
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.
To customize a graph
1 Right-click it and choose Edit Chart. The Chart Editing dialog box appears.
2 Use the Chart page of the Chart Editing dialog box to view a list of visible series, select the series definition to
use when two or more are available for the same series, change graph types for a template or series, and set
overall graph properties.
The Series list on the Chart page shows all decision cube dimensions (preceded by Template:) and currently
visible categories. Each category, or series, is a separate object. You can:
Add or delete series derived from existing decision-graph series. Derived series can provide annotations for
existing series or represent values calculated from other series.
Change the default graph type, and change the title of templates and series.
3 Use the Series page to establish dimension templates, then customize properties for each individual graph series.
By default, all series are graphed as bar graphs and up to 16 default colors are assigned. You can edit the template
type and properties to create a new default. Then, as you pivot the decision source to different states, the template
is used to dynamically create the series for each new state. For template details, see Setting decision graph template
defaults..
To customize individual series, follow the instructions in Customizing decision graph series.
1060
You can
Change the default graph type.
Change other graph template properties.
View and set overall graph properties.
1061
1062
1064
Connecting to databases
Connecting to Databases: Overview
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:
Database connection components
Data Access Mechanism
Connection Component
TADOConnection
dbExpress
TSQLConnection
InterBase Express
TIBDatabase
Note: For a discussion of some pros and cons of each of these mechanisms, see Using Databases.
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.
For information about describing an ADO-based connection, see Connecting to a Data Store Using
TADOConnection.
For information about describing a dbExpress connection, see Setting up TSQLConnection.
For information about describing an InterBase Express connection, see TIBDatabase.
Although each type of dataset uses a different connection component, they are all descendants of
TCustomConnection. They all perform many of the same tasks and surface many of the same properties, methods,
and events.
The following topics discuss many of these common tasks:
Using Implicit Connections
Controlling Connections
1065
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.
The following topics provide details about opening and closing database connections:
Connecting to a Database Server
Disconnecting From a Database Server
1066
At design time, if a server requires a login, a standard login dialog box prompts for a user name and password when
you first attempt to connect to the database.
At runtime, there are three ways you can handle a server's request for a login:
The first way is to let the default login dialog and processes handle the login. This is the default approach. Set the
LoginPrompt property of the connection component to True (the default) and add DBLogDlg to the uses clause of
the unit that declares the connection component. Your application displays the standard login dialog box when the
server requests a user name and password.
The second way is to 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:
The third way is to 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
1068
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 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 StartTransaction method 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:
1069
TSQLConnection also uses the StartTransaction method, 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. 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.
When the actions that make up the transaction have all succeeded, you can make the database changes permanent
by committing the transaction. For TDatabase, you commit a transaction using the Commit method:
MyOracleConnection.Commit;
For TSQLConnection, you also use the Commit method, but you must specify which transaction you are committing
by supplying the transaction descriptor you gave to the StartTransaction method:
1070
MyOracleConnection.Commit(TD);
For TIBDatabase, you commit a transaction object using its Commit method:
IBDatabase1.DefaultTransaction.Commit;
Note: It is possible for a nested transaction to be committed, only to have the changes rolled back later if the parent
transaction is rolled back.
After the transaction is successfully committed, an ADO connection component receives an
OnCommitTransComplete event. Other connection components do not receive any similar events.
A call to commit the current transaction is usually attempted in a try...except statement. That way, if the transaction
cannot commit successfully, you can use the except block to handle the error and retry the operation or to roll back
the transaction.
If an error occurs when making the changes that are part of the transaction or when trying to commit the transaction,
you will want to discard all changes that make up the transaction. Discarding these changes is called rolling back
the transaction.
For TDatabase, you roll back a transaction by calling the Rollback method:
MyOracleConnection.Rollback;
For TSQLConnection, you also use the Rollback method, but you must specify which transaction you are rolling
back by supplying the transaction descriptor you gave to the StartTransaction method:
MyOracleConnection.Rollback(TD);
For TIBDatabase, you roll back a transaction object by calling its Rollback method:
IBDatabase1.DefaultTransaction.Rollback;
For TADOConnection, you roll back a transaction by calling the RollbackTrans method:
ADOConnection1.RollbackTrans;
After the transaction is successfully rolled back, an ADO connection component receives an
OnRollbackTransComplete event. Other connection components do not receive any similar events.
A call to roll back the current transaction usually occurs in
Exception handling code when you can't recover from a database error.
Button or menu event code, such as when a user clicks a Cancel button.
1071
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.
1073
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.
1074
var
I: Integer;
begin
with MyDBConnection do
begin
for I := 0 to DataSetCount - 1 do
DataSets[I].DisableControls;
end;
end;
Note: TADOConnection supports command objects as well as datasets. You can iterate through these much like
you iterate through the datasets, by using the Commands and CommandCount properties.
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.
GetTableNames has two parameters: the string list to fill with table names, and a boolean that indicates whether the
list should include system tables, or ordinary tables. Note that not all servers use system tables to store metadata,
so asking for system tables may result in an empty list.
Note: For most database connection components, GetTableNames returns a list of all available non-system tables
when the second parameter is False. For TSQLConnection, however, you have more control over what type
is added to the list when you are not fetching only the names of system tables. When using
TSQLConnection, the types of names added to the list are controlled by the TableScope property.
TableScope indicates whether the list should contain any or all of the following: ordinary tables, system tables,
synonyms, and views.
1075
Note: GetIndexNames is only available for TSQLConnection, although most table-type datasets have an equivalent
method.
To convert the parameter descriptions that are added to the list into the more familiar TParams object, call the global
LoadParamListItems procedure. Because GetProcedureParams dynamically allocates the individual records, your
application must free them when it is finished with the information. The global FreeProcParams routine can do this
for you.
Note: GetProcedureParams is only available for TSQLConnection.
1076
Understanding datasets
Understanding Datasets: Overview
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.
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 "Working with field components."
The following topics describe 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.
Using TDataSet Descendants
Determining Dataset States
Opening and Closing Datasets
Navigating Datasets
Searching Datasets
Displaying and Editing a Subset of Data Using Filters
Modifying Data
Calculating Fields
1077
Types of Datasets
1078
Value
State
Meaning
dsInactive
Inactive
dsBrowse
Browse
DataSet open. Its data can be viewed, but not changed. This is the default state of an open
dataset.
dsEdit
Edit
DataSet open. The current row can be modified. (not supported on unidirectional datasets)
dsInsert
Insert
DataSet open. A new row is inserted or appended. (not supported on unidirectional datasets)
dsSetKey
SetKey
DataSet open. Enables setting of ranges and key values used for ranges and GotoKey
operations. (not supported by all datasets)
dsCalcFields
CalcFields
DataSet open. Indicates that an OnCalcFields event is under way. Prevents changes to fields
that are not calculated.
dsCurValue
CurValue
DataSet open. Indicates that the CurValue property of fields is being fetched for an event
handler that responds to errors in applying cached updates.
dsNewValue
NewValue
DataSet open. Indicates that the NewValue property of fields is being fetched for an event
handler that responds to errors in applying cached updates.
dsOldValue
OldValue
DataSet open. Indicates that the OldValue property of fields is being fetched for an event handler
that responds to errors in applying cached updates.
dsFilter
Filter
DataSet open. Indicates that a filter operation is under way. A restricted set of data can be
viewed, and no data can be changed. (not supported on unidirectional datasets)
dsBlockRead
Block Read
DataSet open. Data-aware controls are not updated and events are not triggered when the
current record changes.
dsInternalCalc Internal Calc DataSet open. An OnCalcFields event is underway for calculated values that are stored with
the record. (client datasets only)
dsOpening
Opening
DataSet is in the process of opening but has not finished. This state occurs when the dataset
is opened for asynchronous fetching.
Typically, an application checks the dataset state to determine when to perform certain tasks. For example, you
might check for the dsEdit or dsInsert state to ascertain whether you need to post updates.
Note: Whenever a dataset's state changes, the OnStateChange event is called for any data source components
associated with the dataset. For more information about data source components and OnStateChange, see
Responding to Changes Mediated by the Data Source.
Sample Code
CustTable.Active := True;
CustQuery.Open;
When you open the dataset, the dataset first receives a BeforeOpen event, then it opens a cursor, populating itself
with data, and finally, it receives an AfterOpen event.
The newly-opened dataset is in browse mode, which means your application can read the data and navigate through
it.
1079
Sample Code
CustQuery.Active := False;
CustTable.Close;
Just as the dataset receives BeforeOpen and AfterOpen events when you open it, it receives a BeforeClose and
AfterClose event when you close it. 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:
Navigational methods of datasets
Method Moves the Cursor to
First
Last
Next
Prior
The data-aware, visual component TDBNavigator encapsulates these methods as buttons that users can click to
move among records at runtime. For information about the navigator component, see Navigating and manipulating
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
1080
(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.
Navigational properties of datasets
Property
Description
BOF (Beginning-of-file) True: the cursor is at the first row in the dataset.
False: the cursor is not known to be at the first row in the dataset
EOF (End-of-file)
The following topics discuss these properties and methods in more detail:
Using the First and Last methods
Using the Next and Prior methods
Using the MoveBy method
Using the Eof and Bof Properties
Marking and Returning to Records
The Last method moves the cursor to the last row in a dataset and sets the EOF property to True. If the cursor is
already at the last row in the dataset, Last does nothing.
The following code moves to the last record in CustTable:
CustTable.Last;
For example, the following code moves to the next record in CustTable:
CustTable.Next;
The Prior method moves the cursor back one row in the dataset, and setsEOF to False if the dataset is not empty.
If the cursor is already at the first row in the dataset when you call Prior, Prior does nothing.
For example, the following code moves to the previous record in CustTable:
CustTable.Prior;
Note: If your application uses MoveBy in a multi-user database environment, keep in mind that datasets are fluid.
A record that was five records back a moment ago may now be four, six, or even an unknown number of
records back if several users are simultaneously accessing the database and changing its data.
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.
1082
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 }
.
.
1083
.
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;
1084
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.
The following topics discuss Locate and Lookup in greater detail:
Using Locate
Using Lookup
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.
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;
1085
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.',
Contact; Phone');
end;
'Company;
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.
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
1086
Like Locate, Lookup uses the fastest possible method to locate matching records. If the columns to search are
indexed, Lookup uses the index.
When filtering is enabled, only those records that meet the filter criteria are available to an application. Filtering is
always a temporary condition. You can turn off filtering by setting the Filtered property to False.
Creating Filters
There are two ways to create a filter for a dataset:
Set 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
1087
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.
You can also supply a value for Filter based on text supplied by the user. For example, the following statement
assigns the text in from edit box to Filter:
Dataset1.Filter := Edit1.Text;
You can, of course, create a string based on both hard-coded text and user-supplied data:
Dataset1.Filter := 'State = ' + QuotedStr(Edit1.Text);
Blank field values do not appear unless they are explicitly included in the filter:
Dataset1.Filter := 'State <> ''CA'' or State = BLANK';
Note: After you specify a value for Filter, to apply the filter to the dataset, set the Filtered property to True.
Filters can compare field values to literals and to constants using the following comparison and logical operators:
Comparison and logical operators that can appear in a filter
Operator Meaning
<
Less than
>
Greater than
>=
<=
Equal to
<>
Not equal to
AND
NOT
1088
OR
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)
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.
When filtering is enabled, an OnFilterRecord event is generated for each record retrieved. The event handler tests
each record, and only those that meet the filter's conditions are displayed. Because the OnFilterRecord event is
generated for every record in a dataset, you should keep the event handler as tightly coded as possible to avoid
adversely affecting the performance.
You can code any number of OnFilterRecord event handlers and switch among them at runtime. For example, the
following statements switch to an OnFilterRecord event handler called NewYorkFilter:
DataSet1.OnFilterRecord := NewYorkFilter;
Refresh;
1089
Value
Meaning
foCaseInsensitive
foNoPartialCompare Disable partial string matching; that is, don't match strings that end with an asterisk (*).
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');
Purpose
FindFirst Move to the first record that matches the current filter criteria. The search for the first matching record always begins
at the first record in the unfiltered dataset.
FindLast Move to the last record that matches the current filter criteria.
FindNext Moves from the current record in the filtered dataset to the next one.
FindPrior Move from the current record in the filtered dataset to the previous one.
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.)
1090
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 dataaware 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
1091
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.
On forms in your application, the data-aware grid and navigator controls can put a dataset into dsInsert state if
The control's ReadOnly property is False (the default), and
CanModify is True for the dataset.
Note: Even if a dataset is in dsInsert state, adding 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 dsInsert mode, a user or application can enter values into the fields associated with the new
record. Except for the grid and navigational controls, there is no visible difference to a user between Insert and
Append. On a call to Insert, an empty row appears in a grid above what was the current record. On a call to
Append, the grid is scrolled to the last record in the dataset, an empty row appears at the bottom of the grid, and
the Next and Last buttons are dimmed on any navigator component associated with the dataset.
Data-aware controls for which inserting is enabled automatically call Post when a user executes any action that
changes which record is current (such as moving to a different record in a grid). Otherwise you must call Post in
your code.
Post writes the new record to the database, or, if you are caching updates, Post writes the record to an in-memory
cache. To write cached inserts and appends to the database, call the dataset's ApplyUpdates method.
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.
1092
For SQL databases, the physical location of the insertion is implementation-specific. If the table is indexed, the
index is updated with the new record information.
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.
1093
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.
Description
AppendRecord([array of values]) Appends a record with the specified column values at the end of a table; analogous to
Append. Performs an implicit Post.
InsertRecord([array of values])
Inserts the specified values as a record before the current cursor position of a table;
analogous to Insert. Performs an implicit Post.
SetFields([array of values])
Sets the values of the corresponding fields; analogous to assigning values to TFields. The
application must perform an explicit Post.
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.
1094
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".
To update the record, an application could use the following code:
with CountryTable do
begin
if Locate('Name', 'Japan', loCaseInsensitive) then;
begin
Edit;
SetFields(nil, nil, nil, 344567, 164700000);
Post;
end;
end;
This code assigns values to the Area and Population fields and then posts them to the database. The three NULL
pointers act as place holders for the first three columns to preserve their current contents.
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.
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).
Warning: 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.
1095
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 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:
1096
TADODataSet and TSQLDataSethave 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.
the dataset. The data source component is used to pass a result set from the dataset to data-aware components
for display.
Emptying Tables
Synchronizing Tables
Note: For Paradox tables, the primary index is unnamed, and is therefore not returned by GetIndexNames. You
can still change the index back to a primary index on a Paradox table after using an alternative index, however,
by setting the IndexName property to a blank string.
To obtain information about the fields of the current index, use the
IndexFieldCount property, to determine the number of columns in the index.
IndexFields property, to examine a list the field components for the columns that comprise the index.
The following code illustrates how you might use IndexFieldCount and IndexFields to iterate through a list of column
names in an application:
var
I: Integer;
ListOfIndexFields: array[0 to 20} of string;
begin
with CustomersTable do
begin
1098
for I := 0 to IndexFieldCount - 1 do
ListOfIndexFields[I] := IndexFields[I].FieldName;
end;
end;
Note: IndexFieldCount is not valid for a dBASE table opened on an expression index.
For information on specifying dBASE non-production index files and dBASE III PLUS-style .NDX files, see Specifying
a dBASE index file
Note: If you use IndexFieldNames on Paradox and dBASE tables, the dataset attempts to find an index that uses
the columns you specify. If it cannot find such an index, it raises an exception.
TTable and all types of client dataset support similar indexed-based searches, but use a combination of related
methods. The following table summarizes the six related methods provided by TTable and client datasets to support
index-based searches:
Index-based search methods
Method
Purpose
EditKey
Preserves the current contents of the search key buffer and puts the dataset into dsSetKey state so your
application can modify existing search criteria prior to executing a search.
FindKey
Searches for the first record in a dataset that exactly matches the search criteria, and moves the cursor to that
record if one is found.
GotoNearest Searches on string-based fields for the closest match to a record based on partial key values, and moves the
cursor to that record.
SetKey
Clears the search key buffer and puts the table into dsSetKey state so your application can specify new search
criteria prior to executing a search.
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.
The following topics discuss the Goto and Find methods in greater detail:
Executing a Search with Goto Methods
Executing a Search with Find Methods
Specifying the Current Record After a Successful Search
Searching on Partial Keys
Repeating or Extending a Search
list of field components you can access by specifying ordinal numbers corresponding to columns. The first column
number in a dataset is 0.
5 Search for and move to the first matching record found with GotoKey or GotoNearest.
For example, the following code, attached to a button's OnClick event, uses the GotoKey method to move to the first
record where the first field in the index has a value that exactly matches the text in an edit box:
1100
GotoNearest is similar. It searches for the nearest match to a partial field value. It can be used only for string fields.
For example,
Table1.SetKey;
Table1.Fields[0].AsString := 'Sm';
Table1.GotoNearest;
If a record exists with "Sm" as the first two characters of the first indexed field's value, the cursor is positioned on
that record. Otherwise, the position of the cursor does not change and GotoNearest returns False.
parameter, a comma-delimited list of field values, where each value corresponds to an indexed column in the
underlying table.
Note: FindNearest can only be used for string fields.
1101
Specifying Ranges
There are two mutually exclusive ways to specify a range:
Specify the beginning and ending separately using SetRangeStart and SetRangeEnd.
1102
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.
1103
ApplyRange;
end;
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 a multi-column index, you can specify starting and ending values 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. To omit a value for
the first field in an index, and specify values for successive fields, pass a null value for the omitted field.
Always specify the ending values for a range, even if you want a range to end on the last record in the dataset. If
you do not provide ending values, the dataset assumes the ending value of the range is a null value. A range with
null ending values is always empty because the starting range is greater than or equal to the ending range.
This code includes all records in a range where LastName is greater than or equal to "Smith." The value specification
could also be:
Contacts['LastName'] := 'Sm';
This statement includes records that have LastName greater than or equal to "Sm."
1104
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.
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.
1105
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;
.
.
.
1106
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.
Property
First TTable
DatabaseName: DBDEMOS
TableName: CUSTOMER
Name: CustomersTable
Second TTable
DatabaseName: DBDEMOS
TableName: ORDERS
Name: OrdersTable
First TDataSource
Name: CustSource
DataSet: CustomersTable
Second TDataSource
Name: OrdersSource
DataSet: OrdersTable
Use Unit to specify that the form should use the data module.
5 Set the DataSource property of the first grid component to "CustSource", and set the DataSource property of the
1107
Choose OK to commit your selections and exit the Field Link Designer.
8 Set the Active properties of CustomersTable and OrdersTable to True to display data in the grids on the form.
9 Compile and run the application.
If you run the application now, you will see that the tables are linked together, and that when you move to a new
record in the CUSTOMER table, you see only those records in the ORDERS table that belong to the current
customer.
1108
ReadOnly determines whether a user can both view and edit data. When ReadOnly is False (the default), a user
can both view and edit data. To restrict a user to viewing data, set ReadOnly to True before opening the table.
Note: ReadOnly is implemented on all table type datasets except TSQLTable, which is always read-only.
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.
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.
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.
Note: You can't define indexes for the new table if you are using persistent field components instead of field
definition objects.
1109
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;
TableFound := Exists; // check whether the table already exists
if TableFound then
if MessageDlg('Overwrite existing table ' + Edit1.Text + '?',
1110
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;
Warning: 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.
Table Type
Method
TTable and TIBTable You can delete all the records by calling the EmptyTable method at runtime:
PhoneTable.EmptyTable;
TADOTable
TSQLTable
You can use the DeleteRecords method. Note, that the TSQLTable version of DeleteRecords never
takes any parameters:
PhoneTable.DeleteRecords;
EmptyDataSet
Note: For tables on SQL servers, these methods only succeed if you have DELETE privilege for the table.
Warning: When you empty a dataset, the data you delete is gone forever.
1111
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);
statement.
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.
1112
In addition to the basic steps described above, the following topics describe how to establish master/detsil
relationships when using query-type datasets and how to improve performance when you only need a unidirectional
cursor:
Establishing master/detail relationships using parameters
Using unidirectional result sets
Queries that do not return records include statements that use Data Definition Language (DDL) or Data Manipulation
Language (DML) statements other than SELECT statements (For example, INSERT, DELETE, UPDATE, CREATE
INDEX, and ALTER TABLE commands do not return any records). The language used in commands is serverspecific, but usually compliant with the SQL-92 standard for the SQL language.
The SQL command you execute must be acceptable to the server you are using. Datasets neither evaluate the SQL
nor execute it. They merely pass the command to the server for execution. In most cases, the SQL command must
be only one complete SQL statement, although that statement can be as complex as necessary (for example, a
SELECT statement with a WHERE clause that uses several nested logical operators such as AND and OR). Some
servers also support "batch" syntax that permits multiple statements; if your server supports such syntax, you can
enter multiple statements when you specify the query.
The SQL statements used by queries can be verbatim, or they can contain replaceable parameters. Queries that
use parameters are called parameterized queries. When you use parameterized queries, the actual values assigned
to the parameters are inserted into the query before you execute, or run, the query. Using parameterized queries is
very flexible, because you can change a user's view of and access to data on the fly at runtime without having to
alter the SQL statement. For more information about parameterized queries, see Using parameters in queries.
1113
The code below demonstrates modifying only a single line in an existing SQL statement. In this case, the ORDER
BY clause already exists on the third line of the statement. It is referenced via the SQL property using an index of 2.
MyQuery.SQL[2] := 'ORDER BY OrderNo';
Note: The dataset must be closed when you specify or modify the SQL property.
At design time, use the String List editor to specify the query. Click the ellipsis button by the SQL property in the
Object Inspector to display the String List editor.
Note: With some versions of Delphi, if you are using TQuery, you can also use the SQL Builder to construct a query
based on a visible representation of tables and fields in a database. To use the SQL Builder, select the query
component, right-click it to invoke the context menu, and choose Graphical Query Editor. To learn how to
use SQL Builder, open it and use its online help.
Because the SQL property is a TStrings object, you can load the text of the query from a file by calling the TStrings.
LoadFromFile method:
MyQuery.SQL.LoadFromFile('custquery.sql');
You can also use the Assign method of the SQL property to copy the contents of a string list object into the SQL
property. The Assign method automatically clears the current contents of the SQL property before copying the new
statement:
MyQuery.SQL.Assign(Memo1.Lines);
At design time, you can type the query directly into the Object Inspector, or, if the dataset already has an active
connection to the database, you can click the ellipsis button by the CommandText property to display the Command
Text editor. The Command Text editor lists the available tables, and the fields in those tables, to make it easier to
compose your queries.
In this SQL statement, :Name, :Capital, and :Population are placeholders for actual values supplied to the statement
at runtime by your application. Note that the names of parameters begin with a colon. The colon is required so that
the parameter names can be distinguished from literal values. You can also include unnamed parameters by adding
1114
a question mark (?) to your query. Unnamed parameters are identified by position, because they do not have unique
names.
Before the dataset can execute the query, you must supply values for any parameters in the query text. TQuery,
TIBQuery, TSQLQuery, and client datasets use the Params property to store these values. TADOQuery uses the
Parameters property instead. Params (or Parameters) is a collection of parameter objects (TParam or TParameter),
where each object represents a single parameter. When you specify the text for the query, the dataset generates
this set of parameter objects, and (depending on the dataset type) initializes any of their properties that it can deduce
from the query.
Note: You can suppress the automatic generation of parameter objects in response to changing the query text by
setting the ParamCheck property to False. This is useful for data definition language (DDL) statements that
contain parameters as part of the DDL statement that are not parameters for the query itself. For example,
the DDL statement to create a stored procedure may define parameters that are part of the stored procedure.
By setting ParamCheck to False, you prevent these parameters from being mistaken for parameters of the
query.
Parameter values must be bound into the SQL statement before it is executed for the first time. Query components
do this automatically for you even if you do not explicitly call the Prepare method before executing a query.
Tip: It is a good programming practice to provide variable names for parameters that correspond to the actual name
of the column with which it is associated. For example, if a column name is "Number," then its corresponding
parameter would be ":Number". Using matching names is especially important if the dataset uses a data source
to obtain parameter values from another dataset. This process is described in Establishing master/detail
relationships using parameters.
The following topics describe how to specify the datatypes and values of parameters for your query:
Supplying Parameters at Design Time
Supplying Parameters at Runtime
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.
The same code can be rewritten using the Params property, using an index of 0 (assuming the :Capital parameter
is the first parameter in the SQL statement):
SQLQuery1.Params[0].AsString := Edit1.Text;
The command line below sets three parameters at once, using the Params.ParamValues property:
Query1.Params.ParamValues['Name;Capital;Continent'] :=
VarArrayOf([Edit1.Text, Edit2.Text, Edit3.Text]);
Note that ParamValues uses Variants, avoiding the need to cast values.
1116
If parameter values for a parameterized query are not bound at design time or specified at runtime, query-type
datasets attempt to supply values for them based on the DataSource property. DataSource identifies a different
dataset that is searched for field names that match the names of unbound parameters. This search dataset can be
any type of dataset. The search dataset must be created and populated before you create the detail dataset that
uses it. If matches are found in the search dataset, the detail dataset binds the parameter values to the values of
the fields in the current record pointed to by the data source.
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.
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 datasetis 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.
1117
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.
Tip: If you are executing the query multiple times, it is a good idea to set the Prepared property to True.
Although the query does not return any records, you may want to know the number of records it affected (for example,
the number of records deleted by a DELETE query). The RowsAffected property gives the number of affected records
after a call to ExecSQL.
Tip: When you do not know at design time whether the query returns a result set (for example, if the user supplies
the query dynamically at runtime), you can code both types of query execution statements in a try...except
block. Put a call to the Open method in the try clause. An action query is executed when the query is activated
with the Open method, but an exception occurs in addition to that. Check the exception, and suppress it if it
merely indicates the lack of a result set. (For example, TQuery indicates this by an ENoResultSet exception.)
1118
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).
StoredProcName property. The one exception is TADOStoredProc, which has a ProcedureName property
instead.
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.
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 Preparing stored procedures.
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.
Input/output parameters, used to pass values to a stored procedure for processing, and used by the stored
procedure to pass return values to the application.
A result parameter, used by some stored procedures to return an error or status value to an application. A stored
procedure can only return one result parameter.
Whether a stored procedure uses a particular type of parameter depends both on the general language
implementation of stored procedures on your database server and on a specific instance of a stored procedure. For
any server, individual stored procedures may or may not use input parameters. On the other hand, some uses of
parameters are server-specific. For example, on MS-SQL Server and Sybase stored procedures always return a
result parameter, but the InterBase implementation of a stored procedure never returns a result parameter.
Access to stored procedure parameters is provided by the Params property (in TStoredProc, TSQLStoredProc,
TIBStoredProc) or the Parameters property (in TADOStoredProc). When you assign a value to the
StoredProcName (or ProcedureName) property, the dataset automatically generates objects for each parameter of
the stored procedure. For some datasets, if the stored procedure name is not specified until runtime, objects for
each parameter must be programmatically created at that time. Not specifying the stored procedure and manually
creating the TParam or TParameter objects allows a single dataset to be used with any number of available stored
procedures.
Note: Some stored procedures return a dataset in addition to output and result parameters. Applications can display
dataset records in data-aware controls, but must separately process output and result parameters.
1120
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.
Even if you do not need to add the individual parameter objects at runtime, you may want to access individual
parameter objects to assign values to input parameters and to retrieve values from output parameters. You can use
the dataset's ParamByName method to access individual parameters based on their names. For example, the
following code sets the value of an input/output parameter, executes the stored procedure, and retrieves the returned
value:
1121
with SQLStoredProc1 do
begin
ParamByName('IN_OUTVAR').AsInteger := 103;
ExecProc;
IntegerVar := ParamByName('IN_OUTVAR').AsInteger;
end;
When you explicitly prepare the dataset, the resources allocated for executing the stored procedure 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 change the parameters when using Oracle overloaded procedures).
Note: TADOStoredProc does not have a ParamByName method. To obtain output parameter values when using
ADO, access parameter objects using the Parameters property.
Tip: If you are executing the procedure multiple times, it is a good idea to set the Prepared property to True.
1122
begin
DataSet2 := SQLStoredProc1.NextRecordSet;
...
In TSQLStoredProc, NextRecordSet returns a newly created TCustomSQLDataSet component that provides access
to the next set of records. In TADOStoredProc, NextRecordset returns an interface that can be assigned to the
RecordSet property of an existing ADO dataset. For either class, the method returns the number of records in the
returned dataset as an output parameter.
The first time you call NextRecordSet, it returns the second set of records. Calling NextRecordSet again returns a
third dataset, and so on, until there are no more sets of records. When there are no additional cursors,
NextRecordSet returns nil.
1123
Purpose
Alignment
DisplayWidth
DisplayFormat Specifies data formatting for display (such as how many decimal places to show).
EditFormat
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 topics discuss field components in greater detail:
Dynamic Field Components
Persistent Field Components
Working with Field Component Methods at Runtime
1124
of the data and setting any properties that specify what data the dataset represents.
3 Associate the data sources with the datasets.
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.
1125
Create new fields, such as lookup fields, calculated fields, and aggregated fields, that base their values on
existing fields in a dataset.
Validate data entry.
Remove field components from the list of persistent components to prevent your application from accessing
particular columns in an underlying database.
Define new fields to replace existing fields, based on columns in the table or query underlying a dataset.
At design time, you canand shoulduse the Fields editor to create persistent lists of the field components used
by the datasets in your application. Persistent field component lists are stored in your application, and do not change
even if the structure of a database underlying a dataset is changed. Once you create persistent fields with the Fields
editor, you can also create event handlers for them that respond to changes in data values and that validate data
entries.
Note: When you create persistent fields for a dataset, only those fields you select are available to your application
at design time and runtime. At design time, you can always use the Fields editor to add or remove persistent
fields for a dataset.
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.
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.
The following topics describe how to use the Fields editor to create or modify the persistent fields in a dataset, and
how to work with persistent fields:
Creating Persistent Fields
Arranging Persistent Fields
Defining New Persistent Fields
Deleting Persistent Field Components
Setting Persistent Field Properties and Events
component or provider and specifying any properties to describe the data. For example, If you are using
TADODataSet, you can set the Connection property to a properly configured TADOConnection component and
set the CommandText property to a valid query.
3 Double-click the dataset component in the data module to invoke the Fields editor. The Fields editor contains a
The title bar of the Fields editor displays both the name of the data module or form containing the dataset, and
the name of the dataset itself. For example, if you open the Customers dataset in the CustomerData data module,
the title bar displays 'CustomerData.Customers,' or as much of the name as fits.
Below the title bar is a set of navigation buttons that let you scroll one-by-one through the records in an active
dataset at design time, and to jump to the first or last record. The navigation buttons are dimmed if the dataset
is not active or if the dataset is empty. If the dataset is unidirectional, the buttons for moving to the last record
and the previous record are always dimmed.
The list box displays the names of persistent field components for the dataset. The first time you invoke the Fields
editor for a new dataset, the list is empty because the field components for the dataset are dynamic, not persistent.
If you invoke the Fields editor for a dataset that already has persistent field components, you see the field
component names in the list box.
4 Right click in the Fields editor and choose Add Fields.
5 Select the fields to make persistent in the Add Fields dialog box. By default, all fields are selected when the dialog
If you select a noncontiguous set of fields and drag them to a new location, they are inserted as a contiguous block.
Within the block, the order of fields does not change.
Alternatively, you can select the field, and use Ctrl+Up and Ctrl+Dn to change an individual field's order in the list.
1127
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:
Special persistent field kinds
Field Kind
Purpose
Data
Calculated
Lookup
Retrieve values from a specified dataset at runtime based on search criteria you specify. (not supported by
unidirectional datasets)
InternalCalc Displays values calculated at runtime by a client dataset and stored with its data.
Aggregate
Displays a value summarizing the data in a set of records from a client dataset.
The Lookup definition group box is only used to create lookup fields. This is described more fully in Defining a lookup
field.
The following topics describe how to create different field types:
Defining a Data Field
Defining a Calculated Field
Defining a Lookup Field
Defining an Aggregate Field
1128
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,
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.
with a client dataset. The significant difference between these types of calculated fields is that the values
calculated for an InternalCalc field are stored and retrieved as part of the client dataset's data.
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.
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.
For example, suppose you have created a CityStateZip calculated field for the Customers table on the
CustomerData data module. CityStateZip should display a company's city, state, and zip code on a single line in a
data-aware control.
To add code to the CalcFields procedure for the Customers table, select the Customers table from the Object
Inspector drop-down list, switch to the Events page, and double-click the OnCalcFields property.
The TCustomerData.CustomersCalcFields procedure appears in the unit's source code window. Add the following
code to the procedure to calculate the field:
CustomersCityStateZip.Value := CustomersCity.Value + ', ' + CustomersState.Value
CustomersZip.Value;
+ ' ' +
Note: When writing the OnCalcFields event handler for an internally calculated field, you can improve performance
by checking the client dataset's State property and only recomputing the value when State is
dsInternalCalc. See Using internally calculated fields in client datasets for details.
dataset must be different from the dataset for the field component itself, or a circular reference exception is raised
at runtime. Specifying a lookup dataset enables the Lookup Keys and Result Field combo boxes.
6 Choose from the Key Fields drop-down list a field in the current dataset for which to match values. To match
more than one field, enter field names directly instead of choosing from the drop-down list. Separate multiple
field names with semicolons. If you are using more than one field, you must use persistent field components.
1130
7 Choose from the Lookup Keys drop-down list a field in the lookup dataset to match against the Source Fields
field you specified in step 6. If you specified more than one key field, you must specify the same number of lookup
keys. To specify more than one field, enter field names directly, separating multiple field names with semicolons.
8 Choose from the Result Field drop-down list a field in the lookup dataset to return as the value of the lookup field
1131
Note: You can also delete selected fields by invoking the context menu and choosing Delete.
Fields you remove are no longer available to the dataset and cannot be displayed by data-aware controls. You can
always recreate a persistent field component that you delete by accident, but any changes previously made to its
properties or events is lost. For more information, see Creating persistent fields
Note: If you remove all persistent field components for a dataset, the dataset reverts to using dynamic field
components for every column in the underlying database table.
Purpose
Alignment
Left justifies, right justifies, or centers a field contents within a data-aware component.
ConstraintErrorMessage Specifies the text to display when edits clash with a constraint condition.
CustomConstraint
Currency
Numeric fields only. True: displays monetary values.False (default): does not display monetary
values.
DisplayFormat
DisplayLabel
DisplayWidth
Specifies the width, in characters, of a grid column that display this field.
EditFormat
1132
EditMask
Limits data-entry in an editable field to specified types and ranges of characters, and specifies any
special, non-editable characters that appear within the field (hyphens, parentheses, and so on).
FieldKind
FieldName
Specifies the actual name of a column in the table from which the field derives its value and data type.
HasConstraints
ImportedConstraint
Specifies an SQL constraint imported from the Data Dictionary or an SQL server.
Index
LookupDataSet
Specifies the table used to look up field values when Lookup is True.
LookupKeyFields
Specifies the field(s) in the lookup dataset to match when doing a lookup.
LookupResultField
Specifies the field in the lookup dataset from which to copy values into this field.
MaxValue
Numeric fields only. Specifies the maximum value a user can enter for the field.
MinValue
Numeric fields only. Specifies the minimum value a user can enter for the field.
Name
Origin
Precision
ReadOnly
True: Displays field values in data-aware controls, but prevents editing. False (the default): Permits
display and editing of field values.
Size
Specifies the maximum number of characters that can be displayed or entered in a string-based
field, or the size, in bytes, of TBytesField and TVarBytesField fields.
Tag
Long integer bucket available for programmer use in every component as needed.
Transliterate
True (default): specifies that translation to and from the respective locales will occur as data is
transferred between a dataset and a database. False: Locale translation does not occur.
Visible
True (the default): Permits display of field in a data-aware grid.False: Prevents display of field in a
data-aware grid component.User-defined components can make display decisions based on this
property.
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.
And this statement changes field ordering by setting the Index property of the CityStateZip field in the Customers
table to 3:
1133
CustomersCityStateZip.Index := 3;
The name for the attribute set defaults to the name of the current field. You can specify a different name for the
attribute set by choosing Save Attributes As instead of Save Attributes from the context menu.
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.
Data Dictionary that has the same name as the current field, that set name appears in the edit box.
Warning: If the attribute set in the Data Dictionary is changed at a later date, you must reapply the attribute set to
each field component that uses it. You can invoke the Fields editor and multi-select field components
within a dataset when reapplying attributes.
1134
Warning: Unassociating an attribute set does not change any field properties. A field retains the settings it had
when the attribute set was applied to it. To change these properties, select the field in the Fields editor
and set its properties in the Object Inspector.
Used by . . .
FormatFloat
TFloatField, TCurrencyField
FormatDateTime
1135
SQLTimeStampToString TSQLTimeStampField
FormatCurr
TCurrencyField, TBCDField
BcdToStrF
TFMTBCDField
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:
Field component events
Event
Purpose
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.
field in the record, you want the data-aware control containing the faulty data to have focus so that the user can
enter corrections.
You control focus for a field's data-aware components with a field's FocusControl method. FocusControl sets focus
to the first data-aware control in a form that is associated with a field. An event handler should call a field's
FocusControl method before validating the field. The following code illustrates how to call the FocusControl method
for the Company field in the Customers table:
CustomersCompany.FocusControl;
The following table lists some other field component methods and their uses. For a complete list and detailed
information about using each method, see TField.
Selected field component methods
Method
Purpose
AssignValue Sets a field value to a specified value using an automatic conversion function based on the field's type.
Clear
GetData
IsValidChar
Determines if a character entered by a user in a data-aware control to set a value is allowed for this field.
SetData
1137
This method works well for string values, but may require additional programming to handle conversions for other
data types. Fortunately, field components have built-in properties for handling conversions.
Note: You can also use Variants to access and set field values.
AsDateTime,
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
1138
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.
The following table lists permissible conversions that produce special results:
Special conversion results
Conversion
Result
String to Boolean
Converts "True," "False," "Yes," and "No" to Boolean. Other values raise exceptions.
Float to Integer
DateTime or SQLTimeStamp to Float Converts date to number of days since 12/31/1899, time to a fraction of 24 hours.
Boolean to String
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);
Because the FieldValues property is of type Variant, it automatically converts other datatypes into a Variant value.
1139
To use the Fields property you must know the order of and data types of fields in the dataset. You use an ordinal
number to specify the field to access. The first field in a dataset is numbered 0. Field values must be converted as
appropriate using each field component's conversion properties.
For example, the following statement assigns the current value of the seventh column (Country) in the Customers
table to an edit control:
Edit1.Text := CustTable.Fields[6].AsString;
Conversely, you can assign a value to a field by setting the Fields property of the dataset to the desired field. For
example:
begin
Customers.Edit;
Customers.Fields[6].AsString := Edit1.Text;
Customers.Post;
end;
1140
The name used to refer to the value of the field can be any string that is not a reserved SQL keyword, as long as it
is used consistently throughout the constraint expression.
Note: Custom constraints are only available in BDE-enabled and client datasets.
Custom constraints are imposed in addition to any constraints to the field's value that come from the server. To see
the constraints imposed by the server, read the ImportedConstraint property.
Do not change the value of ImportedConstraint, except to edit nonstandard or server-specific SQL that has been
imported as a comment because it cannot be interpreted by the database engine.
To add additional constraints on the field value, use the CustomConstraint property. Custom constraints are imposed
in addition to the imported constraints. If the server constraints change, the value of ImportedConstraint also changed
but constraints introduced in the CustomConstraint property persist.
Removing constraints from the ImportedConstraint property will not change the validity of field values that violate
those constraints. Removing constraints results in the constraints being checked by the server instead of locally.
When constraints are checked locally, the error message supplied as the ConstraintErrorMessage property is
displayed when violations are found, instead of displaying an error message from the server.
1141
TADTField
TArrayField
TDataSetField
TReferenceField
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.
Common object field descendant properties
Property
Purpose
Fields
Given these persistent fields, you can simply access the child fields of an ADT field by name:
CityEdit.Text := CustomerAddrCity.AsString;
Although persistent fields are the easiest way to access ADT child fields, it is not possible to use them if the structure
of the dataset is not known at design time. When accessing ADT child fields without using persistent fields, you must
set the dataset's ObjectView property to True.
1143
Note that you can omit the property name (FieldValues) because FieldValues is the dataset's default property.
Note: Unlike other runtime methods for accessing ADT child field values, the FieldValues property works even if
the dataset's ObjectView property is False.
Because FieldValues is the default property of TADTField, the property name (FieldValues) can be omitted. Thus,
the following statement is equivalent to the one above:
CityEdit.Text := TADTField(Customer.FieldByName('Address'))[1];
or by name:
CityEdit.Text := TADTField(Customer.FieldByName('Address')).Fields.FieldByName
('City').AsString;
1144
CustomerTelNos_Array4: TStringField;
CustomerTelNos_Array5: TStringField;
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;
Because FieldValues is the default property of TArrayField, this can also be written
TelEdit.Text := TArrayField(Customer.FieldByName('TelNos_Array'))[1];
1145
dataset.
3 Set that DataSetField property of the dataset created in step 2 to the persistent dataset field you created in step 1.
If the nested dataset field for the current record has a value, the detail dataset component will contain records with
the nested data; otherwise, the detail dataset will be empty.
Before inserting records into a nested dataset, you should be sure to post the corresponding record in the master
table, if it has just been inserted. If the inserted record is not posted, it will be automatically posted before the nested
dataset posts.
1146
If the reference is assigned, the reference dataset will contain a single record with the referenced data. If the
reference is null, the reference dataset will be empty.
You can also use the reference field's Fields property to access the data in a reference field. For example, the
following lines are equivalent and assign data from the reference field CustomerRefCity to an edit box called CityEdit:
CityEdit.Text := CustomerRefCity.Fields[1].AsString;
CityEdit.Text := CustomerRefCity.NestedDataSet.Fields[1].AsString;
When data in a reference field is edited, it is actually the referenced data that is modified.
To assign a reference field, you need to first use a SELECT statement to select the reference from the table, and
then assign. For example:
var
AddressQuery: TQuery;
CustomerAddressRef: TReferenceField;
begin
AddressQuery.SQL.Text := 'SELECT REF(A) FROM AddressTable A WHERE A.City = ''San
Francisco''';
AddressQuery.Open;
CustomerAddressRef.Assign(AddressQuery.Fields[0]);
end;
1147
BDE-based Architecture
When using the BDE, your application uses a variation of the general database architecture described in Database
Architecture. 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.
The relationships between the components in a BDE-based application are illustrated in the following figure:
1148
is recommended for most applications, because the database component gives you greater control over how the
connection is established, including the login process, and lets you create and use transactions.
To associate a BDE-enabled dataset with a database, use the DatabaseName property. DatabaseName is a string
that contains different information, depending on whether you are using an explicit database component and, if not,
the type of database you are using:
If you are using an explicit TDatabase component, DatabaseName is the value of the DatabaseName property
of the database component.
If you want to use an implicit database component and the database has a BDE alias, you can specify a BDE
alias as the value of DatabaseName. A BDE alias represents a database plus configuration information for that
database. The configuration information associated with an alias differs by database type (Oracle, Sybase,
InterBase, Paradox, dBASE, and so on).
If you want to use an implicit database component for a Paradox or dBASE database, you can also use
DatabaseName to simply specify the directory where the database tables are located.
A session provides global management for a group of database connections in an application. When you add BDEenabled datasets to your application, your application automatically contains a session component, named Session.
As you add database and dataset components to the application, they are automatically associated with this default
session. It also controls access to password protected Paradox files, and it specifies directory locations for sharing
Paradox files over a network. You can control database connections and access to Paradox files using the properties,
events, and methods of the session.
You can use the default session to control all database connections in your application. Alternatively, you can add
additional session components at design time or create them dynamically at runtime to control a subset of database
connections in an application. To associate your dataset with an explicitly created session component, use the
SessionName property. If you do not use explicit session components in your application, you do not have to provide
a value for this property. Whether you use the default session or explicitly specify a session using the
SessionName property, you can access the session associated with a dataset by reading the DBSession property.
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.
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.
DBLocale is a handle to the BDE language driver for the dataset. The locale controls the sort order and character
set used for string data.
These properties are automatically assigned to a dataset when it is connected to a database server through the BDE.
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.
Because TTable is a BDE-enabled dataset, it must be associated with a database and a session. 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:
Specifying the Table Type for Local Tables
Controlling Read/Write Access to Local Tables
Specifying a dBASE Index File
Renaming Local Tables
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.
Table Type
Paradox
.DBF
dBASE
.TXT
ASCII text
If your local Paradox, dBASE, and ASCII text tables use the file extensions as described in the previous table, then
you can leave TableType set to ttDefault. Otherwise, your application must set TableType to indicate the correct
table type. The following table indicates the values you can assign to TableType:
1151
TableType values
Value
Table Type
ttDefault
ttParadox Paradox
ttDBase
dBASE
ttFoxPro
FoxPro
ttASCII
Note: You can attempt to set Exclusive on SQL tables, but some servers do not support exclusive table-level locking.
Others may grant an exclusive lock, but permit other applications to read data from the table. For more
information about exclusive locking of database tables on your server, see your server documentation.
1152
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 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;
Renaming a Table
To rename a Paradox or dBASE table at runtime, call the table's RenameTable method. For example, the following
statement renames the Customer table to CustInfo:
Customer.RenameTable('CustInfo');
Meaning
batAppend
Append all records from the source table to the end of this table.
batAppendUpdate Append all records from the source table to the end of this table and update existing records in this table
with matching records from the source table.
batCopy
Copy all records from the source table into this table.
batDelete
Delete all records in this table that also appear in the source table.
batUpdate
Update existing records in this table with matching records from the source table.
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);
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.
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.) 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.
The following topics discuss features that are unique to TQuery components (as opposed to other query-type
datasets):
Creating Heterogeneous Queries.
Obtaining an Editable Result Set
1154
SQL explorer.
2 Leave the DatabaseName property of the TQuery blank; the names of the databases used will be specified in
BDE alias for the table's database, enclosed in colons. This whole reference is then enclosed in quotation marks.
4 Set any parameters for the query in the Params property.
5 Call Prepare to prepare the query for execution prior to executing it for the first time.
6 Call Open or ExecSQL depending on the type of query you are executing.
For example, suppose you define an alias called Oracle1 for an Oracle database that has a CUSTOMER table, and
Sybase1 for a Sybase database that has an ORDERS table. A simple query against these two tables would be:
SELECT Customer.CustNo, Orders.OrderNo
FROM ":Oracle1:CUSTOMER"
JOIN ":Sybase1:ORDERS"
ON (Customer.CustNo = Orders.CustNo)
WHERE (Customer.CustNo = 1503)
As an alternative to using a BDE alias to specify the database in a heterogeneous query, you can use a
TDatabase component. Configure the TDatabase as normal to point to the database, set the
TDatabase.DatabaseName to an arbitrary but unique value, and then use that value in the SQL statement instead
of a BDE alias name.
Subqueries
ORDER BY clauses not based on an index
Queries against a remote database server are parsed by the server. If the RequestLive property is set to True,
the SQL statement must abide by Local SQL standards in addition to any server-imposed restrictions because
the BDE needs to use it for conveying data changes to the table. A live result set for a query against a single
table or view is returned if the query does not contain any of the following:
A DISTINCT clause in the SELECT statement
Aggregate functions, with or without GROUP BY or HAVING clauses
References to more than one base table or updatable views (joins)
Subqueries that reference the table in the FROM clause or other tables
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.
Because TStoredProc is a BDE-enabled dataset, it must be associated with a database and a session. Once the
dataset is associated with a database and session, you can bind it to a particular stored procedure by setting the
StoredProcName property.
1156
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.
1158
property. If you specify DriverName, any value already assigned to AliasName is cleared to avoid potential conflicts
between the driver name you specify and the driver name that is part of the BDE alias identified in AliasName.
DatabaseName lets you provide your own name for a database connection. The name you supply is in addition
to AliasName or DriverName, and is local to your application. DatabaseName can be a BDE alias, or, for Paradox
and dBASE files, a fully-qualified path name. Like AliasName, DatabaseName appears in subsequent drop-down
lists for dataset components to let you link them to database components.
At design time, to specify a BDE alias, assign a BDE driver, or create a local BDE alias, double-click a database
component to invoke the Database Properties editor.
You can enter a DatabaseName in the Name edit box in the properties editor. You can enter an existing BDE alias
name in the Alias name combo box for the Alias property, or you can choose from existing aliases in the drop-down
list. The Driver name combo box enables you to enter the name of an existing BDE driver for the DriverName
property, or you can choose from existing driver names in the drop-down list.
Note: The Database Properties editor also lets you view and set BDE connection parameters, and set the states
of the LoginPrompt and KeepConnection properties. For information on connection parameters, see Setting
BDE Alias Parameters. For information on LoginPrompt, see Controlling Server Login. For information on
KeepConnection see Opening a Connection Using TDatabase.
1159
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.
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.
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.
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.
1161
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.
1162
OpenDatabase actives the session if it is not already active, and then checks if the specified database name matches
the DatabaseName property of any database components for the session. If the name does not match an existing
database component, OpenDatabase creates a temporary database component using the specified name. Finally,
OpenDatabase calls the Open method of the database component to connect to the server. Each call to
OpenDatabase increments a reference count for the database by 1. As long as this reference count remains greater
than 0, the database is open.
If the specified database name is associated with a temporary (implicit) database component, and the session's
KeepConnections property is False, the database component is freed, effectively closing the connection.
Note: If KeepConnections is False temporary database components are closed and freed automatically when the
last dataset associated with the database component is closed. An application can always call
CloseDatabase prior to that time to force closure. To free temporary database components when
KeepConnections is True, call the database component's Close method, and then call the session's
DropConnections method.
Note: Calling CloseDatabase for a persistent database component does not actually close the connection. To close
the connection, call the database component's Close method directly.
There are two ways to close all database connections within the session:
Set the Active property for the session to False.
Call the Close method for the session.
When you set Active to False, Delphi automatically calls the Close method. Close disconnects from all active
databases by freeing temporary database components and calling each persistent database component's Close
method. Finally, Close sets the session's BDE handle to nil.
these connections and free all inactive temporary database components for a session by calling the
DropConnections method. For example, the following code frees all inactive, temporary database components for
the default session:
Session.DropConnections;
Temporary database components for which one or more datasets are active are not dropped or freed by this call.
To free these components, call Close.
1164
Note: Use of the InputBox function, above, is for demonstration purposes. In a real-world application, use password
entry facilities that mask the password as it is entered, such as the PasswordDialog function or a custom form.
The Add button of the PasswordDialog function dialog has the same effect as the AddPassword method.
if PasswordDialog(Session) then
Table1.Open
else
ShowMessage('No password given, could not open table!');
end;
1165
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 passwordprotected 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 }
1166
ShowMessage('Error!' + CRLF +
E.Message + CRLF +
'Terminating application...');
Application.Terminate;
end;
end;
end;
Note: NetFileDir can only be changed when an application does not have any open Paradox files. If you change
NetFileDir at runtime, verify that it points to a valid network directory that is shared by your network users.
PrivateDir specifies the directory for storing temporary table processing files, such as those generated by the BDE
to handle local SQL statements. If no value is specified for the PrivateDir property, the BDE automatically uses the
current directory at the time it is initialized. If your application runs directly from a network file server, you can improve
application performance at runtime by setting PrivateDir to a user's local hard drive before opening the database.
Note: Do not set PrivateDir at design time and then open the session in the IDE. Doing so generates a Directory
is busy error when running your application from the IDE.
The following code changes the setting of the default session's PrivateDir property to a user's C:\TEMP directory:
Session.PrivateDir := "C:\TEMP";
Warning: Do not set PrivateDir to a root directory on a drive. Always specify a subdirectory.
var
AliasParams: TStringList;
begin
AliasParams := TStringList.Create;
try
with AliasParams do begin
Add('OPEN MODE=READ');
Add('USER NAME=TOMSTOPPARD');
Add('SERVER NAME=ANIMALS:/CATS/PEDIGREE.GDB');
end;
Session.AddAlias('CATS', 'INTRBASE', AliasParams);
...
finally
AliasParams.Free;
end;
end;
AddStandardAlias creates a new BDE alias for Paradox, dBASE, or ASCII tables. AddStandardAlias takes three
string parameters: the name for the alias, the fully-qualified path to the Paradox or dBASE table to access, and the
name of the default driver to use when attempting to open a table that does not have an extension. For example,
the following statement uses AddStandardAlias to create a new alias for accessing a Paradox table:
AddStandardAlias('MYDBDEMOS', 'C:\TESTING\DEMOS\', 'Paradox');
When you add an alias to a session, the BDE stores a copy of the alias in memory, where it is only available to this
session and any other sessions with cfmPersistent included in the ConfigMode property. ConfigMode is a set that
describes which types of aliases can be used by the databases in the session. The default setting is cmAll, which
translates into the set [cfmVirtual, cfmPersistent, cfmSession]. If ConfigMode is cmAll, a session can see all aliases
created within the session (cfmSession), all aliases in the BDE configuration file on a user's system
(cfmPersistent), and all aliases that the BDE maintains in memory (cfmVirtual). You can change ConfigMode to
restrict what BDE aliases the databases in a session can use. For example, setting ConfigMode to cfmSession
restricts a session's view of aliases to those created within the session. All other aliases in the BDE configuration
file and in memory are not available.
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.
1168
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 Using transactions with the BDE.. For more
information about BDE aliases see the BDE online help, BDE32.HLP.
Purpose
GetAliasDriverName
GetAliasNames
GetAliasParams
GetConfigParams
GetDatabaseNames
Retrieves the list of BDE aliases and the names of any TDatabase components currently in use.
GetDriverNames
GetDriverParams
GetStoredProcNames Retrieves the names of all stored procedures for a specified database.
GetTableNames
Retrieves the names of all tables matching a specified pattern for a specified database.
GetFieldNames
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);
...
1169
finally
List.Free;
end;
end;
components for the session, sets the KeepConnections property to True, and adds the session to the list of
sessions maintained by the application's session list component.
3 Set the SessionName property for the new session to a unique name. This property is used to associate database
components with the session. For more information about the SessionName property, see Naming a session.
4 Activate the session and optionally adjust its properties.
You can also create and open sessions using the OpenSession method of TSessionList. Using OpenSession is
safer than calling Create, because OpenSession only creates a session if it does not already exist. For information
about OpenSession, see Managing multiple sessions..
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;
For more information about using Sessions, see Managing Multiple Sessions..
1170
This statement generates a unique name for a new session by retrieving the current number of sessions, and adding
one to that value. Note that if you dynamically create and destroy sessions at runtime, this example code will not
work as expected. Nevertheless, this example illustrates how to use the properties and methods of Sessions to
manage multiple sessions.
Sessions is a variable of type TSessionList that is automatically instantiated for BDE-based database applications.
You use the properties and methods of Sessions to keep track of multiple sessions in a multi-threaded database
application. The following table summarizes the properties and methods of the TSessionList component:
TSessionList properties and methods
Property or Method Purpose
Count
Returns the number of sessions, both active and inactive, in the session list.
FindSession
Searches for a session with a specified name and returns a pointer to it, or nil if there is no session with
the specified name. If passed a blank session name, FindSession returns a pointer to the default session,
Session.
GetSessionNames
Populates a string list with the names of all currently instantiated session components. This procedure
always adds at least one string, "Default" for the default session.
List
Returns the session component for a specified session name. If there is no session with the specified
name, an exception is raised.
OpenSession
Creates and activates a new session or reactivates an existing session for a specified session name.
Sessions
As an example of using Sessions properties and methods in a multi-threaded application, consider what happens
when you want to open a database connection. To determine if a connection already exists, use the Sessions
property to walk through each session in the sessions list, starting with the default session. For each session
component, examine its Databases property to see if the database in question is open. If you discover that another
thread is already using the desired database, examine the next session in the list.
If an existing thread is not using the database, then you can open the connection within that session.
If, on the other hand, all existing threads are using the database, you must open a new session in which to open
another database connection.
If you are replicating a data module that contains a session in a multi-threaded application, where each thread
contains its own copy of the data module, you can use the AutoSessionName property to make sure that all datasets
in the data module use the correct session. Setting AutoSessionName to True causes the session to generate its
own unique name dynamically when it is created at runtime. It then assigns this name to every dataset in the data
1171
module, overriding any explicitly set session names. This ensures that each thread has its own session, and each
dataset uses the session in its own data module.
1172
Note: When SQLPASSTHRU MODE is NOT SHARED, you must use separate database components for datasets
that pass SQL transaction statements to the server and datasets that do not.
1173
Purpose
CachedUpdates
Not needed for client datasets, which always Determines whether cached
cache updates.
updates are in effect for the
dataset.
UpdateObject
UpdatesPending
ChangeCount
UpdateRecordTypes
StatusFilter
UpdateStatus
UpdateStatus
OnUpdateError
OnReconcileError
OnUpdateRecord
BeforeUpdateRecord
ApplyUpdates (database)
ApplyUpdates
CancelUpdates
CancelUpdates
CommitUpdates
Reconcile
FetchAll
RevertRecord
RevertRecord
For an overview of the cached update process, see Overview of using cached updates.
The following topics describe in more detail on how to use the BDE to cache updates:
Enabling BDE-based Cached Updates.
Applying BDE-based Cached Updates.
Using Update Objects to Update a Dataset.
Note: Even if you are using a client dataset to cache updates, you may want to read the section about update
objects. You can use update objects in the BeforeUpdateRecord event handler of TBDEClientDataSet or
TDataSetProvider to apply updates from stored procedures or multi-table queries.
is cached in local memory. Users view and edit this local copy of data. Changes, insertions, and deletions are also
cached in memory. They accumulate in memory until the application applies those changes to the database server.
If changed records are successfully applied to the database, the record of those changes are freed in the cache.
The dataset caches all updates until you set CachedUpdates to False. Applying cached updates does not disable
further cached updates; it only writes the current set of changes to the database and clears them from memory.
Canceling the updates by calling CancelUpdates removes all the changes currently in the cache, but does not stop
the dataset from caching any subsequent changes.
Note: If you disable cached updates by setting CachedUpdates to False, any pending changes that you have not
yet applied are discarded without notification. To prevent losing changes, test the UpdatesPending property
before disabling cached updates.
once for each record written to the database. If an error occurs when a record is applied to the database, the
OnUpdateError event is triggered if you provide one.
3 The transaction is committed if writes are successful or rolled back if they are not,
Write Status
Transaction
Successful
Unsuccessful Database changes are rolled back, ending the database transaction.
Cached updates are not committed, remaining intact in the internal cache.
For information about creating and using an OnUpdateRecord event handler, see Creating an OnUpdateRecord
Event Handler. For information about handling update errors that occur when applying cached updates, see Handling
Cached Update errors.
Note: Applying cached updates is particularly tricky when you are working with multiple datasets linked in a master/
detail relationship because the order in which you apply updates to each dataset is significant. Usually, you
must update master tables before detail tables, except when handling deleted records, where this order must
be reversed. Because of this difficulty, it is strongly recommended that you use client datasets when caching
updates in a master/detail form. Client datasets automatically handle all ordering issues with master/detail
relationships.
There are two ways to apply BDE-based updates:
You can apply updates using a database component by calling its ApplyUpdates method. This method is the
simplest approach, because the database handles all details of managing a transaction for the update process
and of clearing the dataset's cache when updating is complete.
You can apply updates for a single dataset by calling the dataset's ApplyUpdates and CommitUpdates methods.
When applying updates at the dataset level you must explicitly code the transaction that wraps the update
process as well as explicitly call CommitUpdates to commit updates from the cache.
1175
Warning: 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 for details.
The above sequence writes cached updates to the database in the context of an automatically-generated transaction.
If successful, it commits the transaction and then commits the cached updates. If unsuccessful, it rolls back the
transaction and leaves the update cache unchanged. In this latter case, you should handle cached update errors
through a dataset's OnUpdateError event. For more information about handling update errors, see Handling cached
update errors.
The main advantage to calling a database component's ApplyUpdates method is that you can update any number
of dataset components that are associated with the database. The parameter for the ApplyUpdates method for a
database is an array of TDBDataSet. For example, the following code applies updates for two queries:
if not (Database1.IsSQLBased) and not (Database1.TransIsolation = tiDirtyRead) then
Database1.TransIsolation := tiDirtyRead;
Database1.ApplyUpdates([CustomerQuery, OrdersQuery]);
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 }
1176
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.
1177
DataSet references the dataset to which updates are applied. You can use this dataset to access new and old values
during error handling. The original values for fields in each record are stored in a read-only TField property called
OldValue. Changed values are stored in the analogous TField property NewValue. These values provide the only
way to inspect and change update values in the event handler.
Warning: Do not call any dataset methods that change the current record (such as Next and Prior). Doing so causes
the event handler to enter an endless loop.
The E parameter is usually of type EDBEngineError. From this exception type, you can extract an error message
that you can display to users in your error handler. For example, the following code could be used to display the
error message in the caption of a dialog box:
ErrorLabel.Caption := E.Message;
1178
This parameter is also useful for determining the actual cause of the update error. You can extract specific error
codes from EDBEngineError, and take appropriate action based on it.
The UpdateKind parameter describes the type of update that generated the error. Unless your error handler takes
special actions based on the type of update being carried out, your code probably will not make use of this parameter.
The following table lists possible values for UpdateKind:
UpdateKind values
Value
Meaning
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.
1179
To update a dataset
1 If you are using a client dataset, use an external provider component with TClientDataSet rather than
TBDEClientDataSet. This is so you can set the UpdateObject property of the BDE-enabled source dataset (step
3).
2 Add a TUpdateSQL component to the same data module as the BDE-enabled dataset.
3 Set the BDE-enabled dataset component's UpdateObject property to the TUpdateSQL component in the data
module.
4 Specify the SQL statements needed to perform updates using the update object's ModifySQL, InsertSQL, and
DeleteSQL properties. You can use the Update SQL editor to help you compose these statements.
5 Close the dataset.
6 Set the dataset component's CachedUpdates property to True or link the dataset to the client dataset using a
dataset provider.
7 Reopen the dataset.
Note: Sometimes, you need to use multiple update objects. For example, when updating a multi-table join or a
stored procedure that represents data from multiple datasets, 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.
1180
At design time, you can use the Update SQL editor to help you compose the SQL statements that apply updates.
Update objects provide automatic parameter binding for parameters that reference the dataset's original and updated
field values. Typically, therefore, you insert parameters with specially formatted names when you compose the SQL
statements.
UpdateObject property. This step ensures that the Update SQL editor you invoke in the next step can determine
suitable default values to use for SQL generation options.
2 Right-click the update object and select UpdateSQL Editor from the context menu. This displays the Update SQL
editor. The editor creates SQL statements for the update object's ModifySQL, InsertSQL, and DeleteSQL
properties based on the underlying data set and on the values you supply to it.
The Update SQL editor has two pages. The Options page is visible when you first invoke the editor. Use the Table
Name combo box to select the table to update. When you specify a table name, the Key Fields and Update Fields
list boxes are populated with available columns.
The Update Fields list box indicates which columns should be updated. When you first specify a table, all columns
in the Update Fields list box are selected for inclusion. You can multi-select fields as desired.
The Key Fields list box is used to specify the columns to use as keys during the update. For Paradox, dBASE, and
FoxPro the columns you specify here must correspond to an existing index, but this is not a requirement for remote
SQL databases. Instead of setting Key Fields you can click the Primary Keys button to choose key fields for the
update based on the table's primary index. Click Dataset Defaults to return the selection lists to the original state:
all fields selected as keys and all selected for update.
Check the Quote Field Names check box if your server requires quotation marks around field names.
After you specify a table, select key columns, and select update columns, click Generate SQL to generate the
preliminary SQL statements to associate with the update component's ModifySQL, InsertSQL, and DeleteSQL
properties. In most cases you will want or need to fine tune the automatically generated SQL statements.
To view and modify the generated SQL statements, select the SQL page. If you have generated SQL statements,
then when you select this page, the statement for the ModifySQL property is already displayed in the SQL Text
memo box. You can edit the statement in the box as desired.
Warning: Keep in mind that generated SQL statements are starting points for creating update statements. You may
need to modify these statements to make them execute correctly. For example, when working with data
that contains NULL values, you need to modify the WHERE clause to read
WHERE field IS NULL
rather then using the generated field variable. Test each of the statements directly yourself before accepting them.
Use the Statement Type radio buttons to switch among generated SQL statements and edit them as desired.
To accept the statements and associate them with the update component's SQL properties, click OK.
1181
New field values are typically used in the InsertSQL and ModifySQL statements. In an update for a modified record,
the new field value from the update cache is used by the UPDATE statement to replace the old field value in the
base table updated.
In the case of a deleted record, there are no new values, so the DeleteSQL property uses the ":OLD_FieldName"
syntax. Old field values are also normally used in the WHERE clause of the SQL statement for a modified or deletion
update to determine which record to update or delete.
In the WHERE clause of an UPDATE or DELETE update SQL statement, supply at least the minimal number of
parameters to uniquely identify the record in the base table that is updated with the cached data. For instance, in a
list of customers, using just a customer's last name may not be sufficient to uniquely identify the correct record in
the base table; there may be a number of records with "Smith" as the last name. But by using parameters for last
name, first name, and phone number could be a distinctive enough combination. Even better would be a unique field
value like a customer number.
Note: If you create SQL statements that contain parameters that do not refer the edited or original field values, the
update object does not know how to bind their values. You can, however, do this manually, using the update
object's Query property.
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
1182
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.
1183
The update object uses this dataset to obtain original and updated field values for parameter substitution and, if it
is a BDE-enabled dataset, to identify the session and database to use when applying the updates. So that parameter
substitution will work correctly, the update object's DataSet property must be the dataset that contains the updated
field values. When using the BDE-enabled dataset to cache updates, this is the BDE-enabled dataset itself. When
using a client dataset, this is a client dataset that is provided as a parameter to the BeforeUpdateRecord event
handler.
When the update object has not been assigned to the dataset's UpdateObject property, its SQL statements are not
automatically executed when you call ApplyUpdates. To update records, 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). In the event handler, the minimum actions you need to take are
If you are using a client dataset to cache updates, you must be sure that the updates object's DatabaseName
and SessionName properties are set to the DatabaseName and SessionName properties of the source dataset.
The event handler must call the update object's ExecSQL or Apply method. This invokes the update object for
each record that requires updating. For more information about executing update statements, see Executing
the SQL statements.
Set the event handler's UpdateAction parameter to uaApplied (OnUpdateRecord) or the Applied parameter
to True (BeforeUpdateRecord).
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.
1184
Call the Apply method to apply the update for the current record in the update cache. The Apply method is most
often called from within a handler for the dataset's OnUpdateRecord event or from a provider's
BeforeUpdateRecord event handler.
Warning: If you use the dataset's UpdateObject property to associate dataset and update object, Apply is called
automatically. In that case, do not call Apply in an OnUpdateRecord event handler as this will result in a
second attempt to apply the current 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;
1185
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.
1186
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.
The following topics describe how to work with a TBatchMove component:
Creating a Batch Move Component
Specifying a Batch Move Mode
Mapping Data Types
Executing a Batch Move
Handling Batch Move Errors
update records. You can select tables from the drop-down list of available dataset components.
5 Set the Destination property to the dataset to create, append to, or update. You can select a destination table
batUpdate, batAppendUpdate, batCopy, and batDelete. For information about these modes, see Specifying a
batch move mode.
7 Optionally set the Transliterate property. If Transliterate is True (the default), character data is translated from
the Source dataset's character set to the Destination dataset's character set as necessary.
8 Optionally set column mappings using the Mappings property. You need not set this property if you want batch
move to match columns based on their position in the source and destination tables. For more information about
mapping columns, see Mapping data types.
9 Optionally specify the ChangedTableName, KeyViolTableName, and ProblemTableName properties. Batch
move stores problem records it encounters during the batch operation in the table specified by
ProblemTableName. If you are updating a Paradox table through a batch move, key violations can be reported
in the table you specify in KeyViolTableName. ChangedTableName lists all records that changed in the
destination table as a result of the batch move operation. If you do not specify these properties, these error tables
are not created or used. For more information about handling batch move errors, see Handling batch move errors.
1187
Purpose
batAppend
batUpdate
Update records in the destination table with matching records from the source table. Updating is based
on the current index of the destination table.
batAppendUpdate If a matching record exists in the destination table, update it. Otherwise, append records to the destination
table.
batCopy
Create the destination table based on the structure of the source table. If the destination table already
exists, it is dropped and recreated.
batDelete
Delete records in the destination table that match records in the source table.
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 as appropriate.
1188
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.
To map a column named SourceColName in the source table to a column named DestColName in the destination
table, the syntax is as follows:
DestColName = SourceColName
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.
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.
You can also execute a batch move at design time by right clicking the mouse on a batch move component and
choosing Execute from the context menu.
The MovedCount property keeps track of the number of records that are moved when a batch move executes.
The RecordCount property specifies the maximum number of records to move. If RecordCount is zero, all records
are moved, beginning with the first record in the source dataset. If RecordCount is a positive number, a maximum
1189
of RecordCount records are moved, beginning with the current record in the source dataset. If RecordCount is greater
than the number of records between the current record in the source dataset and its last record, the batch move
terminates when the end of the source dataset is reached. You can examine MoveCount to determine how many
records were actually transferred.
1190
Note: A programming interface to the Data Dictionary is available in the drintf unit (located in the lib directory). This
interface supplies the following methods:
Data Dictionary interface
Routine
Use
DictionaryActive
DictionaryDeactivate
IsNullID
FindDatabaseID
FindTableID
FindFieldID
FindAttrID
GetAttrName
GetAttrNames
GetAttrID
NewAttr
UpdateAttr
CreateField
UpdateField
AssociateAttr
UnassociateAttr
GetControlClass
QualifyTableName
UpdateConstraints
UpdateDataset
1192
1193
Use
TADOConnection A database connection component that establishes a connection with an ADO data store; multiple ADO
dataset and command components can share this connection to execute commands, retrieve data, and
operate on metadata.
TADODataSet
The primary dataset for retrieving and operating on data; TADODataSet can retrieve data from a single or
multiple tables; can connect directly to a data store or use a TADOConnection component.
TADOTable
A table-type dataset for retrieving and operating on a recordset produced by a single database table;
TADOTable can connect directly to a data store or use a TADOConnection component.
TADOQuery
A query-type dataset for retrieving and operating on a recordset produced by a valid SQL statement;
TADOQuery can also execute data definition language (DDL) SQL statements. It can connect directly to
a data store or use a TADOConnection component
TADOStoredProc A stored procedure-type dataset for executing stored procedures; TADOStoredProc executes stored
procedures that may or may not retrieve data. It can connect directly to a data store or use a
TADOConnection component.
TADOCommand
A simple component for executing commands (SQL statements that do not return result sets);
TADOCommand can be used with a supporting dataset component, or retrieve a dataset from a table; It
can connect directly to a data store or use a TADOConnection component.
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:
Connection parameters
Parameter
Description
Provider
Data Source
File name
Remote Provider The name of an ADO provider that resides on a remote machine.
Remote Server
Note: The connection parameters in ConnectionString do not need to include the Provider or Remote Provider
parameter if you specify an ADO provider using the Provider property. Similarly, you do not need to specify
the Data Source parameter if you use the DefaultDatabase property.
In addition, to the parameters listed above, ConnectionString can include any connection parameters peculiar to the
specific ADO provider you are using. These additional connection parameters can include user ID and password if
you want to hardcode the login information.
At design-time, you can use the Connection String Editor to build a connection string by selecting connection
elements (like the provider and server) from lists. Click the ellipsis button for the ConnectionString property in the
Object Inspector to launch the Connection String Editor, which is an ActiveX property editor supplied by ADO.
Once you have specified the ConnectionString property (and, optionally, the Provider property), you can use the
ADO connection component to connect to or disconnect from the ADO data store, although you may first want to
use other properties to fine-tune the connection. When connecting to or disconnecting from the data store,
TADOConnection lets you respond to a few additional events beyond those common to all database connection
components..
1195
Note: If you do not explicitly activate the connection by setting the connection component's Connected property to
True, it automatically establishes the connection when the first dataset component is opened or the first time
you use an ADO command component to execute a command.
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.
The following topics describe the properties you can use to fine-tune the connection:
Forcing asynchronous connections
Controlling time-outs
Indicating the types of operations the connection supports
Specifying whether the connection automatically initiates transactions
1196
Controlling Timeouts
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;
Meaning
cmUnknown
Permissions are not yet set for the connection or cannot be determined.
cmRead
cmWrite
cmReadWrite
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.
Attributes is a set that can contain one, both, or neither of the constants xaCommitRetaining and
xaAbortRetaining. When Attributes contains xaCommitRetaining, the connection uses retaining commits. When
Attributes contains xaAbortRetaining, it uses retaining aborts.
Check whether either retaining commits or retaining aborts is enabled using the in operator. Enable retaining commits
or aborts by adding the appropriate value to the attributes property; disable them by subtracting the value. The
example routines below respectively enable and disable retaining commits in an ADO connection component.
procedure TForm1.RetainingCommitsOnButtonClick(Sender: TObject);
begin
with ADOConnection1 do begin
Close;
if not (xaCommitRetaining in Attributes) then
Attributes := (Attributes + [xaCommitRetaining])
Open;
end;
end;
procedure TForm1.RetainingCommitsOffButtonClick(Sender: TObject);
begin
with ADOConnection1 do begin
Close;
if (xaCommitRetaining in Attributes) then
Attributes := (Attributes - [xaCommitRetaining]);
Open;
end;
end;
1198
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.
1199
1200
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.
1201
FilterOnBookmarks([BM1, BM2]);
end;
end;
Note that the example above also adds the bookmarks to a list object named BMList. This is necessary so that the
application can later free the bookmarks when they are no longer needed.
Meaning
eoAsyncExecute
eoAsyncFetch
The dataset first fetches the number of records specified by the CacheSize property
synchronously, then fetches any remaining rows asynchronously.
eoAsyncFetchNonBlocking Asynchronous data fetches or command execution do not block the current thread of execution.
eoExecuteNoRecords
A command or stored procedure that does not return data. If any rows are retrieved, they are
discarded and not returned.
TClientDataSet
Description
LockType
CursorType
Not used: client datasets always work with an inmemory snapshot of data
RecordStatus UpdateStatus
FilterGroup
StatusFilter
UpdateBatch ApplyUpdates
1202
CancelBatch
CancelUpdates
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.
1203
Note: For the FilterGroup property to have an effect, the ADO dataset component's Filtered property must be set
to True.
1204
ADODataSet1.CancelBatch;
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, TADODataSet lets you sort using temporary indexes by setting the Sort property.
Indexed-based searches performed using the Seek method use the current index.
1205
Emptying the dataset. The DeleteRecordsDeleteRecords 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.
Setting up master/detail relationships using parameters.
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.
Fetching multiple result sets.
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. As an alternative, you can use an RDS DataSpace object to connect the TADODataSet to an ADObased application server. To use an RDS DataSpace object, set the RDSConnection property to a TRDSConnection
object.
If no specific type is specified, the server is left to decide as best it can based on the command in CommandText.
CommandText can contain the text of an SQL query that includes parameters or the name of a stored procedure
that uses parameters. You must then supply parameter values, which are bound to the parameters before executing
the command. See Handling command parameters for details.
Other versions of Execute let you provide parameter values using a Variant array, and to obtain the number of records
affected by the command.
For information on executing commands that return a result set, see Retrieving result sets with commands.
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;
1207
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.
As soon as the result set is assigned to the ADO dataset's Recordset property, the dataset is automatically activated
and the data is available.
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
1208
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;
1209
Typically, all unidirectional datasets in an application share the same connection component, unless you are working
with data from multiple database servers. However, you may want to use a separate connection for each dataset if
the server does not support multiple statements per connection. Check whether the database server requires a
separate connection for each dataset by reading the MaxStmtsPerConn property. By default, TSQLConnection
generates connections as needed when the server limits the number of statements that can be executed over a
connection. If you want to keep stricter track of the connections you are using, set the AutoClone property to False.
Before you assign the SQLConnection property, you will need to set up the TSQLConnection component so that it
identifies the database server and any required connection parameters (including which database to use on the
server, the host name of the machine running the server, the username, password, and so on).
1211
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.
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.
1213
The following topics describe how you can specify a set of records for each type of source:
Representing the results of a query
Representing the records in a table
Representing the results of a stored procedure
Note: You can also populate the unidirectional dataset with metadata about what is available on the server. For
information on how to do this, see Fetching metadata into a unidirectional dataset.
1214
TSQLDataSet generates the following query, which lists all the records in the Employee table, sorted by HireDate
and, within HireDate, by Salary:
select * from Employee order by HireDate, Salary
When using TSQLStoredProc, you need only specify the name of the stored procedure as the value of the
StoredProcName property.
SQLStoredProc1.StoredProcName := 'MyStoredProcName';
After you have identified a stored procedure, your application may need to enter values for any input parameters of
the stored procedure or retrieve the values of output parameters after you execute the stored procedure. See Working
with stored procedure parameters for information about working with stored procedure parameters.
1215
CustQuery.Open;
Use the Active property or the Open method with any unidirectional dataset that obtains records from the server. It
does not matter whether these records come from a SELECT query (including automatically-generated queries when
the CommandType is ctTable) or a stored procedure.
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 change a parameter value or the SortFieldNames property).
NextRecordSet returns a newly created TCustomSQLDataSet component that provides access to the next set of
records. That is, the first time you call NextRecordSet, it returns a dataset for the second set of records. Calling
NextRecordSet returns a third dataset, and so on, until there are no more sets of records. When there are no
additional datasets, NextRecordSet returns nil.
1216
Note: If the command does not return any records, you do not need to use a unidirectional dataset at all, because
there is no need for the dataset methods that provide access to a set of records. The SQL connection
component that connects to the database server can be used directly to execute a command on the server.
See Sending commands to the server for details.
The following topics discuss how to create and execute a command that does not return any records:
Specifying the command to execute
Executing the command
In addition, the following topic discusses some of the SQL commands that do not return datasets:
Creating and modifying server metadata
If you are executing the query or stored procedure multiple times, it is a good idea to set the Prepared property to True.
1217
The following code uses a TSQLDataSet to create this stored procedure. Note the use of the ParamCheck property
to prevent the dataset from confusing the parameters in the stored procedure definition (:EMP_NO and :PROJ_ID)
with a parameter of the query that creates the stored procedure.
with SQLDataSet1 do
begin
ParamCheck := False;
CommandType := ctQuery;
CommandText := 'CREATE PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT) ' +
'RETURNS (PROJ_ID CHAR(5)) AS ' +
'BEGIN ' +
'FOR SELECT PROJ_ID FROM EMPLOYEE_PROJECT ' +
'WHERE EMP_NO = :EMP_NO ' +
'INTO :PROJ_ID ' +
'DO SUSPEND; ' +
END';
ExecSQL;
end;
1218
1219
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.
RECNO
ftInteger
CATALOG_NAME ftString
The name of the catalog (database) that contains the table. This is the same as the
Database parameter on an SQL connection component.
SCHEMA_NAME
ftString
The name of the schema that identifies the owner of the table.
TABLE_NAME
ftString
The name of the table. This field determines the sort order of the dataset.
TABLE_TYPE
ftInteger
Identifies the type of table. It is a sum of one or more of the following values: 1: Table 2: View
4: System table 8: Synonym 16: Temporary table 32: Local table.
RECNO
ftInteger
CATALOG_NAME ftString
The name of the catalog (database) that contains the stored procedure. This is the same as
the Database parameter on an SQL connection component.
SCHEMA_NAME
ftString
The name of the schema that identifies the owner of the stored procedure.
PROC_NAME
ftString
The name of the stored procedure. This field determines the sort order of the dataset.
PROC_TYPE
ftInteger
Identifies the type of stored procedure. It is a sum of one or more of the following values: 1:
Procedure 2: Function 4: Package 8: System procedure
IN_PARAMS
OUT_PARAMS
RECNO
ftInteger
CATALOG_NAME
ftString
The name of the catalog (database) that contains the table whose fields you listing. This
is the same as the Database parameter on an SQL connection component.
SCHEMA_NAME
ftString
The name of the schema that identifies the owner of the field.
TABLE_NAME
ftString
COLUMN_NAME
ftString
The name of the field. This value determines the sort order of the dataset.
COLUMN_POSITION
COLUMN_TYPE
ftInteger
Identifies the type of value in the field. It is a sum of one or more of the following: 1: Row
ID 2: Row Version 4: Auto increment field 8: Field with a default value
COLUMN_DATATYPE ftSmallint The datatype of the column. This is one of the logical field type constants defined in
sqllinks.pas.
COLUMN_TYPENAME ftString
COLUMN_SUBTYPE
ftSmallint A subtype for the column's datatype. This is one of the logical subtype constants defined
in sqllinks.pas.
COLUMN_PRECISION ftInteger
The size of the field type (number of characters in a string, bytes in a bytes field, significant
digits in a BCD value, members of an ADT field, and so on).
COLUMN_SCALE
ftSmallint The number of digits to the right of the decimal on BCD values, or descendants on ADT
and array fields.
COLUMN_LENGTH
ftInteger
COLUMN_NULLABLE
ftSmallint A Boolean that indicates whether the field can be left blank (0 means the field requires a
value).
1221
RECNO
ftInteger
CATALOG_NAME
ftString
The name of the catalog (database) that contains the index. This is the same as the
Database parameter on an SQL connection component.
SCHEMA_NAME
ftString
The name of the schema that identifies the owner of the index.
TABLE_NAME
ftString
INDEX_NAME
ftString
The name of the index. This field determines the sort order of the dataset.
PKEY_NAME
ftString
COLUMN_NAME
ftString
ftSmallint Identifies the type of index. It is a sum of one or more of the following values: 1: Non-unique
2: Unique 4: Primary key
SORT_ORDER
ftString
FILTER
ftString
RECNO
ftInteger
CATALOG_NAME
ftString
The name of the catalog (database) that contains the stored procedure. This is the same
as the Database parameter on an SQL connection component.
SCHEMA_NAME
ftString
The name of the schema that identifies the owner of the stored procedure.
PROC_NAME
ftString
PARAM_NAME
ftString
The name of the parameter. This field determines the sort order of the dataset.
PARAM_TYPE
ftSmallint Identifies the type of parameter. This is the same as a TParam object's ParamType
property.
PARAM_DATATYPE ftSmallint The datatype of the parameter. This is one of the logical field type constants defined in
sqllinks.pas.
PARAM_SUBTYPE
ftSmallint A subtype for the parameter's datatype. This is one of the logical subtype constants defined
in sqllinks.pas.
PARAM_TYPENAME ftString
PARAM_PRECISION ftInteger
The maximum number of digits in floating-point values or bytes (for strings and Bytes
fields).
1222
PARAM_SCALE
ftSmallint The number of digits to the right of the decimal on floating-point values.
PARAM_LENGTH
ftInteger
PARAM_NULLABLE
ftSmallint A Boolean that indicates whether the parameter can be left blank (0 means the parameter
requires a value).
RECNO
ftInteger
CATALOG_NAME ftString
The name of the catalog (database) that contains the package. This is the same as the
Database parameter on an SQL connection component.
SCHEMA_NAME
ftString
The name of the schema that identifies the owner of the package.
OBJECT_NAME
ftString
The name of the package. This field determines the sort order of the dataset.
To use TSQLMonitor
1 Add a TSQLMonitor component to the form or data module containing the TSQLConnection component whose
Meaning
Error messages returned by the server. The error message may include an error code, depending on the
server.
1223
traceSTMT
traceCONNECT
Operations associated with connecting and disconnecting to databases, including allocation of connection
handles and freeing connection handles, if required by server.
Operations on Binary Large Object (BLOB) data, including STORE BLOB, GET BLOB HANDLE, and so on.
traceMISC
traceVENDOR
API function calls to the server. For example, ORLON for Oracle, ISC_ATTACH for InterBase.
traceDATAIN
traceDATAOUT
As SQL commands are sent to the server, the SQL monitor's TraceList property is automatically updated to list all
the SQL commands that are intercepted.
You can save this list to a file by specifying a value for the FileName property and then setting the AutoSave property
to True. AutoSave causes the SQL monitor to save the contents of the TraceList property to a file every time is logs
a new message.
If you do not want the overhead of saving a file every time a message is logged, you can use the OnLogTrace event
handler to only save files after a number of messages have been logged. For example, the following event handler
saves the contents of TraceList every 10th message, clearing the log after saving it so that the list never gets too long:
procedure TForm1.SQLMonitor1LogTrace(Sender: TObject; CBInfo: Pointer);
var
LogFileName: string;
begin
with Sender as TSQLMonitor do
begin
if TraceCount = 10 then
begin
LogFileName := 'c:\log' + IntToStr(Tag) + '.txt';
Tag := Tag + 1; {ensure next log file has a different name }
SaveToFile(LogFileName);
TraceList.Clear; { clear list }
end;
end;
end;
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.
The dbExpress driver calls your callback every time the SQL connection component passes a command to the server
or the server returns an error message.
Warning: Do not call SetTraceCallbackEvent if the TSQLConnection object has an associated TSQLMonitor
component. TSQLMonitor uses the callback mechanism to work, and TSQLConnection can only support
one callback at a time.
1225
Comparisons
=
State = 'CA'
Yes
<>
Yes
>=
Yes
1227
<=
Yes
>
Percentile > 50
Yes
<
Yes
BLANK
IS NULL
Field1 IS NULL
No
IS NOT NULL
No
Logical operators
and
or
Yes
not
Yes
Depends on driver
Field1 - 7 <> 10
Depends on driver
Depends on driver
Depends on driver
Upper
Upper(Field1) = 'ALWAYS'
No
Lower
No
Substring
Substring(DateFld,8) = '1998'
No
No
No
See Trim.
No
See Trim.
Arithmetic operators
String functions
Substring(DateFld,1,3) = 'JAN'
Trim
Trim(Field1 + Field2)
Trim(Field1, '-')
TrimLeft
TrimLeft(StringField)
TrimLeft(Field1, '$') <> ''
TrimRight
TrimRight(StringField)
TrimRight(Field1, '.') <> ''
DateTime functions
Year
Year(DateField) = 2000
No
Month
Month(DateField) <> 12
No
Day
Day(DateField) = 1
No
Hour
Hour(DateField) < 16
No
Minute
Minute(DateField) = 0
No
Second
Second(DateField) = 30
No
GetDate
No
1228
Date
DateField = Date(GetDate)
No
Time
No
Like
No
In
Day(DateField) in (1,7)
No
State = 'M*'
Yes
Miscellaneous
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.
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 filebased 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
1229
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 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 for more information about this process.
1230
1231
Methods
Description
Use the IndexFieldNames property To create a temporary index at runtime that sorts the records in the client dataset, you
can use the IndexFieldNames property. Specify field names, separated by semicolons.
Ordering of field names in the list determines their order in the index.
This is the least powerful method of adding indexes. You can't specify a descending or
case-insensitive index, and the resulting indexes do not support grouping. These indexes
do not persist when you close the dataset, and are not saved when you save the client
dataset to a file.
Call AddIndex
To create an index at runtime that can be used for grouping, call AddIndex. AddIndex
lets you specify the properties of the index, including:
The name of the index. This can be used for switching indexes at runtime.
The fields that make up the index. The index uses these fields to sort records and to
locate records that have specific values on these fields.
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 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.
1232
100
50
200
75
10
200
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:
SalesRep Customer OrderNo Amount
100
50
200
75
10
200
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.
1233
While you can still do this, client datasets let you minimize the number of times calculated fields must be recomputed
by saving calculated values in the client dataset's data. When calculated values are saved with the client dataset,
they must still be recomputed when the user edits the current record, but your application need not recompute values
every time the current record changes. To save calculated values in the client dataset's data, use internally calculated
fields instead of calculated fields.
Internally calculated fields, just like calculated fields, are calculated in an OnCalcFields event handler. However, you
can optimize your event handler by checking the State property of your client dataset. When State is
dsInternalCalc, you must recompute internally calculated fields. When State is dsCalcFields, you need only
recompute regular calculated fields.
To use internally calculated fields, you must define the fields as internally calculated before you create the client
dataset. Depending on whether you use persistent fields or field definitions, you do this in one of the following ways:
If you use persistent fields, define fields as internally calculated by selecting InternalCalc in the Fields editor.
If you use field definitions, set the InternalCalcField property of the relevant field definition to True.
Note: Other types of datasets use internally calculated fields. However, with other datasets, you do not calculate
these values in an OnCalcFields event handler. Instead, they are computed automatically by the BDE or
remote database server.
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 each aggregate, the Expression property indicates the summary calculation it represents. Expression can
contain a simple summary expression such as
Sum(Field1)
1234
Aggregate expressions include one or more of the summary operators in the following table
Summary operators for maintained aggregates
Operator Use
Sum
Avg
Count
Min
Indicates the minimum value for a string, numeric, or date-time field or expression
Max
Indicates the maximum value for a string, numeric, or date-time field or expression
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:
Sum(Qty * Price)
Max(Field1) - Max(Field2)
Avg(DiscountRate) * 100
Min(Sum(Field1))
Count(Field1) - Field2
1235
100
50
200
75
10
200
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.
by using a provider. Once a data packet is assigned to Data, its contents are displayed automatically in data-aware
controls connected to the client dataset by a data source component.
When you open a client dataset that represents server data or that uses an external provider component, data
packets are automatically assigned to Data.
When your client dataset does not use a provider, you can copy the data from another client dataset as follows:
ClientDataSet1.Data := ClientDataSet2.Data;
Note: When you copy the Data property of another client dataset, you copy the change log as well, but the copy
does not reflect any filters or ranges that have been applied. To include filters or ranges, you must clone the
source dataset's cursor instead.
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;
the data to another dataset. Optionally, it can be included with the Delta property so that a provider can read this
information when it receives updates from the client dataset.
To save application-specific information with the Data property, use the SetOptionalParam method. This method
lets you store an OleVariant that contains the data under a specific name.
To retrieve this application-specific information, use the GetOptionalParam method, passing in the name that was
used when the information was stored.
1238
dbExpress
1239
TIBClientDataSet
InterBase Express
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.
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.
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 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.)
1241
ApplyUpdates takes a single parameter, MaxErrors, which indicates the maximum number of errors that the provider
should tolerate before aborting the update process. If MaxErrors is 0, then as soon as an update error occurs, the
entire update process is terminated. No changes are written to the database, and the client dataset's change log
remains intact. If MaxErrors is -1, any number of errors is tolerated, and the change log contains all records that
could not be successfully applied. If MaxErrors is a positive value, and more errors occur than are permitted by
MaxErrors, all updates are aborted. If fewer errors occur than specified by MaxErrors, all records successfully applied
are automatically cleared from the client dataset's change log.
ApplyUpdates returns the number of actual errors encountered, which is always less than or equal to MaxErrors
plus one. This return value indicates the number of records that could not be written to the database.
The client dataset's ApplyUpdates method does the following:
It indirectly calls the provider's ApplyUpdates method. The provider's ApplyUpdates method writes the updates
to the database, source dataset, or XML document and attempts to correct any errors it encounters. Records
that it cannot apply because of error conditions are sent back to the client dataset.
The client dataset 's ApplyUpdates method then attempts to reconcile these problem records by calling the
Reconcile method. Reconcile is an error-handling routine that calls the OnReconcileError event handler. You
must code the OnReconcileError event handler to correct errors. For details about using OnReconcileError, see
Reconciling Update Errors.
Finally, Reconcile removes successfully applied changes from the change log and updates Data to reflect the
newly updated records. When Reconcile completes, ApplyUpdates reports the number of errors that occurred.
Warning: In some cases, the provider can't determine how to apply updates (for example, when applying updates
from a stored procedure or multi-table join). Client datasets and provider components generate events
that let you handle these situations. See Intervening as updates are applied for details.
Tip: If the provider is on a stateless application server, you may want to communicate with it about persistent state
information before or after you apply updates. TClientDataSet receives a BeforeApplyUpdates event before
the updates are sent, which lets you send persistent state information to the server. After the updates are
applied (but before the reconcile process), TClientDataSet receives an AfterApplyUpdates event where you
can respond to any persistent state information returned by the application server.
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;
for client datasets that use an internal provider. If you are using TClientDataSet, you can use the provider
component's OnUpdateError event instead.
After the entire update operation is finished, the client dataset generates an OnReconcileError event for every
record that the provider could not apply to the database server.
You should always code an OnReconcileError or OnUpdateError event handler, even if only to discard the records
returned that could not be applied. The event handlers for these two events work the same way. They include the
following parameters:
DataSet: A client dataset that contains the updated record which couldn't be applied. You can use this dataset's
methods to get information about the problem record and to edit the record in order to correct any problems. In
particular, you will want to use the CurValue, OldValue, and NewValue properties of the fields in the current record
to determine the cause of the update problem. However, you must not call any client dataset methods that change
the current record in your event handler.
E: An object that represents the problem that occurred. You can use this exception to extract an error message or
to determine the cause of the update error.
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;
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.
Provider's location
1245
Note: If the connection component is an instance of TDCOMConnection, the application server must be registered
on the client machine.
At runtime, you can switch among available providers (both local and remote) by setting ProviderName in code.
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.
1246
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 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.
Execute tells the provider to execute the query or stored procedure and return any output parameters. These returned
parameters are then used to automatically update the Params property.
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.
Note: You may want to initialize parameter values from the current settings on the source dataset. You can do this
by right-clicking the client dataset and choosing Fetch Params at design time or calling the FetchParams
method at runtime.
1248
then it can use the Params property to limit the records that it caches in memory. Each parameter represents a field
value that must be matched before a record can be included in the client dataset's data. This works much like a filter,
except that with a filter, the records are still cached in memory, but unavailable.
Each parameter name must match the name of a field. When using TClientDataSet, these are the names of fields
in the TTable or TSQLTable component associated with the provider. When using TSimpleDataSet or
TBDEClientDataSet, these are the names of fields in the table on the database server. The data in the client dataset
then includes only those records whose values on the corresponding fields match the values assigned to the
parameters.
For example, consider an application that displays the orders for a single customer. When the user identifies the
customer, the client dataset sets its Params property to include a single parameter named CustID (or whatever field
in the source table is called) whose value identifies the customer whose orders should be displayed. When the client
dataset requests data from the source dataset, it passes this parameter value. The provider then sends only the
records for the identified customer. This is more efficient than letting the provider send all the orders records to the
client application and then filtering the records using the client dataset.
1249
Tip: Always call DisableConstraints and EnableConstraints in paired blocks to ensure that constraints are enabled
when you intend them to be.
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;
events).
4 The provider receives an AfterApplyUpdates event, where it can respond to the current value of OwnerData and
5 The client dataset receives an AfterApplyUpdates event, where it can respond to the returned value of
OwnerData.
Every other IAppServer method call is accompanied by a similar set of BeforeXXX and AfterXXX events that let you
customize the communication between client dataset and provider.
In addition, the client dataset has a special method, DataRequest, whose only purpose is to allow application-specific
communication with the provider. When the client dataset calls DataRequest, it passes an OleVariant as a parameter
that can contain any information you want. This, in turn, generates an is the OnDataRequest event on the provider,
where you can respond in any application-defined way and return a value to the client dataset.
Save data
1252
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.
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.
Only TClientDataSet can be used in a multi-tiered database application. Thus, if you are writing a multi-tiered
application, or if you intend to scale up to a multi-tiered application eventually, you should use TClientDataSet
with an external provider and source dataset.
Because the source dataset is internal to the simple dataset component, you can't link two source datasets in
a master/detail relationship to obtain nested detail sets. (You can, however, link two simple datasets into a
master/detail relationship.)
The simple dataset does not surface any of the events or properties that occur on its internal dataset provider.
However, in most cases, these events are used in multi-tiered applications, and are not needed for two-tiered
applications.
Setting up a simple dataset provides information on setting up a simple dataset:
To use TSimpleDataSet:
1 Place the TSimpleDataSet component in a data module or on a form. Set its Name property to a unique value
If you have a named connection in the connections file, expand the Connection property and specify the
ConnectionName value.
For greater control over connection properties, transaction support, login support, and the ability to use a single
connection for more than one dataset, use a separate TSQLConnection component instead. Specify the
TSQLConnection component as the value of the Connection property. For details on TSQLConnection, see
Connecting to databases.
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.
1254
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.
1255
1256
tiered applications, this parameter indicates the provider on the application server with which the client dataset
communicates. Most methods also include an OleVariant parameter called OwnerData that allows a client dataset
and a provider to pass custom information back and forth. OwnerData is not used by default, but is passed to all
event handlers so that you can write code that allows your provider to adjust to application-defined information before
and after each call from a client dataset.
AppServer interface members
IAppServer
Provider Component
TClientDataSet
AS_ApplyUpdates method
AS_DataRequest method
AS_Execute method
AS_GetParams method
AS_GetRecords method
AS_RowRequest method
1258
Note: These techniques for controlling the content of data packets are only available for dataset providers. When
using TXMLTransformProvider, you can only control the content of data packets by controlling the
transformation file the provider uses.
Meaning
poAutoRefresh
The provider refreshes the client dataset with current record values whenever it applies updates.
poReadOnly
poDisableEdits
Client datasets can't modify existing data values. If the user tries to edit a field, the client dataset
raises exception. (This does not affect the client dataset's ability to insert or delete records).
poDisableInserts
Client datasets can't insert new records. If the user tries to insert a new record, the client dataset
raises an exception. (This does not affect the client dataset's ability to delete records or modify
existing data)
poDisableDeletes
Client datasets can't delete records. If the user tries to delete a record, the client dataset raises
an exception. (This does not affect the client dataset's ability to insert or modify records)
poFetchBlobsOnDemand
BLOB field values are not included in data packets. Instead, client datasets must request these
values on an as-needed basis. If the client dataset's FetchOnDemand property is True, it
requests these values automatically. Otherwise, the application must call the client dataset's
FetchBlobs method to retrieve BLOB data.
poFetchDetailsOnDemand
When the provider's dataset represents the master of a master/detail relationship, nested detail
values are not included in data packets. Instead, client datasets request these on an as-needed
basis. If the client dataset's FetchOnDemand property is True, it requests these values
automatically. Otherwise, the application must call the client dataset's FetchDetails method to
retrieve nested details.
poIncFieldProps
The data packet includes the following field properties (where applicable): Alignment,
DisplayLabel, DisplayWidth, Visible, DisplayFormat, EditFormat, MaxValue, MinValue,
Currency, EditMask, DisplayValues.
1259
poCascadeDeletes
When the provider's dataset represents the master of a master/detail relationship, the server
automatically deletes detail records when master records are deleted. To use this option, the
database server must be set up to perform cascaded deletes as part of its referential integrity.
poCascadeUpdates
When the provider's dataset represents the master of a master/detail relationship, key values
on detail tables are updated automatically when the corresponding values are changed in
master records. To use this option, the database server must be set up to perform cascaded
updates as part of its referential integrity.
poAllowMultiRecordUpdates A single update can cause more than one record of the underlying database table to change.
This can be the result of triggers, referential integrity, SQL statements on the source dataset,
and so on. Note that if an error occurs, the event handlers provide access to the record that was
updated, not the other records that change in consequence.
poNoReset
Client datasets can't specify that the provider should reposition the cursor on the first record
before providing data.
poPropogateChanges
Changes made by the server to updated records as part of the update process are sent back
to the client and merged into the client dataset.
poAllowCommandText
The client can override the associated dataset's SQL text or the name of the table or stored
procedure it represents.
poRetainServerOrder
The client dataset should not re-sort the records in the dataset to enforce a default order.
When the client dataset applies updates, the time the original records were provided can be read in the provider's
OnUpdateData event:
procedure TMyDataModule1.Provider1UpdateData(Sender: TObject; DataSet:
TCustomClientDataSet);
var
WhenProvided: TDateTime;
begin
WhenProvided := DataSet.GetOptionalParam('TimeProvided');
1260
...
end;
Description
usInserted
usDeleted
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;
Unless you specify otherwise, all fields in the delta packet records are included in the UPDATE clause and in the
WHERE clause. However, you may want to exclude some of these fields. One way to do this is to set the UpdateMode
property of the provider. UpdateMode can be assigned any of the following values:
1262
UpdateMode values
Value
Meaning
upWhereAll
upWhereChanged Only key fields and fields that are changed are used to locate records.
upWhereKeyOnly Only key fields are used to locate records.
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 the following table
ProviderFlags values
Value
Description
pfInWhere The field appears in the WHERE clause of generated INSERT, DELETE, and UPDATE statements when
UpdateMode is upWhereAll or upWhereChanged.
pfInUpdate The field appears in the UPDATE clause of generated UPDATE statements.
pfInKey
The field is used in the WHERE clause of generated statements when UpdateMode is upWhereKeyOnly.
pfHidden
The field is included in records to ensure uniqueness, but can't be seen or used on the client side.
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.
1263
a mechanism for applying updates to a stored procedure (which can't be updated automatically), allowing the provider
to skip any automatic processing once the record is updated from within the event handler.
1264
1265
1266
Encapsulation of business logic in a shared middle tier. Different client applications all access the same
middle tier. This allows you to avoid the redundancy (and maintenance cost) of duplicating your business rules
for each separate client application.
Thin client applications. Your client applications can be written to make a small footprint by delegating more
of the processing to middle tiers. Not only are client applications smaller, but they are easier to deploy because
they don't need to worry about installing, configuring, and maintaining the database connectivity software (such
as the database server's client-side software). Thin client applications can be distributed over the Internet for
additional flexibility.
Distributed data processing. Distributing the work of an application over several machines can improve
performance because of load balancing, and allow redundant systems to take over when a server goes down.
Increased opportunity for security. You can isolate sensitive functionality into tiers that have different access
restrictions. This provides flexible and configurable levels of security. Middle tiers can limit the entry points to
sensitive material, allowing you to control access more easily. If you are using HTTP or COM+, you can take
advantage of the security models they support.
Description
Specialized data modules that can act as a COM Automation server or implement a Web Service
to give client applications access to any providers they contain. Used on the application server.
Provider component
A data broker that provides data by creating data packets and resolves client updates. Used on
the application server.
Client dataset component A specialized dataset that uses midas.dll or midaslib.dcu to manage data stored in data packets.
The client dataset is used in the client application. It caches updates locally, and applies them in
delta packets to the application server.
Connection components
A family of components that locate the server, form connections, and make the IAppServer
interface available to client datasets. Each connection component is specialized to use a particular
communications protocol.
The provider and client dataset components require midas.dll or midaslib.dcu, which manages datasets stored as
data packets. (Note that, because the provider is used on the application server and the client dataset is used on
the client application, if you are using midas.dll, you must deploy it on both application server and client application.)
Note: You must purchase server licenses for deploying your application server.
An overview of the architecture into which these components fit is described in Using a multi-tiered architecture. For
more information on how these components fit together to create a multi-tiered application, see
Overview of a Three-tiered Application
The Structure of the Client Application
The Structure of the Application Server
Choosing a Connection Protocol
1267
time or runtime). If the application server is not already running, it starts. The client receives an IAppServer
interface for communicating with the application server.
2 The client requests data from the application server. A client may request all data at once, or may request chunks
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).
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.
10The client refreshes its data from the server.
Protocol
TDCOMConnection DCOM
TSocketConnection Windows sockets (TCP/IP)
1268
TWebConnection
HTTP
CORBA (IIOP)
Note: The DataSnap category of the Tool 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.
1269
For every dataset that the remote data module exposes to clients, it must include a dataset provider. A dataset
provider packages data into data packets that are sent to client datasets and applies updates received from client
datasets back to a source dataset or a database server.
It must include an XML provider for every XML document that the remote data module exposes to clients. An XML
provider acts like a dataset provider, except that it fetches data from and applies updates to an XML document rather
than a database server.
Note: Do not confuse database connection components, which connect datasets to a database server, with the
connection components used by client applications in a multi-tiered application. The connection components
in multi-tiered applications can be found on the DataSnap category or WebServices category of the Tool
palette.
By default, all automatically generated calls to a transactional data module are transactional (that is, they assume
that when the call exits, the data module can be deactivated and any current transactions committed or rolled back).
You can write a transactional data module that depends on persistent state information by setting the AutoComplete
property to False, but it will not support transactions, just-in-time activation, or as-soon-as-possible deactivation
unless you use a custom interface.
Warning: Application servers containing transactional data modules should not open database connections until
the data module is activated. While developing your application, be sure that all datasets are not active
and the database is not connected before running your application. In the application itself, add code to
open database connections when the data module is activated and close them when it is deactivated.
1271
1272
Web connections can take advantage of the SSL security provided by wininet.dll (a library of Internet utilities that
runs on the client system). Once you have configured the Web server on the server system to require authentication,
you can specify the user name and password using the properties of the Web connection component.
As an additional security measure, the application server must register its availability to clients using a Web
connection. By default, all new remote data modules automatically register themselves by adding a call to
EnableWebTransport in the UpdateRegistry method. You can remove this call to prevent Web connections to your
application server.
Web connections can take advantage of object pooling. This allows your server to create a limited pool of remote
data module instances that are available for client requests. By pooling the remote data modules, your server does
not consume the resources for the data module and its database connection except when they are needed.
Unlike most other connection components, you can't use callbacks when the connection is formed via HTTP.
The order of creation is important. You should create and run the application server before you create a client. At
design time, you can then connect to the application server to test your client. You can, of course, create a client
without specifying the application server at design time, and only supply the server name at runtime. However, doing
so prevents you from seeing if your application works as expected when you code at design time, and you will not
be able to choose servers and providers using the Object Inspector.
Note: If you are not creating the client application on the same system as the server, and you are using a DCOM
connection, you may want to register the application server on the client system. This makes the connection
component aware of the application server at design time so that you can choose server names and provider
names from a drop-down list in the Object Inspector. (If you are using a Web connection, SOAP connection,
or socket connection, the connection component fetches the names of registered providers from the server
machine.)
If you are using SOAP as a transport protocol, this should be a new Web Service application. Choose File
New Other, and on the WebServices page of the new items dialog, choose SOAP Server application. Select
the type of Web Server you want to use, and when prompted whether you want to define a new interface for
the SOAP module, say no.
For any other transport protocol, you need only choose File
New
Application.
New
ActiveX, Delphi Files, or WebServices page of the new items dialog, select
Remote Data Module if you are creating a COM Automation server that clients access using DCOM, HTTP,
or sockets.
Transactional Data Module if you are creating a remote data module that runs under COM+ (or MTS).
Connections can be formed using DCOM, HTTP, or sockets. However, only DCOM supports the security
services.
SOAP Server Data Module if you are creating a SOAP server in a Web Service application.
For more detailed information about setting up a remote data module, see Setting up the remote data module.
Note:
Remote data modules are more than simple data modules. The SOAP data module implements
an invokable interface in a Web Service application. Other data modules are COM Automation
objects.
3 Place the appropriate dataset components on the data module and set them up to access the database server.
4 Place a TDataSetProvider component on the data module for each dataset you want to expose to clients. This
provider is required for brokering client requests and packaging data. Set the DataSet property for each provider
to the name of the dataset to access. You can set additional properties for the provider. See Using provider
components for more detailed information about setting up a provider.
If you are working with data from XML documents, you can use a TXMLTransformProvider component instead
of a dataset and TDataSetProvider component. When using TXMLTransformProvider, set the XMLDataFile
property to specify the XML document from which data is provided and to which updates are applied.
5 Write application server code to implement events, shared business rules, shared data validation, and shared
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.
1274
Configuring TRemoteDataModule
To add a TRemoteDataModule component to your application, choose File
New
Other and select Remote
Data Module from the ActiveX 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.
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.
1275
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.
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.
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
descendantthat 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
1276
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.
1277
1278
The detail table must fetch and store all of its records from the application server even though it only uses one
detail set at a time. (This problem can be mitigated by using parameters. For more information, see Limiting
records with parameters.)
It is very difficult to apply updates, because client datasets apply updates at the dataset level and master/detail
updates span multiple datasets. Even in a two-tiered environment, where you can use the database connection
component to apply updates for multiple tables in a single transaction, applying updates in master/detail forms
is tricky.
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.
To enable incremental fetching in a stateless application server, you can do the following:
When the provider packages a set of records in a data packet, it notes the value of CUST_NO on the last record in
the packet:
TRemoteDataModule1.DataSetProvider1GetData(Sender: TObject; DataSet: TCustomClientDataSet);
begin
DataSet.Last; { move to the last record }
with Sender as TDataSetProvider do
Tag := DataSet.FieldValues['CUST_NO']; {save the value of CUST_NO }
end;
The provider sends this last CUST_NO value to the client after sending the data packet:
1279
TRemoteDataModule1.DataSetProvider1AfterGetRecords(Sender: TObject;
var OwnerData: OleVariant);
begin
with Sender as TDataSetProvider do
OwnerData := Tag; {send the last value of CUST_NO }
end;
On the client, the client dataset saves this last value of CUST_NO:
TDataModule1.ClientDataSet1AfterGetRecords(Sender: TObject; var OwnerData: OleVariant);
begin
with Sender as TClientDataSet do
Tag := OwnerData; {save the last value of CUST_NO }
end;
Before fetching a data packet, the client sends the last value of CUST_NO it received:
TDataModule1.ClientDataSet1BeforeGetRecords(Sender: TObject; var OwnerData: OleVariant);
begin
with Sender as TClientDataSet do
begin
if not Active then Exit;
OwnerData := Tag; { Send last value of CUST_NO to application server }
end;
end;
Finally, on the server, the provider uses the last CUST_NO sent as a minimum value in the query:
TRemoteDataModule1.DataSetProvider1BeforeGetRecords(Sender: TObject;
var OwnerData: OleVariant);
begin
if not VarIsEmpty(OwnerData) then
with Sender as TDataSetProvider do
with DataSet as TSQLDataSet do
begin
Params.ParamValues['MinVal'] := OwnerData;
Refresh; { force the query to reexecute }
end;
end;
1280
For information about extending the parent remote data module's interface, see Extending the application server's
interface.
Tip: You may also want to extend the interface for each child data module, exposing the parent data module's
interface, or the interfaces of the other child data modules. This lets the various data modules in your application
server communicate more freely with each other.
Once you have added properties that represent the child remote data modules to the main remote data module,
client applications do not need to form separate connections to each remote data module on the application server.
Instead, they share a single connection to the parent remote data module, which then dispatches messages to the
"child" data modules. Because each client application uses the same connection for every remote data module, the
remote data modules can share a single database connection, conserving resources. For information on how child
applications share a single connection, see Connecting to an Application Server That Uses Multiple Data Modules.
communication protocol you want to use. See The Structure of the Client Application for details.
3 Set properties on your connection component to specify the application server with which it should establish a
connection. To learn more about setting up the connection component, see Connecting to the Application Server.
1281
4 Set the other connection component properties as needed for your application. For example, you might set
the ObjectBroker property to allow the connection component to choose dynamically from several servers. For
more information about using the connection components, see Managing Server Connections.
5 Place as many TClientDataSet components as needed on the data module, and set the RemoteServer property
for each component to the name of the connection component you placed in Step 2. For a full introduction to
client datasets, see Using Client Datasets.
6 Set the ProviderName property for each TClientDataSet component. If your connection component is connected
to the application server at design time, you can choose available application server providers from the
ProviderName property's drop-down list.
7 Continue in the same way you would create any other database application. There are a few additional features
1282
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.
1283
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.
IAppServerSOAP descendant) so that TSOAPConnection can identify the remote data module you want to use.
There are two ways to do this:
Set the SOAPServerIID property to indicate the interface of the target remote data module. This method works
for any server that implements an IAppServerSOAP descendant. SOAPServerIID identifies the target interface
by its GUID. At runtime, you can use the interface name, and the compiler automatically extracts the GUID.
However, at design time, in the Object Inspector, you must specify the GUID string.
If the server is written using the Delphi language, you can simply include the name of the SOAP data module's
interface following a slash at the end of the path portion of the URL. This lets you specify the interface by name
rather than GUID, but is only available when both client and server are written in Delphi.
Tip: The first approach, using the SOAPServerIID method, has the added advantage that it lets you call extensions
to the remote data module's interface.
If you are using a proxy server, you must indicate the name of the proxy server using the Proxy property. If that proxy
requires authentication, you must also set the values of the UserName and Password properties so that the
connection component can log on.
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.
1285
1286
MyConnection.AppServer.SpecialMethod(x,y);
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.
To use early binding under DCOM, the server's type library must be registered on the client machine. You can use
TRegsvr.exe, which ships with Delphi to register the type library.
Note: See the TRegSvr demo (which provides the source for TRegsvr.exe) for an example of how to register the
type library programmatically.
Note: To use the dispinterface, you must add the _TLB unit that is generated when you save the type library to the
uses clause of your client module.
The TSOAPConnection component must have its UseSOAPAdapter property set to True. This means that the
server must support the IAppServerSOAP interface. If the application server is built using Delphi 6 or Kylix 1, it
does not support IAppServerSOAP and you must use a separate THTTPRio component instead. For details on
how to call an interface using a THTTPRio component, see Calling invokable interfaces.
You must set the SOAPServerIID property of the SOAP connection component to the GUID of the server
interface. You must set this property before your application connects to the server, because it tells the
TSOAPConnection component what interface to fetch from the server.
Assuming the previous three conditions are met, you can fetch the server interface as follows:
with MyConnection.GetSOAPServer as IMyAppServer do
SpecialMethod(x,y);
Connecting to the Application Server. 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.
1288
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 inprocess 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.
Warning: 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.
the same system as the client application. You can use a ready-made server such as Microsoft's Personal Web
server or you can write your own using the socket components described in "Working with Sockets."
2 Create the client application following the steps described in Creating the client application except start by
choosing File
New
ActiveX
3 If your client application uses a data module, add a call to explicitly create the data module in the active form
initialization.
4 When your client application is finished, compile the project, and select Project
Any Web browser that can run Active forms can run your client application by specifying the .HTM file that was
created when you deployed the client application. This .HTM file has the same name as your client application project,
and appears in the directory specified as the Target directory.
1290
Web browser. You can also build an InternetExpress application using the Site Express architecture by using the
InternetExpress page producer (TInetXPageProducer).
New
Other to display the New Items dialog box, and on the New page select Web Server
application. This process is described in Creating Web server applications with Web Broker.
2 From the DataSnap category of the Tool palette, add a connection component to the Web Module that appears
when you create a new Web server application. The type of connection component you add depends on the
communication protocol you want to use. See Choosing a connection protocol for details.
3 Set properties on your connection component to specify the application server with which it should establish a
connection. To learn more about setting up the connection component, see Connecting to the application server.
4 Instead of a client dataset, add an TXMLBroker from the InternetExpress category of the Tool palette to the Web
module. Like TClientDataSet, TXMLBroker represents the data from a provider on the application server and
interacts with the application server through an IAppServer interface. However, unlike client datasets, XML
brokers request data packets as XML instead of as OleVariants and interact with InternetExpress components
instead of data controls.
5 Set the RemoteServer property of the XML broker to point to the connection component you added in step 2.
Set the ProviderName property to indicate the provider on the application server that provides data and applies
updates. For more information about setting up the XML broker, see Using an XML broker.
6 Add an InternetExpress page producer (TInetXPageProducer) to the Web module for each separate page that
users will see in their browsers. For each page producer, you must set the IncludePathURL property to indicate
where it can find the javascript libraries that augment its generated HTML controls with data management
capabilities.
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.
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.
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.
Description
xmldom.js
This library is a DOM-compatible XML parser written in javascript. It allows parsers that do not support XML to
use XML data packets. Note that this does not include support for XML Islands, which are supported by IE5
and later.
xmldb.js
This library defines data access classes that manage XML data packets and XML delta packets.
xmldisp.js
This library defines classes that associate the data access classes in xmldb with HTML controls in the HTML
page.
1291
xmlerrdisp.js This library defines classes that can be used when reconciling update errors. These classes are not used by
any of the built-in InternetExpress components, but are useful when writing a Reconcile producer.
xmlshow.js
This library includes functions to display formatted XML data packets and XML delta packets. This library is not
used by any of the InternetExpress components, but is useful when debugging.
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).
list of accounts with access permission, where computername is the name of the machine that runs the Web
application.
4 Select Use Custom Launch Permissions, and press the Edit button. Add IUSR_computername to this list as well.
5 Click the Apply button.
1292
Note: Even when using SOAP, where the application server supports IAppServerSOAP, the XML broker uses
IAppServer because the connection component acts as an adapter between the two interfaces.
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.
1293
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
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
1294
Note: You must have Internet Explorer 4 or better installed to use the Web page editor.
The top of the Web page editor displays the Web items that generate the HTML document. These Web items are
nested, where each type of Web item assembles the HTML generated by its subitems. Different types of items can
contain different subitems. On the left, a tree view displays all of the Web items, indicating how they are nested. On
the right, you can see the Web items included by the currently selected item. When you select a component in the
top of the Web page editor, you can set its properties using the Object Inspector.
Click the New Item button to add a subitem to the currently selected item. The Add Web Component dialog lists only
those items that can be added to the currently selected item.
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).
You can 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,
1295
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 also 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.
<HEAD>
</HEAD>
<BODY>
</BODY>
</HTML>
<#STYLES> generates the statements that defines a style sheet from definitions listed in the Styles or StylesFile
property of the InternetExpress page producer.
1296
<#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.
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.
1297
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.
1298
Consider, for example, an XML document that represents a set of email messages. It might look like the following
(containing a single message):
<?xml version="1.0" standalone="yes" ?>
<email>
<head>
<from>
<name>Dave Boss</name>
<address>dboss@MyCo.com</address>
</from>
<to>
<name>Joe Engineer</name>
<address>jengineer@MyCo.com</address>
</to>
<cc>
<name>Robin Smith/name>
<address>rsmith@MyCo.com</address>
</cc>
<cc>
<name>Leonard Devon</name>
<address>ldevon@MyCo.com</address>
</cc>
</head>
<body>
<subject>XML components</subject>
<content>
Joe,
Attached is the specification for the XML component support in Delphi.
This looks like a good solution to our buisness-to-buisness application!
Also attached, please find the project schedule. Do you think its reasonable?
Dave.
</content>
<attachment attachfile="XMLSpec.txt"/>
<attachment attachfile="Schedule.txt"/>
</body>
</email>
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:
SenderName SenderAddress
Dave Boss
To
CC
Subject
Content Attach
Address
Address
Robin Smith
rsmith@MyCo.Com
1299
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.
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.
Choose the Datapacket to XML button if the mapping goes from data packet to XML document.
1301
Choose the XML to Datapacket button if the mapping goes from XML document to data packet.
2 If you are generating a data packet, you will also want to use the radio buttons in the Create Datapacket As
section. These buttons let you specify how the data packet will be used: as a dataset, as a delta packet for
applying updates, or as the parameters to supply to a provider before fetching data.
3 Click Create and Test Transformation to generate an in-memory version of the transformation. XML mapper
displays the XML document that would be generated for the data packet in the Datapacket pane or the data
packet that would be generated for the XML document in the XML Document pane.
4 Finally, choose File
Save Transformation to save the transformation file. The transformation file is a special
XML file (with the .xtr extension) that describes the transformation you have defined.
1302
1303
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.
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;
1305
1306
WebSnap
Backward compatible
No scripting support.
No session support.
Every request must be explicitly handled, using either an action Dispatch components automatically respond to a variety of
item or an auto-dispatching component.
requests.
Only a few specialized components provide previews of the
content they produce. Most development is not visual.
For more information on Web Broker, see Using Web Broker. For more information on WebSnap, see Creating Web
Server Applications Using WebSnap.
1308
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.
The first line identifies the request as a GET. A GET request message asks the Web server application to return the
content associated with the URI that follows the word GET (in this case /art/gallery.dll/animals?
animal=doc&color=black). The last part of the first line indicates that the client is using the HTTP 1.0 standard.
The second line is the Connection header, and indicates that the connection should not be closed once the request
is serviced. The third line is the User-Agent header, and provides information about the program generating the
request. The next line is the Host header, and provides the Host name and port on the server that is contacted to
1309
form the connection. The final line is the Accept header, which lists the media types the client can accept as valid
responses.
which specifies an HTTP server in the www.TSite.com domain. The client contacts www.TSite.com, connects to the
HTTP server, and passes it a request. The request might look something like this:
GET /art/gallery.dll/animals?animal=dog&color=black HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/3.0b4Gold (WinNT; I)
Host: www.TSite.com:1024
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
1310
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';
1311
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.
New and select Web Server Application. In the New Web Server Application
dialog box, choose the appropriate target type.
New and select the template you saved in step 3. This will be on the page
1312
Project Manager. Expand your project so all of its units are visible.
3 In the Project Manager, click the New button to create a new Web server application project. Double-click the
WebSnap Application item in the WebSnap tab. Select the appropriate options for your project, including the
server type you want to use, then click OK.
4 Expand the new project in the Project Manager. Select any files appearing there and delete them.
5 One at a time, select each file in your project (except for the form file in a Web App Debugger project) and drag
it to the new project. When a dialog appears asking if you want to add that file to your new project, click Yes.
executable.
Run. This displays the console window of the COM server that is your Web server application.
The first time you run your application, it registers your COM server so that the Web App debugger can access it.
2 Choose Run
3 Select Tools
4 Click the Start button. This displays the ServerInfo page in your default Browser.
5 The ServerInfo page provides a drop-down list of all registered Web Application Debugger executables. Select
your application from the drop-down list. If you do not find your application in this drop-down list, try running your
application as an executable. Your application must be run once so that it can register itself. If you still do not
find your application in the drop-down list, try refreshing the Web page. (Sometimes the Web browser caches
this page, preventing you from seeing the most recent changes.)
1313
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.
Once you have set the Host Application and Run Parameters, you can set up your breakpoints so that when the
server passes a request message to your DLL, you hit one of your breakpoints, and can debug normally.
Note: Before launching the Web server using your application's run parameters, make sure that the server is not
already running.
and double-click User Rights Assignment. Double-click Act as part of the operating system in the right-hand panel.
2 Select Add to add a user to the list. Add your current user.
3 Reboot so the changes take effect.
1314
To create a new Web server application using the Web Broker architecture:
1 Select File
New
Other.
2 In the New Items dialog box, select the New tab under Delphi Projects and choose Web Server Application.
3 A dialog box appears, where you can select one of the Web server application types:
ISAPI and NSAPI: Selecting this type of application sets up your project as a DLL, with the exported methods
expected by the 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.
CGI stand-alone: Selecting this type of application sets up your project as a console application, and adds the
required entries to the uses clause of the project file.
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.
1315
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.
1316
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.
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.
that checks whether the response is complete and either sends the response or returns an error code. Or, you can
add a separate action item for every type of request, where each action item completely handles the request.
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.
Set up action items for your Web server application by
Adding actions to the dispatcher
Determining when action items fire
Responding to request messages with action items
and assuming that the /gallery.dll part indicates the Web server application, the path information portion is
/mammals
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.
Meaning
mtGet
The request is asking for the information associated with the target URI to be returned in a response message.
mtHead The request is asking for the header properties of a response, as if servicing an mtGet request, but omitting the
content of the response.
mtPost
mtPut
The request asks that the resource associated with the target URI be replaced by the content of the request message.
mtAny
Matches any request method type, including mtGet, mtHead, mtPut, and mtPost.
1319
Warning: Changing the Enabled property of an action during execution may cause unexpected results for
subsequent requests. If the Web server application is a DLL that caches Web modules, the initial state
will not be reinitialized for the next request. Use the BeforeDispatch event to ensure that all action items
are correctly initialized to their appropriate starting states.
1320
1321
HEAD
Header information from an equivalent GET message, without the content of the response.
POST
The server application to post the data included in the Content property, as appropriate.
PUT
The server application to replace the resource indicated by the URL property with the data included in the Content
property.
DELETE
The server application to delete or hide the resource identified by the URL property.
TRACE
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.
1322
1323
1324
The Content property is a string. Delphi strings are not limited to text values, so the value of the Content property
can be a string of HTML commands, graphics content such as a bit-stream, or any other MIME content type.
Use the ContentStream property if the content for the response message can be read from a stream. For example,
if the response message should send the contents of a file, use a TFileStream object for the ContentStream property.
As with the Content property, ContentStream can provide a string of HTML commands or other MIME content type.
If you use the ContentStream property, do not free the stream yourself. The Web response object automatically frees
it for you.
Note: If the value of the ContentStream property is not nil, the Content property is ignored.
HTML Templates
An HTML template is a sequence of HTML commands and HTML-transparent tags. An HTML-transparent tag has
the form
1325
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.
When working with HTML templates, you will
Optionally, Use predefined HTML-transparent tag Names
Specify the HTML template
Convert HTML-transparent tags
Link
tgLink
A hypertext link. The result is an HTML sequence beginning with an <A> tag and ending with an </
A> tag.
Image
tgImage
Table
tgTable
An HTML table. The result is an HTML sequence beginning with a <TABLE> tag and ending with
a </TABLE> tag.
ImageMap tgImageMap A graphic image with associated hot zones. The result is an HTML sequence beginning with a
<MAP> tag and ending with a </MAP> tag.
Object
tgObject
An embedded ActiveX object. The result is an HTML sequence beginning with an <OBJECT> tag
and ending with an </OBJECT> tag.
Embed
tgEmbed
A Netscape-compliant add-in DLL. The result is an HTML sequence beginning with an <EMBED>
tag and ending with an </EMBED> tag.
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.
object that contains the HTML template. If you use either the HTMLFile property or the HTMLDoc property to specify
the template, you can generate the converted HTML commands by calling the Content method.
In addition, you can call the ContentFromString method to directly convert an HTML template that is a single string
which is passed in as a parameter. You can also call the ContentFromStream method to read the HTML template
from a stream. Thus, for example, you could store all your HTML templates in a memo field in a database, and use
the ContentFromStream method to obtain the converted HTML commands, reading the template directly from a
TBlobStream object.
The OnHTMLTag event handler replaces the custom tag (<#UserName>) in the HTML during execution:
procedure WebModule1.PageProducer1HTMLTag(Sender : TObject;Tag: TTag;
const TagString: string; TagParams: TStrings; var ReplaceText: string);
begin
if CompareText(TagString,'UserName') = 0 then
ReplaceText := TPageProducer(Sender).Dispatcher.Request.Content;
end;
If the content of the request message was the string Mr. Ed, the value of Response.Content would be
<HTML>
<HEAD><TITLE>Our Brand New Web Site</TITLE></HEAD>
<BODY>
Hello Mr. Ed! Welcome to our Web site.
1327
</BODY>
</HTML>
Note: This example uses an OnAction event handler to call the content producer and assign the content of the
response message. You do not need to write an OnAction event handler if you assign the page producer's
HTMLFile property at design time. In that case, you can simply assign PageProducer1 as the value of the
action item's Producer property to accomplish the same effect as the OnAction event handler above.
For example, consider an application that returns calendar pages in response to request messages that specify the
month and year of the desired page. Each calendar page contains a picture, followed by the name and year of the
month between small images of the previous month and next months, followed by the actual calendar. The resulting
image looks something like this:
The general form of the calendar is stored in a template file. It looks like this:
<HTML>
<Head></HEAD>
1328
<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.
1329
in the HTML template, where FieldName specifies the name of the field in the dataset whose value should be
displayed.
When your application calls the Content, ContentFromString, or ContentFromStream method, the dataset page
producer substitutes the current field values for the tags that represent fields.
1330
At design time, specify these properties using the Object Inspector. Select the table producer object in the Object
Inspector and expand the TableAttributes property to access the display properties of the THTMLTableAttributes
object.
You can also specify these properties programmatically at runtime.
Using TDataSetTableProducer
TDataSetTableProducer is a table producer that creates an HTML table for a dataset. Set the DataSet property
of TDataSetTableProducer to specify the dataset that contains the records you want to display. You do not set
the DataSource property, as you would for most data-aware objects in a conventional database application. This is
because TDataSetTableProducer generates its own data source internally.
You can set the value of DataSet at design time if your Web application always displays records from the same
dataset. You must set the DataSet property at runtime if you are basing the dataset on the information in the HTTP
request message.
1331
Using TQueryTableProducer
You can produce an HTML table to display the results of a query, where the parameters of the query come from the
HTTP request message. Specify the TQuery object that uses those parameters as the Query property of a
TQueryTableProducer component.
If the request message is a GET request, the parameters of the query come from the Query fields of the URL that
was given as the target of the HTTP request message. If the request message is a POST request, the parameters
of the query come from the content of the request message.
When you call the Content method of TQueryTableProducer, it runs the query, using the parameters it finds in the
request object. It then formats an HTML table to display the records in the resulting dataset.
As with any table producer, you can customize the display properties or column bindings of the HTML table, or
embed the table in a larger HTML document.
1332
Using WebSnap
Creating Web Server Applications Using WebSnap
WebSnap augments Web Broker with additional components, wizards, and viewsmaking it easier to build Web
server applications that deliver complex, data-driven Web pages. WebSnap's support for multiple modules and for
server-side scripting makes development and maintenance easier for teams of developers and Web designers.
WebSnap allows HTML design experts on your team to make a more effective contribution to Web server
development and maintenance. The final product of the WebSnap development process includes a series of
scriptable HTML page templates. These pages can be changed using HTML editors that support embedded script
tags, like Microsoft FrontPage, or even a simple text editor. Changes can be made to the templates as needed, even
after the application is deployed. There is no need to modify the project source code at all, which saves valuable
development time. Also, WebSnap's multiple module support can be used to partition your application into smaller
pieces during the coding phases of your project. Developers can work more independently.
The dispatcher components automatically handle requests for page content, HTML form submissions, and requests
for dynamic images. WebSnap components called adapters provide a means to define a scriptable interface to the
business rules of your application. For example, the TDataSetAdapter object is used to make dataset components
scriptable. You can use WebSnap producer components to quickly build complex, data-driven forms and tables, or
to use XSL to generate a page. You can use the session component to keep track of end users. You can use the
user list component to provide access to user names, passwords, and access rights.
The Web application wizard allows you to quickly build an application that is customized with the components that
you will need. The Web page module wizard allows you to create a module that defines a new page in your
application. Or use the Web data module wizard to create a container for components that are shared across your
Web application.
When the Web Page module uses TAdapterPageProducer the page module views become available when this
component is double-clicked. The page module views show the result of server-side scripting without running the
application. You can view the generated HTML 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 topics explain how to use the WebSnap components to create a Web server application:
Fundamental WebSnap components
Creating Web Server Applications
Server-side scripting in WebSnap
Dispatching requests
1333
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.
The following topics describe Web modules in greater detail:
Web application module types
Web page modules
Web data modules
1334
Page
Creates a content page. The page module contains a page producer which is responsible for
generating the content of a page. The page producer displays its associated page when the
HTTP request pathinfo matches the page name. The page can act as the default page when
the pathinfo is blank.
Data
Used as a container for components shared by other modules, such as database components
used by multiple Web page modules.
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.
1335
(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 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
1336
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. 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 the Advanced HTML design topic.
1337
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.
New
2 In the right pane of the New Items window choose WebSnap Application.
1338
For further information about adding application module components, see Specifying Application Module
Components.
Description
Sets up your project as a DLL with the exported methods expected by the Web server.
Apache
Sets up your project as a DLL with the exported methods expected by the appropriate Apache
Web server. Both Apache 1 and 2 are supported.
CGI stand-alone
Sets up your project as a console application which conforms to the Common Gateway
Interface (CGI) standard.
1339
Web App Debugger executable Creates an environment for developing and testing Web server applications. This type of
application is not intended for deployment.
Description
Application Adapter Contains information about the application, such as the title. The default type is TApplicationAdapter.
1340
Contains information about the user, such as their name, access rights, and whether they are logged in.
The default type is TEndUserAdapter. TEndUserSessionAdapter may also be selected.
Page Dispatcher
Examines the HTTP request's pathinfo and calls the appropriate page module to return the content of a
page. The default type is TPageDispatcher.
Adapter Dispatcher Automatically handles HTML form submissions and requests for dynamic images by calling adapter
action and field components. The default type is TAdapterDispatcher.
Dispatch Actions
Allows you to define a collection of action items to handle requests based on pathinfo and method type.
Action items call user-defined events or request the content of page-producer components. The default
type is TWebDispatcher.
Locate File Service Provides control over the loading of template files, and script include files, when the Web application is
running. The default type is TLocateFileService.
Sessions Service
Stores information about end users that is needed for a short period of time. For example, you can use
sessions to keep track of logged-in users and to automatically log a user out after a period of inactivity.
The default type is TSessionsService.
Keeps track of authorized users, their passwords, and their access rights. The default type is
TWebUserList.
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.
For information about modifying application module components, see Selecting Web Application Module Options.
Login Required: Check Login Required to require the user to log on before the page can be accessed.
want to convert (let's call it ModuleName), copy all of the information from the HTML Script tab to the
ModuleName.html tab, replacing all of the information that it contained previously.
Note:
When the Web Page module uses TAdapterPageProducer the page module views become
available when this component is double-clicked.
2 Drop a page producer (located on the Internet category of theTool Palette) onto your Web page module.
3 Set the page producer's ScriptEngine property to match that of the adapter page producer it replaces.
4 Change the page producer in the Web page module from the adapter page producer to the new page producer.
5 The adapter page producer has now been bypassed. You may now delete it from the Web page module.
1342
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. 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.
For additional information on adding login support, refer to Adding Login Support.
1343
If you are adding login support to an existing Web application module, you can drag these components directly into
your module from the WebSnap category of the Tool 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 the topic "User access rights".
For information on login support, see Login Support.
1344
in the background before it receives a page request. Otherwise it will terminate after each page request, and
users will never be able to get past the login page.
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 Websnap 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
New
Other, and
producer. To create a login page, start by creating a new Web page module. Choose File
select WebSnap from the Delphi Projects folder. In the right pane of the New Items window select the WebSnap
Page Module. 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.
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 (accessed by double clicking the AdapterPageProducer) .
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.
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.
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.
1346
To remove the login requirement from a page, reverse the process and recomment the wpLoginRequired portion of
the creator.
Note: You can use the same process to make the page published or not. Simply add or remove comment marks
around the wpPublished portion as needed.
the form page, she sees a series of edit boxes which allow her to change field contents. Using access rights in this
manner can save you from creating extra pages.
The appearance of some page elements, such as TAdapterDisplayField, is determined by its ViewMode property.
If ViewMode is set to vmToggleOnAccess, the page element will appear as an edit box to users with modify access.
Users without modify access will see plain text. Set the ViewMode property to vmToggleOnAccess to allow the page
element's appearance and function to be determined dynamically.
A Web user list is a list of TWebUserListItem objects, one for each user who can login to the system. Permissions
for users are stored in their Web user list item's AccessRights property. AccessRights is a text string, so you are
free to specify permissions any way you like. Create a name for every kind of access right you want in your server
application. If you want a user to have multiple access rights, separate items in the list with a space, semicolon or
comma.
Access rights for fields are controlled by their ViewAccess and ModifyAccess properties. ViewAccess stores the
name of the access rights needed to view a given field. ModifyAccess dictates what access rights are needed to
modify field data. These properties appear in two places: in each field and in the adapter object that contains them.
Checking access rights is a two-step process. When deciding the appearance of a field in a page, the application
first checks the field's own access rights. If the value is an empty string, the application then checks the access rights
for the adapter which contains the field. If the adapter property is empty as well, the application will follow its default
behavior. For modify access, the default behavior is to allow modifications by any user in the Web user list who has
a non-empty AccessRights property. For view access, permission is automatically granted when no view access
rights are specified.
To check permissions before granting access, you need to supply the string for the necessary permission in the sixth
parameter. For example, let's say that the permission is called "Access". This is how you could modify the creator:
TWebPageInfo.Create([wpPublished, wpLoginRequired], '.html', '', '', '', 'Access')
Access to the page will now be denied to anyone who lacks Access permission.
1348
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>
1349
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.
When the template includes script from another page, the script is evaluated by the including page. Use the following
code statement to include the unevaluated content of page1.
<!-- #include page="page1" -- >
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
Script object
Description
Application
EndUser
Provides access to the end user adapter of the Web Application module.
Session
Pages
Modules
Page
1350
Producer
Response
Provides access to the WebResponse. Use this object when tag replacement is not desired.
Request
Adapter objects All of the adapter components on the current page can be referenced without qualification. Adapters in other
modules must be qualified using the Modules objects.
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 %>
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.
1351
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.
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 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. The following figure shows how adapter components are used to generate
content.
1352
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:
Request information found in action requests
Request informaton
Description
Component name
Adapter mode
Defines a mode. For example, TDataSetAdapter supports Edit, Insert, and Browse modes. An
adapter action may execute differently depending on the mode.
Success page
Failure page
Identifies the page displayed if an error occurs during execution of the action.
Action request parameters Identifies the parameters need by the adapter action. For example, the TDataSetAdapter Apply
action will include the key values identifying the record to be updated.
Adapter field values
Specifies values for the adapter fields passed in the HTTP request when an HTML form is
submitted. A field value can include new values entered by the end user, the original values of
the adapter field, and uploaded files.
Record keys
1353
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.
1354
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.
The following figure illustrates how adapter image fields respond to a request.
1355
If the page dispatcher's DefaultPage property contains a page name, the page dispatcher uses this name as the
default page name. If the DefaultPage property is blank and the Web application module is a page module, the page
dispatcher uses the name of the Web application module as the default page name.
If the page name is not blank, the page dispatcher searches for a Web page module with a matching name. If it finds
a Web page module, it calls that module to generate a response. If the page name is blank, or if the page dispatcher
does not find a Web page module, the page dispatcher raises an exception.
The following figure shows how the page dispatcher responds to a request.
1356
Using IntraWeb
Creating Web Server Applications Using IntraWeb
IntraWeb is a tool which simplifies Web server application development. You can use IntraWeb to build Web server
applications exactly the same way you would build traditional GUI applications, using forms. You can write all of your
business logic in the Delphi language; IntraWeb will automatically convert program elements to script or HTML when
necessary.
isn't deployed on a commercial server; instead, IntraWeb's own Application Server is used for application
deployment.
2 Application Mode. The application object is provided by IntraWeb. The application is deployed on a commercial
server.
3 Page mode. The application object is provided by Web Broker or WebSnap. IntraWeb is used to develop pages.
equivalent of CheckBox. (The name used in source code starts with the letter T, of course, like TIWCheckBox.) On
the Tool palette, the icons for IntraWeb components are nearly identical to their VCL counterparts, making it easier
to find the IntraWeb components you need.
The following table lists VCL components and their IntraWeb counterparts. For more information on these
components and how to use them, refer to the IntraWeb help files and other IntraWeb documentation.
VCL and IntraWeb components
VCLcomponent
IntraWeb equivalent
Button
IWButton
IW Standard
CheckBox
IWCheckBox
IW Standard
ComboBox
IWComboBox
IW Standard
DBCheckBox
IWDBCheckBox
IW Data
DBComboBox
IWDBComboBox
IW Data
DBEdit
IWDBEdit
IW Data
DBGrid
IWDBGrid
IW Data
DBImage
IWDBImage
IW Data
DBLabel
IWDBLabel
IW Data
DBListBox
IWDBListBox
IW Data
IWDBLookupListBox
IW Data
DBMemo
IWDBMemo
IW Data
DBNavigator
IWDBNavigator
IW Data
DBText
IWDBText
IW Data
Edit
IWEdit
IW Standard
Image
Label
IWLabel
IW Standard
ListBox
IWListBox
IW Standard
Memo
IWMemo
IW Standard
RadioGroup
IWRadioGroup
IW Standard
Timer
IWTimer
IW Standard
TreeView
IWTreeView
IW Standard
the IWButton component on the IW Standard category. Even the icons for the two different button components look
almost identical. The only difference is an "IW" in the top right corner of the IntraWeb button icon.
Follow the four step tutorial, below, to see how easy it is to build an IntraWeb application. The application you develop
in the tutorial asks the user for some input and displays the input in a popup window. The tutorial uses IntraWeb's
standalone mode, so the application you create will run without a commercial Web server.
directory. This is where the project files will be stored. IntraWeb will set the new project's name to match that of
the directory.
2 Choose File
New
Other, then select the IntraWeb folder under Delphi Projects. The New Items dialog box
appears.
1359
You have just created your IntraWeb application in the Hello directory. All of its source code files have already been
saved. You are now ready to edit the main form to create the Web user interface for your application.
For information about editing the main form, see Editing the Main Form.
Open, then select IWUnit1.pas and click OK. The main form window (named formMain) should
appear in the IDE.
2 Click on the main form window. In the Object Inspector, change the form's Title property to "What is your name?
" This question will appear in the title bar of the Web browser when you run the application and view the page
corresponding to the main form.
3 Drop an IWLabel component (found on the IW Standard tab of the Tool palette) onto the form. In the Object
Inspector, change the Caption property to "What is your name?" That question should now appear on the form.
1360
4 Drop an IWEdit component onto the form underneath the IWLabel component. Use the Object Inspector to
You will now write an event handler that will display a greeting when the user clicks OK.
1 Double-click the OK button on the form. An empty event handler is created in the editor window, like the one
shown here:
procedure TformMain.IWButton1Click(Sender: TObject);
begin
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
1361
For information about running the completed application, see Running the Completed Application.
1362
3 Assume your name is World. Type World in the edit box and click the OK button. A modal dialog box will appear:
When you are finished using your application, you can terminate it by closing the browser window and then closing
the IntraWeb Application Server.
To create Web pages using IntraWeb tools, use the following steps:
1 Create or open a Web Broker or WebSnap application, and drop a WebDispatcher component on your Web
uncheck the New File box in the HTML section before continuing.
Note:
If you create a page module with the New File box checked, you can change the result later.
Open the page module's unit file in the editor. Next, change '.html' to an empty string (") in the
WebRequestHandler.AddWebModuleFactory call at the bottom of the unit.
4 Remove any existing page producer components from your Web module (Web Broker) or Web page module
(WebSnap).
1363
New>
Other
IntraWeb
7 Add an OnGetForm event handler by double-clicking the IWPageProducer component on your Web module or
Web page module. A new method will appear in the editor window.
8 Connect the IntraWeb form to the Web module or Web page module by adding a line of code to your
OnGetForm event handler. The code line should be similar to, if not identical to, the following:
VForm := TformMain.Create(AWebApplication);
If necessary, change TformMain to the name of your IntraWeb form class. To find the form class name, click on
the form. Its name appears next to the form window name in the Object Inspector.
9 In the unit file where you changed the event handler, add IWApplication and IWPageForm to the uses clause.
1364
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).
1365
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.They include
VCL components and interfaces for working with XML documents.
An XML Data Binding wizard for generating classes to represent a particular XML document.
Tools and components for converting between XML documents and data packets, which let you integrate XML
documents into database applications.
1366
You may find it more convenient to use special XML classes rather than working directly with the DOM interfaces.
These are described in:
Working with XML components
Abstracting XML documents with the Data Binding wizard
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
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.
3 Depending on your implementation, you may want to set the ParseOptions property to configure how the
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.
For information on working with the nodes of the XML document, see Working with XML nodes.
1367
<price>15.375</price>
<symbol>BORL</symbol>
<shares>100</shares>
</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.
1368
To create a new element node, specify the name that appears in the new tag and, optionally, the position where the
new node should appear. For example, the following code adds a new stock listing to the document above:
var
NewStock: IXMLNode;
ValueNode: IXMLNode;
begin
NewStock := XMLDocument1.DocumentElement.AddChild('stock');
NewStock.Attributes['exchange'] := 'NASDAQ';
ValueNode := NewStock.AddChild('name');
ValueNode.Text := 'Cisco Systems'
ValueNode := NewStock.AddChild('price');
ValueNode.Text := '62.375';
ValueNode := NewStock.AddChild('symbol');
ValueNode.Text := 'CSCO';
ValueNode := NewStock.AddChild('shares');
ValueNode.Text := '25';
end;
An overloaded version of AddChild lets you specify the namespace URI in which the tag name is defined.
You can delete child nodes using the methods of the ChildNodes property. ChildNodes is an IXMLNodeList interface,
which manages the children of a node. You can use its Delete method to delete a single child node that is identified
by position or by name. For example, the following code deletes the last stock listed in the document above:
StockList := XMLDocument1.DocumentElement;
StockList.ChildNodes.Delete(StockList.ChildNodes.Count - 1);
The Data Binding wizard generates the following interface (along with a class to implement it):
ICustomer = interface(IXMLNode)
['{8CD6A6E8-24FC-426F-9718-455F0C507C8E}']
{ Property Accessors }
function Get_Id: Integer;
function Get_Name: WideString;
function Get_Phone: WideString;
procedure Set_Id(Value: Integer);
procedure Set_Name(Value: WideString);
procedure Set_Phone(Value: WideString);
{ Methods & Properties }
property Id: Integer read Get_Id write Set_Id;
1369
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(Doc: IXMLDocument): IXMLCustomerType;
function Loadcustomers(const FileName: WideString): IXMLCustomerType;
function Newcustomers: IXMLCustomerType;
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;
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 the Adding and
deleting child nodes section of Working with XML Nodes.) 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.
The following topics provide detailed information on using the XML Data Binding wizard:
Using the XML Data Binding wizard
Using code that the XML Data Binding wizard generates
New Other and select the icon labeled XML Data Binding from the right pane of the New folder
located under Delphi Projects.
1370
3 On the first page of the wizard, specify the XML document or schema for which you want to generate interfaces.
This can be a sample XML document, a Document Type Definition (.dtd) file, a Reduced XML Data (.xdr) file, or
an XML schema (.xsd) file.
4 Click the Options button to specify the naming strategies you want the wizard to use when generating interfaces
and implementation classes and the default mapping of types defined in the schema to native Delphi data types.
5 Move to the second page of the wizard. This page lets you provide detailed information about every node type
in the document or schema. At the left is a tree view that shows all of the node types in the document. For complex
nodes (nodes that have children), the tree view can be expanded to display the child elements. When you select
a node in this tree view, the right-hand side of the dialog displays information about that node and lets you specify
how you want the wizard to treat that node.
The Source Name control displays the name of the node type in the XML schema.
The Source Datatype control displays the type of the node's value, as specified in the XML schema.
The Documentation control lets you add comments to the schema describing the use or purpose of the node.
If the wizard generates code for the selected node (that is, if it is a complex type for which the wizard generates
an interface and implementation class, or if it is one of the child elements of a complex type for which the wizard
generates a property on the complex type's interface), you can use the Generate Binding check box to specify
whether you want the wizard to generate code for the node. If you uncheck Generate Binding, the wizard does
not generate the interface or implementation class for a complex type, or does not create a property in the parent
interface for a child element or attribute.
The Binding Options section lets you influence the code that the wizard generates for the selected element. For
any node, you can specify the Identifier Name (the name of the generated interface or property). In addition, for
interfaces, you must indicate which one represents the root node of the document. For nodes that represent
properties, you can specify the type of the property and, if the property is not an interface, whether it is a readonly property.
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.
1371
Method
Description
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
1372
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.
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.
The following topics describe support for working with Web Services in greater detail:
Understanding invokable interfaces
Writing servers that support Web Services
Writing clients for Web Services
1373
Note: An invokable interface can use overloaded methods, but only if the different overloads can be distinguished
by parameter count. That is, one overload must not have the same number of parameters as another,
including the possible number of parameters when default parameters are taken into account.
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
1374
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.
The preceding example uses only scalar types (integers and doubles) in the methods of the interface. You can use
nonscalar types as well, but they require a bit more work.
ByteBool
WordBool
LongBool Char
Byte
ShortInt
SmallInt
Word
Integer
Cardinal
LongInt
Int64
Single
Double
Extended string
WideString
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.
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.
1375
This type is registered automatically when you register the invokable interface. However, if you want to specify the
namespace in which the type is defined or the name of the type, you must add code to explicitly register the type
using the RegisterXSInfo method of the remotable type registry.
The registration goes in the initialization section of the unit where you declare or use the dynamic array:
RemTypeRegistry.RegisterXSInfo(TypeInfo(TDateTimeArray), MyNameSpace, 'DTarray',
'DTarray');
The first parameter of RegisterXSInfo is the type information for the type you are registering. The second parameter
is the namespace URI for the namespace in which the type is defined. If you omit this parameter or supply an empty
string, the registry generates a namespace for you. The third parameter is the name of the type as it appears in
native code. If you omit this parameter or supply an empty string, the registry uses the type name from the type
information you supplied as the first parameter. The final parameter is the name of the type as it appears in WSDL
documents. If you omit this parameter or supply an empty string, the registry uses the native type name (the third
parameter).
Registering a remotable class is similar, except that you supply a class reference rather than a type information
pointer. For example, the following line comes from the XSBuiltIns unit. It registers TXSDateTime, a TRemotable
descendant that represents TDateTime values:
RemClassRegistry.RegisterXSClass(TXSDateTime, XMLSchemaNameSpace, 'dateTime', '',True);
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.
1376
Note: If you do not include a stored directive, or if you assign any other value to the stored directive (even a function
that returns AS_ATTRIBUTE), the property is encoded as a node rather than an attribute.
If the value of your new TRemotable descendant represents a scalar type in a WSDL document, you should use
TRemotableXS as a base class instead. TRemotableXS is a TRemotable descendant that introduces two methods
for converting between your new class and its string representation. Implement these methods by overriding the
XSToNative and NativeToXS methods.
For certain commonly-used XML scalar types, the XSBuiltIns unit already defines and registers remotable classes
for you. These are listed in the following table:
Remotable classes
XML type
remotable class
TXSDate
time
TXSTime
durationtimeDuration TXSDuration
decimal
TXSDecimal
hexBinary
TXSHexBinary
After you define a remotable class, it must be registered with the remotable type registry, as described in Registering
nonscalar types. 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. For an example of defining and registering a remotable class, see Remotable object example.
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.
1377
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;
1378
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);
Use the following steps to build a server application that implements a Web Service:
1 Choose File
New
Other and on the WebServices page, double-click the Soap Server Application icon to
launch the SOAP Server Application wizard. The wizard creates a new Web server application that includes the
components you need to respond to SOAP requests.
2 When you exit the SOAP Server Application wizard, it asks you if you want to define an interface for your Web
Service.
If you are creating a Web Service from scratch, click yes, and you will see the Add New Web Service wizard.
The wizard adds code to declare and register a new invokable interface for your Web Service. Edit the generated
code to define and implement your Web Service. If you want to add additional interfaces (or you want to define
1379
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.
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.
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.
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.
1380
New
The Add New Web Service wizard lets you specify the name of the invokable interface you want to expose to clients,
and generates the code to declare and register the interface and its implementation class. By default, the wizard
also generates comments that show sample methods and additional type definitions, to help you get started in editing
the generated files.
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;
Add a second parameter to this call that specifies the factory procedure:
InvRegistry.RegisterInvokableClass(TEncodeDecode, CreateEncodeDecode);
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.
you may be using an internal, private registry. Select a public registry from the drop-down in the upper left corner,
or type in the address of a private registry you want to use.
The next step is to locate the business from which you want to import a Web Service. Enter the name of the business
in the edit control labeled Name. Other controls let you specify whether the browser must match this name exactly,
or whether you want a case-insensitive search or want to allow a partial match. You can also specify how many
matches you want to fetch (if multiple businesses meet your criteria) and how to sort the results.
Once you have specified the search criteria, click the Find button to locate the business. All of the matches appear
in the tree view in the upper right corner. Use this tree view to drill down, locating the service you want, and the
TModel within that service that corresponds to the Web Service you want to import. As you select items in this tree
view, the lower right portion of the browser provides information about the selected item. When you select a
TModel that represents a Web Service with a WSDL document, the Import button becomes enabled. When you
locate the Web Service you want to import, click the Import button.
1384
You can access the headers your application receives using the ISOAPHeaders interface. There are two ways to
obtain this interface: from an instance of TInvokableClass or, if you are implementing your invokable interface without
using TInvokableClass, by calling the global GetSOAPHeaders function.
Use the Get method of ISOAPHeaders to access the headers by name. For example:
TServiceImpl.GetQuote(Symbol: string): Double;
var
Headers: ISOAPHeaders;
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: ISOAPHeaders;
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;
However, you may want to let other clients know about the headers you use as well. To enable your application to
export information about its header classes, you must register them with the invocation registry. Registering a header
class also associates that class with a header name that is defined within a namespace.
Like the code that registers your invokable interface, the code to register a header class for export is added to the
initialization section of the unit in which it is defined. Use the global InvRegistry function to obtain a reference to the
invocation registry and call its RegisterHeaderClass method, indicating the interface with which the header is
associated:
initialization
InvRegistry.RegisterInterface(TypeInfo(IMyWebService)); {register the interface}
InvRegistry.RegisterHeaderClass(TypeInfo(IMyWebService), TMyHeaderClass); {and the header}
end.
You can limit the header to a subset of the methods on the interface by subsequent calls to the
RegisterHeaderMethod method.
Note: The implementation section's uses clause must include the InvokeRegistry unit so that the call to the
InvRegistry function is defined.
Once you have registered your header class with the invocation registry, its description is added to WSDL documents
when you publish your Web Service.
Note: This registration of your header class with the invocation registry is in addition to the registration of that class
with the remotable type registry.
that clients must use to access the list of WSDL documents. The Web browser can then request the list of WSDL
documents by specifying an URL that is made up of the location of the server application followed by the path in the
WebDispatch property. This URL looks something like the following:
http://www.myco.com/MyService.dll/WSDL
Tip: If you want to use a physical WSDL file instead, you can display the WSDL document in your Web browser
and then save it to generate a WSDL document file.
Note: In addition to the WSDL document, the THWSDLHTMLPublish also generates a WS-Inspection document
to describe the service for automated tools. The URL for this document looks something like the following:
http://www.myco.com/MyService.dll/inspection.wsil
It is not necessary to publish the WSDL document from the same application that implements your Web Service. To
create an application that simply publishes the WSDL document, omit the code that implements and registers the
implementation objects and only include the code that defines and registers invokable interfaces, remotable classes
that represent complex types, and any remotable exceptions.
By default, when you publish a WSDL document, it indicates that the services are available at the same URL as the
one where you published the WSDL document (but with a different path). If you are deploying multiple versions of
your Web Service application, or if you are publishing the WSDL document from a different application than the one
that implements the Web Service, you will need to change the WSDL document so that it includes updated
information on where to locate the Web Service.
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.
same namespace URI and SOAPAction header when it registers invokable interfaces. These values can be explicitly
specified in the code that registers the interfaces, or it can be automatically generated. If it is automatically generated,
the unit that defines the interfaces must have the same name in both client and server, and both client and server
must define the global AppNameSpacePrefix variable to have the same value.
Once you have the definition of the invokable interface, there are two ways you can obtain an instance to call:
If you imported a WSDL document, the importer automatically generates a global function that returns the
interface, which you can then call.
You can use a remote interfaced object.
The generated function takes two parameters: UseWSDL and Addr. UseWSDL indicates whether to look up the
location of the server from a WSDL document (true), or whether the client application supplies the URL for the server
(false).
When UseWSDL is false, Addr is the URL for the Web Service. When UseWSDL is true, Addr is the URL of a WSDL
document that describes the Web Service you are calling. If you supply an empty string, this defaults to the document
you imported. This second approach is best if you expect that the URL for the Web Service may change, or that
details such as the namespace or SOAP Action header may change. Using this second approach, this information
is looked up dynamically at the time your application makes the method call.
Note: The generated function uses an internal remote interfaced object to implement the invokable interface. If you
are using this function and find you need to access that underlying remote interfaced object, you can obtain
an IRIOAccess interface from the invokable interface, and use that to access the remote interfaced object:
var
Interf: IServerInterface;
RIOAccess: IRIOAccess;
X: THTTPRIO;
begin
Intrf := GetIServerInterface(True,
'http://MyServices.org/scripts/AppServer.dll/wsdl');
RIOAccess := Intrf as IRIOAccess;
X := RIOAccess.RIO as THTTPRIO;
1388
Note: It is important that you do not explicitly destroy the THTTPRio instance. If it is created without an Owner (as
in the previous line of code), it automatically frees itself when its interface is released. If it is created with an
Owner, the Owner is responsible for freeing the THTTPRio instance.
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 inmemory 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.
1389
1390
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.
To implement or use a service using sockets, you must understand
service protocols
services and ports
1391
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
1392
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 socketthis 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.
Most Intranets provide host names for the IP addresses of systems on the Internet. You can learn the host name
associated with any IP address (if one already exists) by executing the following command from a command prompt:
nslookup IPADDRESS
where IPADDRESS is the IP address you're interested in. If your local IP address doesn't have a host name and
you decide you want one, contact your network administrator. It is common for computers to refer to themselves
with the name localhost and the IP number 127.0.0.1.
1393
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.
One way to look at port numbers is as numeric codes for the services implemented by network applications. This is
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.
to determine the address and port number used by the local client or server socket, or use the RemoteHost and
RemotePort properties to determine the address and port number used by the remote client or server socket. Use
the GetSocketAddr method to build a valid socket address based on the host name and port number. You can use
the LookupPort method to look up the port number. Use the LookupProtocol method to look up the protocol number.
Use the LookupHostName method to look up the host name based on the host machine's IP address.
To view network traffic in and out of the socket, use the BytesSent and BytesReceived properties.
1395
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.
1396
When TCP clients shut down their individual connections to your server socket, you are informed by an OnDisconnect
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.
1397
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.
1398
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.
1399
1400
COM basics
Overview of COM Technologies
Delphi provides wizards and classes to make it easy to implement applications based on the Component Object
Model (COM) from Microsoft. With these wizards, you can create COM-based classes and components to use within
applications or you can create fully functional COM clients or servers that implement COM objects, Automation
servers (including Active Server Objects), ActiveX controls, or ActiveForms.
COM is a language-independent software component model that enables interaction between software components
and applications running on a Windows platform. The key aspect of COM is that it enables communication between
components, between applications, and between clients and servers through clearly defined interfaces. Interfaces
provide a way for clients to ask a COM component which features it supports at runtime. To provide additional
features for your component, you simply add an additional interface for those features.
Applications can access the interfaces of COM components that exist on the same computer as the application or
that exist on another computer on the network using a mechanism called Distributed COM (DCOM). For more
information on clients, servers, and interfaces see Parts of a COM Application.
1401
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, and Active Directories. For details on COM extensions, see
COM Extensions.
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 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.
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 theTool 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 as follows:
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.
Key aspects of COM interfaces are as follows:
Once published, interfaces are immutable; that is, they do not change. You can rely on an interface to provide
a specific set of functions. Additional functionality is provided by additional interfaces.
By convention, COM interface identifiers begin with a capital I and a symbolic name that defines the interface,
such as IMalloc or IPersist.
1402
Interfaces are guaranteed to have a unique identification, called a Globally Unique Identifier (GUID), which
is a 128-bit randomly generated number. Interface GUIDs are called Interface Identifiers (IIDs). This eliminates
naming conflicts between different versions of a product or different products.
Interfaces are language independent. You can use any language to implement a COM interface as long as the
language supports a structure of pointers, and can call a function through a pointer either explicitly or implicitly.
Interfaces are not objects themselves; they provide a way to access an object. Therefore, clients do not access
data directly; clients access data through an interface pointer. Windows 2000 adds an additional layer of
indirection known as an interceptor through which it provides COM+ features such as just-in-time activation and
object pooling.
Interfaces are always inherited from the fundamental interface, IUnknown.
Interfaces can be redirected by COM through proxies to enable interface method calls to call between threads,
processes, and networked machines, all without the client or server objects ever being aware of the redirection.
For more information, see In-process, out-of-process, and remote servers.
AddRef and Release Simple reference counting methods that keep track of the object's lifetime so that an object can delete
itself when the client no longer needs its service.
Clients obtain pointers to other interfaces through the IUnknown method, QueryInterface. QueryInterface knows
about every interface in the server object and can give a client a pointer to the requested interface. When receiving
a pointer to an interface, the client is assured that it can call any method of the interface.
Objects track their own lifetime through the IUnknown methods, AddRef and Release, which are simple reference
counting methods. As long as an object's reference count is nonzero, the object remains in memory. Once the
reference count reaches zero, the interface implementation can safely dispose of the underlying object(s).
1403
In Windows 2000 and subsequent versions of Windows, when an object is running under COM+, an added level of
indirection is provided between the interface pointer and the vtable pointer. The interface pointer available to the
client points at an interceptor, which in turn points at the vtable. This allows COM+ to provide such services as justin-time activation, whereby the server can be deactivated and reactivated dynamically in a way that is opaque to the
client. To achieve this, COM+ guarantees that the interceptor behaves as if it were an ordinary vtable pointer.
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.
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.
requests the object's services, the class factory creates another object instance to service the second client. (Clients
can also bind to running COM objects that register themselves to support it.)
A CoClass must have a class factory and a class identifier (CLSID) so that it can be instantiated externally, that is,
from another module. Using these unique identifiers for CoClasses means that they can be updated whenever new
interfaces are implemented in their class. A new interface can modify or add methods without affecting older versions,
which is a common problem when using DLLs.
Delphi wizards take care of assigning class identifiers and of implementing and instantiating class factories.
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 (or local server) Another application (EXE) running in a different process space 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 the following figure, 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.
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.
As shown in the following figure, when the process is either in a different process or in a different machine altogether,
COM uses a proxy to initiate remote procedure calls. The proxy resides in the same process as the client, so from
the client's perspective, all interface calls look alike. The proxy intercepts the client's call and forwards it to where
1405
the real object is running. The mechanism that enables the client to access objects in a different process space, or
even different machine, as if they were in their own process, is called marshaling.
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).
1406
interface, see Automation Interfaces. Even if the object does not implement IDispatch, if it limits itself to automationcompatible 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.
Automation Servers
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 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.
1407
Automation servers
ActiveX controls
Active Server Pages are scripts that generate HTML pages. The scripting
language includes constructs for creating and running Automation objects. That
is, the Active Server Page acts as an Automation controller.
Active Documents
Objects that support linking and embedding, drag-and-drop, visual editing, and
in-place activation. Word documents and Excel spreadsheets are examples of
Active Documents.
Transactional objects
COM+ Event and event subscription objects Objects that support the loosely coupled COM+ Events model. Unlike the tightly
coupled model used by ActiveX controls, the COM+ Events model allows you
to develop event publishers independently of event subscribers.
Type libraries
The following diagram illustrates the relationship of the COM extensions and how they are built upon COM:
1408
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. The following table
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.
COM object requirements
Object
Communication
Active Document
Usually
Automation Server
Occasionally
Automatically marshaled
Required for automatic
using the IDispatch interface marshaling
(for out-of process and
remote servers)
ActiveX Control
Usually
In-process
Automatically marshaled
Required
using the IDispatch interface
MTS or COM+
Occasionally
In-process for
MTS,any for COM+
Required
In-process custom
interface object
Optionally
In-process
Recommended
1409
Type library
No
Optionally
In-process,out-ofprocess, or remote
Recommended
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.
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
1410
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.
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.
1411
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.
1412
type library is saved. For any changes to Interfaces and CoClasses that were created using a wizard, the Type
Library editor also updates your implementation files.
Description
ITypeLib
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.
Type browsers can scan the library, so clients can see the characteristics of your objects.
The RegisterTypeLib function can be used to register your exposed objects in the registration database.
The UnRegisterTypeLib function can be used to completely uninstall an application's type library from the system
registry.
Local server access is improved because Automation uses information from the type library to package the
parameters that are passed to an object in another process.
The COM object wizard also provides an implementation for IDispatch if you specify that you are creating an object
that supports an IDispatch descendant.
For Automation and Active Server objects, the wizard implements IUnknown and IDispatch, which provides
automatic marshaling.
1414
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.
The following table lists the various wizards and the interfaces they implement:
Delphi wizards for implementing COM, Automation, and ActiveX objects
Wizard
Implemented interfaces
COM server
Automation server
IUnknown, IDispatch
1415
Type Library
None, by default
ActiveX library
None, by default
You can add additional COM objects or reimplement an existing implementation. To add a new object, it is easiest
to use the wizard a second time. This is because the wizard sets up an association between the type library and an
implementation class, so that changes you make in the type library editor are automatically applied to your
implementation object.
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:
DAX Base classes for generated implementation classes
Wizard
COM server
TTypedComObject
1416
TAutoObject
Corresponding to the classes in DAX 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.
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.
1417
1418
File
Description
.TLB file
The binary type library file. By default, you do not need to use this file, because the type library is automatically
compiled into the application as a resource. However, you can use this file to explicitly compile the type library into
another project or to deploy the type library separately from the .exe or .ocx. For more information, see Opening
an existing type library and Deploying type libraries.
_TLB unit This unit reflects the contents of the type library for use by your application. It contains all the declarations your
application needs to use the elements defined in the type library. Although you can open this file in the code editor,
you should never edit itit is maintained by the Type Library Editor, so any changes you make will be overwritten
by the Type Library Editor. For more details on the contents of this file, see Code generated when you import
type library information
The following topics describe the Type Library Editor in greater detail:
Parts of the Type Library editor
Using the Type Library editor
Description
Toolbar
Includes buttons to add new types, CoClasses, interfaces, and interface members to your type library. The
toolbar also includes buttons for refreshing your implementation unit, registering the type library, and saving
an IDL file with the information in your type library.
Object list pane Displays all the existing elements in the type library. When you click on an item in the object list pane, it
displays pages valid for that object.
Status bar
Displays syntax errors if you try to add invalid types to your type library.
Pages
Display information about the selected object. Which pages appear here depends on the type of object
selected.
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 dispinterface description.
A CoClass.
1419
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 method of the interface, dispinterface, or an entry point in a module.
A property on an interface or dispinterface.
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)
A field in a record or union.
A constant in an enum or a module.
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.
Descending from the type library node are the elements in the type library:
1420
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.
Type library
Interface
Dispinterface
Attributes
Name, version, and GUID for the type library, as well as information linking the
type library to help.
Uses
List of other type libraries that contain definitions on which this one depends.
Flags
Flags that determine how other applications can use the type library.
Text
All definitions and declarations defining the type library itself (see discussion
below).
Attributes
Name, version, and GUID for the interface, the name of the interface from which
it descends, and information linking the interface to help.
Flags
Flags that indicate whether the interface is hidden, dual, Automationcompatible, and/or extensible.
Text
The definitions and declarations for the Interface (see discussion below).
Attributes
Name, version, and GUID for the interface, and information linking it to help.
Flags
Flags that indicate whether the Dispinterface is hidden, dual, and/or extensible.
Text
The definitions and declarations for the Dispinterface. (see discussion below).
1421
CoClass
Enumeration
Alias
Record
Union
Module
Method
Property
Const
Field
Attributes
Name, version, and GUID for the CoClass, and information linking it to help.
Implements
COM+
Flags
Flags that indicate various attributes of the CoClass, including how clients can
create and use instances, whether it is visible to users in a browser, whether it
is an ActiveX control, and whether it can be aggregated (act as part of a
composite).
Text
The definitions and declarations for the CoClass (see discussion below).
Attributes
Name, version, and GUID for the enumeration, and information linking it to help.
Text
The definitions and declarations for the enumerated type (see discussion
below).
Attributes
Name, version, and GUID for the enumeration, the type the alias represents,
and information linking it to help.
Text
The definitions and declarations for the alias (see discussion below).
Attributes
Name, version, and GUID for the record, and information linking it to help.
Text
The definitions and declarations for the record (see discussion below).
Attributes
Name, version, and GUID for the union, and information linking it to help.
Text
The definitions and declarations for the union (see discussion below).
Attributes
Name, version, GUID, and associated DLL for the module, and information
linking it to help.
Text
The definitions and declarations for the module (see discussion below).
Attributes
Parameters
Method return type, and a list of all parameters with their types and any
modifiers.
Flags
Flags to indicate how clients can view and use the method, whether this is a
default method for the interface, and whether it is replaceable.
Text
The definitions and declarations for the method (see discussion below).
Attributes
Name, dispatch ID, type of property access method (getter vs. setter), and
information linking it to help.
Parameters
Property access method return type, and a list of all parameters with their types
and any modifiers.
Flags
Flags to indicate how clients can view and use the property, whether this is a
default for the interface, whether the property is replaceable, bindable, and so
on.
Text
The definitions and declarations for the property access method (see
discussion below).
Attributes
Name, value, type (for module consts), and information linking it to help.
Flags
Flags to indicate how clients can view and use the constant, whether this
represents a default value, whether the constant is bindable, and so on.
Text
The definitions and declarations for the constant (see discussion below).
Attributes
1422
Flags
Flags to indicate how clients can view and use the field, whether this represents
a default value, whether the field is bindable, and so on.
Text
The definitions and declarations for the field (see discussion below).
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. See Using Delphi or IDL syntax for details. 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.
Special Icons for 'get' and 'set' Methods
A write (set, put) by value property function.
A read (get) |write (set, put)|write by reference property function.
1423
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.
1424
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.
Delphi type IDL type
variant type
Smallint
short
VT_I2
Yes
Integer
long
VT_I4
Yes
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
SCODE
SCODE
VT_ERROR
Yes
WordBool
VARIANT_BOOL VT_BOOL
Yes
OleVariant
VARIANT
VT_VARIANT
Yes
Ole Variant
IUnknown
IUnknown
VT_UNKNOWN
Yes
Shortint
byte
VT_I1
No
Byte
unsigned char
VT_UI1
Yes
Word
unsigned short
VT_UI2
Yes*
LongWord
unsigned long
VT_UI4
Yes*
Int64
__int64
VT_I8
No
Largeuint
uint64
VT_UI8
No
SYSINT
int
VT_INT
Yes*
SYSUINT
unsigned int
VT_UINT
Yes*
HResult
HRESULT
VT_HRESULT
No
Pointer
SafeArray
SAFEARRAY
untyped pointer
VT_SAFEARRAY
No
1426
PChar
LPSTR
PWideChar LPWSTR
VT_LPSTR
No
pointer to Char
VT_LPWSTR
No
pointer to WideChar
* Word, LongWord, SYSINT, and SYSUINT may be Automation-compatible with some applications.
See safe arrays for more information about the SAFEARRAY Variant type.
Note: The Byte (VT_UI1) is Automation-compatible, but is not allowed in a Variant or OleVariant since many
Automation servers do not handle this value correctly.
Besides these IDL types, any interfaces and types defined in the library or defined in referenced libraries can be
used in a type library definition.
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 automationcompatible 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.
1427
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.
Attribute syntax
Attribute name
Example
Applies to
aggregatable
[aggregatable]
typeinfo
appobject
[appobject]
CoClass typeinfo
bindable
[bindable]
control
[control]
custom
[custom '{7B5687A1-F4E9-11D1-92A800C04F8C8FC4}' 0]
anything
default
[default]
CoClass members
defaultbind
[defaultbind]
defaultcollection
[defaultcollection]
defaultvtbl
[defaultvtbl]
CoClass members
dispid
[dispid]
displaybind
[displaybind]
dllname
[dllname 'Helper.dll']
module typeinfo
dual
[dual]
interface typeinfo
helpfile
[helpfile 'c:\help\myhelp.hlp']
type library
helpstringdll
[helpstringdll 'c:\help\myhelp.dll']
type library
helpcontext
[helpcontext 2005]
helpstring
hidden
[hidden]
immediatebind
[immediatebind]
lcid
[lcid $324]
type library
licensed
[licensed]
nonbrowsable
[nonbrowsable]
nonextensible
[nonextensible]
interface typeinfo
oleautomation
[oleautomation]
interface typeinfo
predeclid
[predeclid]
typeinfo
propget
[propget]
propput
[propput]
propputref
[propputref]
1428
public
[public]
alias typeinfo
readonly
[readonly]
replaceable
[replaceable]
requestedit
[requestedit]
restricted
[restricted]
source
[source]
all members
uidefault
[uidefault]
usesgetlasterror
[usesgetlasterror]
uuid
vararg
[vararg]
version
[version 1.1]
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;
1429
For example, the following text declares a dispinterface with the same methods and property as the previous
interface:
MyDispObj = dispinterface
[uuid '{5FD36EEF-70E5-11D1-AA62-00C04FB16F42}',
version 1.0,
helpstring 'dispatch interface for MyObj'
function Calculate(seed:Integer): Integer [dispid 1];
procedure Reset [dispid 2];
property Range: Integer [dispid 3];
end;
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]
1430
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'];);
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}'];
Record syntax
The Delphi syntax for declaring Record type information has the form
recordname = record [attributes] fieldlist end;
1431
Union syntax
The Delphi syntax for declaring Union type information has the form
unionname = record [attributes]
case Integer of
0: field1;
1: field2;
...
end;
1432
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;
New
1433
2 In the Open dialog box, set the File Type to type library.
3 Navigate to the desired type library files and choose Open.
Type Library.
Now, you can add interfaces, CoClasses, and other elements of the type library such as enumerations, properties,
and methods.
Note: Changes you make to any type library information with the Type Library Editor can be automatically reflected
in the associated implementation class. If you want to review the changes before they are added, be sure
that the Apply Updates dialog is on. It is on by default and can be changed in the setting, "Display updates
before refreshing," on theTools
Options
Delphi Options
Type Library page.
Tip: When writing client applications, you do not need to open the type library. You only need the Project_TLB unit
that the Type Library Editor creates from a type library, not the type library itself. You can add this file directly
to a client project, or, if the type library is registered on your system, you can use the Import Type Library dialog
Import Type Library).
(Component
An interface is added to the object list pane prompting you to add a name.
2 Type a name for the interface.
The new interface contains default attributes that you can modify as needed.
You can add properties (represented by getter/setter functions) and methods to suit the purpose of the interface.
Updates dialog enabled, the Type Library Editor notifies you before updating the sources and warns you of potential
problems. For example, if you rename an event interface by mistake, you may get a warning in your source file that
looks like this:
Because of the presence of instance variables in your implementation file,
Delphi was not able to update the file to reflect the change in your event
interface name. As Delphi has updated the type library for you, however, you
must update the implementation file by hand.
You also get a TODO comment in your source file immediately above it.
Warning: If you ignore this warning and TODO comment, the code will not compile.
you can click directly on the property icon to create a read/write property (with both a getter and a setter), or click
the down arrow to display a menu of property types.
The property access method members or method member is added to the object list pane, prompting you to add
a name.
2 Type a name for the member.
The new member contains default settings on its attributes, parameters, and flags pages that you can modify to suit
the member. For example, you will probably want to assign a type to a property on the attributes page. If you are
adding a method, you will probably want to specify its parameters on the parameters page.
As an alternate approach, you can add properties and methods by typing directly into the text page using Delphi or
IDL syntax. For example, if you are working in Delphi syntax, you can type the following property declarations into
the text page of an interface:
Interface1 = interface(IDispatch)
[ uuid '{5FD36EEF-70E5-11D1-AA62-00C04FB16F42}',
version 1.0,
dual,
oleautomation ]
function AutoSelect: Integer [propget, dispid $00000002]; safecall; // Add this
function AutoSize: WordBool [propget, dispid $00000001]; safecall; // And this
procedure AutoSize(Value: WordBool) [propput, dispid $00000001]; safecall; // And this
end;
If you are working in IDL, you can add the same declarations as follows:
[
uuid(5FD36EEF-70E5-11D1-AA62-00C04FB16F42),
version(1.0),
dual,
oleautomation
]
interface Interface1: IDispatch
{ // Add everything between the curly braces
[propget, id(0x00000002)]
1435
After you have added members to an interface using the interface text page, the members appear as separate items
in the object list pane, each with its own attributes, flags, and parameters pages. You can modify each new property
or method by selecting it in the object list pane and using these pages, or by making edits directly in the text page.
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.
1436
The new enum is empty and contains default attributes in its attributes page for you to modify.
Add values to the enum by right clicking the enum and selecting the New
Const button. Then, select each
enumerated value and assign it a name (and possibly a value) using the attributes page.
Once you have added an enumeration, the new type is available for use by the type library or any other type library
that references it from its uses page. For example, you can use the enumeration as the type for a property or
parameter.
By default, the new alias stands for a Long Integer type. Use the Attributes page to change this to the type you
want the alias to represent.
Once you have added an alias, the new type is available for use by the type library or any other type library that
references it from its uses page. For example, you can use the alias as the type for a property or parameter.
Once you have defined the record or union, the new type is available for use by the type library or any other type
library that references it from its uses page. For example, you can use the record or union as the type for a property
or parameter.
1437
1438
You will also get a TODO comment in your source file immediately above it.
Note: If you ignore this warning and TODO comment, the code will not compile.
1440
The dialog lists all the libraries registered on this system. If the type library is not in the list, choose the Add button,
find and select the type library file, choose OK. This registers the type library, making it available. Then repeat
step 2. Note that the type library could be a stand-alone type library file (.tlb, .olb), or a server that provides a
type library (.dll, .ocx, .exe).
3 If you want to generate a VCL component that wraps a CoClass in the type library, check Generate Component
Wrapper. If you do not generate the component, you can still use the CoClass by using the definitions in the
TypeLibName_TLB unit. However, you will have to write your own calls to create the server object and, if
necessary, to set up an event sink.
1442
The Import Type Library dialog only imports CoClasses that are have the CanCreate flag set and that do not
have the Hidden, Restricted, or PreDeclID flags set. These flags can be overridden using the command-line utility
tlibimp.exe.
4 If you do not want to install a generated component wrapper on theTool Palette, choose Create Unit. This
generates the TypeLibName_TLB unit and, if you checked Generate Component Wrapper in step 3, adds the
declaration of the component wrapper. This exits the Import Type Library dialog.
5 If you want to install the generated component wrapper on theTool Palette, select the Palette page on which
this component will reside and then choose Install. This generates the TypeLibName_TLB unit, like the Create
Unit button, and then displays the Install component dialog, letting you specify the package where the
components should reside (either an existing package or a new one). This button is grayed out if no component
can be created for the type library.
When you exit the Import Type Library dialog, the new TypeLibName_TLB unit appears in the directory specified by
the Unit dir name control. This file contains declarations for the elements defined in the type library, as well as the
generated component wrapper if you checked Generate Component Wrapper.
In addition, if you installed the generated component wrapper, a server object that the type library described now
resides on the Tool 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).
The dialog lists all the registered libraries that define ActiveX controls. (This is a subset of the libraries listed in
the Import Type Library dialog.) If the type library is not in the list, choose the Add button, find and select the type
library file, choose OK. This registers the type library, making it available. Then repeat step 2. Note that the type
library could be a stand-alone type library file (.tlb, .olb), or an ActiveX server (.dll, .ocx).
3 If you do not want to install the ActiveX control on theTool Palette, choose Create Unit. This generates the
TypeLibName_TLB unit and adds the declaration of its component wrapper. This exits the Import ActiveX dialog.
4 If you want to install the ActiveX control on theTool Palette, select the Palette page on which this component
will reside and then choose Install. This generates the TypeLibName_TLB unit, like the Create Unit button, and
then displays the Install component dialog, letting you specify the package where the components should reside
(either an existing package or a new one).
When you exit the Import ActiveX dialog, the new TypeLibName_TLB unit appears in the directory specified by the
Unit dir name control. This file contains declarations for the elements defined in the type library, as well as the
generated component wrapper for the ActiveX control.
Note: Unlike the Import Type Library dialog where it is optional, the import ActiveX dialog always generates a
component wrapper. This is because, as a visual control, an ActiveX control needs the additional support of
the component wrapper so that it can fit in with VCL forms.
If you installed the generated component wrapper, an ActiveX control now resides on theTool Palette. You can use
the Object Inspector to set properties or write event handlers for this control. If you add the control to a form or data
module, you can right-click on it at design time to see its property page (if it supports one).
1443
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 TOleControl for
information on how to host such ActiveX controls.
Handling events is easy, because you can use the Object Inspector to write event handlers. Note, however, that
the event handler on your component may have slightly different parameters than those defined for the event in the
1445
type library. Specifically, pointer types (var parameters and interface pointers) are changed to Variants. You must
explicitly cast var parameters to the underlying type before assigning a value. Interface pointers can be cast to the
appropriate interface type using the as operator.
For example, the following code shows an event handler for the ExcelApplication event, OnNewWorkBook. The
event handler has a parameter that provides the interface of another CoClass (ExcelWorkbook). However, the
interface is not passed as an ExcelWorkbook interface pointer, but rather as an OleVariant.
procedure TForm1.XLappNewWorkbook(Sender: TObject; var Wb:OleVariant);
begin
{ Note how the OleVariant for the interface must be cast to the correct type }
ExcelWorkbook1.ConnectTo((iUnknown(wb) as ExcelWorkbook));
end;
In this example, the event handler assigns the workbook to an ExcelWorkbook component (ExcelWorkbook1). This
demonstrates how to connect a component wrapper to an existing interface by using the ConnectTo method. The
ConnectTo method is added to the generated code for the component wrapper.
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.
For an example of importing an using a component wrapper for an Automation object, see Printing a document with
Microsoft Word.
Field Name lists the fields of the database and Property Name lists the ActiveX control properties that can be
bound to a database field. The dispID of the property is in parentheses, for example, Value(12).
2 Click Bind and OK.
Note: If no properties appear in the dialog, the ActiveX control contains no data-aware properties. To enable simple
data binding for a property of an ActiveX control, use the type library.
The following example walks you through the steps of using a data-aware ActiveX control in the Delphi container.
1446
This example uses the Microsoft Calendar Control, which is available if you have Microsoft
Office 97 installed on your system.
1 From the Delphi main menu, choose Component|Import ActiveX Control.
Select a data-aware ActiveX control, such as the Microsoft Calendar control 8.0, change its class name to
TCalendarAXControl, and click Install.
2 In the Install dialog, click OK to add the control to the default user package, which makes the control available
on the Palette.
Choose Close All and File
New
3 From the ActiveX tab, drop a TCalendarAXControl object, which you just added to the Palette, onto the form.
4 Drop a DataSource object from the Data Access tab, and a Table object from the BDE tab onto the form.
Select the DataSource object and set its DataSet property to Table1.
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.
5 Select the TCalendarAXControl object and set its DataSource property to DataSource1.
6 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.
7 Select the HireDate field and the Value property name, choose Bind, and OK.
From the Data Controls tab, drop a DBNavigator object onto the form and set its DataSource property to
DataSource1.
9 Run the application.
1447
The Servers category of the Tool Palette no longer contains any of the servers supplied with Delphi. (If no other
servers have been imported, the Servers category also disappears.)
2 In the Import Type Library dialog, select Microsoft Office 8.0 Object Library.
If Word (Version 8) is not in the list, choose the Add button, go to Program Files\Microsoft Office\Office, select
the Word type library file, MSWord8.olb choose Add, and then select Word (Version 8) from the list.
3 For Tool Palette, choose Servers.
4 Choose Install.
The Install dialog appears. Select the Into New Packages tab and type WordExample to create a new package
containing this type library.
5 Go to the Servers Category, select WordApplication and place it on a form.
6 Write an event handler for the button object as described in the next step.
1448
You simply call on methods of the class you just created. For Word, this is the
TWordApplication class.
1 Select the button, double-click its OnClick event handler and supply the following event handling code:
procedure TForm1.Button1Click(Sender: TObject);
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.
To create and initialize the Application object using the _ApplicationDisp dispatch
wrapper class
1 Select the button, double-click its OnClick event handler and supply the following event handling code:
procedure TForm1.Button1Click(Sender: TObject);
var
MyWord : _ApplicationDisp;
FileName : OleVariant;
begin
if OpenDialog1.Execute then
begin
FileName := OpenDialog1.FileName;
MyWord := CoWordApplication.Create as
_ApplicationDisp;
(MyWord.Documents as DocumentsDisp).Open(FileName,EmptyParam,
EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,
1449
EmptyParam,EmptyParam,EmptyParam);
(MyWord.ActiveDocument as _DocumentDisp).PrintOut(EmptyParam,
EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam);
MyWord.Quit(EmptyParam,EmptyParam,EmptyParam);
end;
end;
2 Build and run the program. By clicking the button, Word prompts you for a file to print.
Choose Component
Install Packages.
From the list, select the WordExample package and click remove.
Click Yes to the message box asking for confirmation.
Exit the Install Packages dialog by clicking OK.
2 Return the Microsoft Office Automation Server Wrapper Components package:
1450
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.
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.
The interface and creator class are defined in the TypeLibName_TLB unit that is generated automatically when you
import a type library.
Another way to use dispatch interfaces is to assign them to a Variant. By assigning the interface returned by
CreateOleObject to a Variant, you can take advantage of the Variant type's built-in support for interfaces. Simply
call the methods of the interface, and the Variant automatically handles all IDispatch calls, fetching the dispatch
ID and invoking the appropriate method. The Variant type includes built-in support for calling dispatch interfaces,
through its var.
V: Variant;
begin
1451
V:= CreateOleObject("TheServerObject");
V.MethodName; { calls the specified method }
...
An advantage of using Variants is that you do not need to import the type library, because Variants use only the
standard IDispatch methods to call the server. The trade-off is that Variants are slower, because they use dynamic
binding at runtime.
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):
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.
1452
To use TOleContainer
1 Place a TOleContainer component on your form.
2 Set the AllowActiveDoc property to true if you want to host an Active document.
3 Set the AllowInPlace property to indicate whether the hosted object should appear in the TOleContainer, or in a
separate window.
4 Write event handlers to respond when the object is activated, deactivated, moved, or resized.
5 To bind the TOleContainer object at design time, right click and choose Insert Object. In the Insert Object dialog,
want to identify the server object. These include CreateObject, which takes a program id, CreateObjectFromFile,
which takes the name of a file to which the object has been saved, CreateObjectFromInfo, which takes a record
containing information on how to create the object, or CreateLinkToFile, which takes the name of a file to which
the object was saved and links to it rather than embeds it.
1453
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.
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.
1455
1456
using System.Runtime.InteropServices;
using System.Windows.Forms;
[assembly:AssemblyKeyFile("KeyFile.snk")]
namespace InteropTest1 {
public interface MyInterface {
void myMethod1();
void myMethod2(string msg);
}
// Restrict clients to using only implemented interfaces.
[ClassInterface(ClassInterfaceType.None)]
public class MyClass : MyInterface {
// The class must have a parameterless constructor for COM interoperability
public MyClass() {
}
// Implement MyInterface methods
public void myMethod1() {
MessageBox.Show("In C# Method!");
}
public void myMethod2(string msg) {
MessageBox.Show(msg);
}
}
}
The assembly is marked with the AssemblyKeyFile attribute. This is required if the component is to be deployed
in the Global Assembly Cache. If you deploy your component in the same directory as the unmanaged executable
client, the strong key is not required. This example component will be deployed in the GAC, so we first generate the
keyfile using the Strong Name Utility of the .NET Framework SDK:
sn -k KeyFile.snk
Execute this command from the same directory where the C# source file is located.
The next step is to compile this code using the C# compiler. Assuming the C# code is in a file called interoptest1.cs:
csc /t:library interoptest1.cs
The result of this command is the creation of an assembly called interoptest1.dll. The assembly must now be
registered, using the regasm utility. Regasm is similar in concept to tregsvr; it creates entries in the Windows registry
that allow the component to be exposed to unmanaged COM clients.
regasm /tlb interoptest1.dll
The use of the /tlb option causes regasm to do two things: First, the registry entries for the assembly are created.
Second, the types in the assembly will be exported to a type library, and the type library will also be registered.
Finally, the component is deployed to the GAC using the gacutil command:
gacutil -i interoptest1.dll
The -i option indicates the assembly is being installed into the GAC. The gacutil command must be executed each
time you build a new version of the .NET component. Later, if you wish to remove the component from the GAC,
execute the gacutil command again, this time with the -u option:
1457
gacutil -u interoptest1
Note: When uninstalling a component, do not include the '.dll' extension on the assembly name.
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 Component 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 Tool
Palette by selecting the Install check box. 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;
1458
1459
New
1460
Instancing: Unless you are creating an in-process server, you need to indicate how COM launches the
application that houses your COM object. If your application implements more than one COM object, you should
specify the same instancing for all of them.
Threading Model: Typically, client requests to your object enter on different threads of execution. You can specify
how COM serializes these threads when it calls your object. Your choice of threading model determines how
the object is registered. You are responsible for providing any threading support implied by the model you
choose. For information on how to provide thread support to your application, see Writing multi-threaded
applications
Include Type Library: You can choose whether you want to include a type library for your object. This is
recommended for two reasons: it lets you use the Type Library editor to define interfaces, thereby updating
much of the implementation, and it gives clients an easy way to obtain information about your object and its
interfaces. If you are implementing an existing interface, Delphi requires your project to use a type library. This
is the only way to provide access to the original interface declaration.
Mark interfaceOleautomation: If you have opted to create a type library and are willing to confine yourself to
Automation-compatible types, you can let COM handle the marshaling for you when you are not generating an
in-process server. By marking your object's interface as OleAutomation in the type library, you enable COM to
set up the proxies and stubs for you and handles passing parameters across process boundaries. You can only
specify whether your interface is Automation-compatible if you are generating a new interface. If you select an
existing interface, its attributes are already specified in its type library. If your object's interface is not marked
as OleAutomation, you must either create an in-process server or write your own marshaling code.
You can optionally add a description of your COM object. This description appears in the type library for your object
if you create one.
New
CoClass name: This is the name of the object as it appears to clients. Your object's default interface is created
with a name based on this CoClass name with an 'I' prepended, and the class created to implement your object
has this name with a 'T' prepended.
Instancing: Unless you are creating an in-process server, you need to indicate how COM launches the
application that houses your COM object. If your application implements more than one COM object, you should
specify the same instancing for all of them.
Threading Model: Typically, client requests to your object enter on different threads of execution. You can specify
how COM serializes these threads when it calls your object. Your choice of threading model determines how
1461
the object is registered. You are responsible for providing any threading support implied by the model you
choose. For information on how to provide thread support to your application, see Writing multi-threaded
applications.
Generate Event support code: You must indicate whether you want your object to generate events to which
clients can respond. The wizard can provide support for the interfaces required to generate events and the
dispatching of calls to client event handlers.
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.
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.
Description
Single
The server provides no thread support. Clients are handled one at a time so no
COM serializes client requests so that threading support is needed.
the application receives one request at
No performance benefit.
a time.
1462
Both
Neutral
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 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.
1463
Free-threaded in-process servers can improve performance by acting as the outer object in an aggregation with the
free-threaded marshaler. The free-threaded marshaler provides a shortcut for COM's standard thread handling when
a free-threaded DLL is called by a host (client) that is not free-threaded.
To aggregate with the free threaded marshaler, you must
Call CoCreateFreeThreadedMarshaler, passing your object's IUnknown interface for the resulting free-threaded
marshaler to use: CoCreateFreeThreadedMarshaler(self as IUnknown, FMarshaler); This line
assigns the interface for the free-threaded marshaler to a class member, FMarshaler.
Using the Type Library Editor, add the IMarshal interface to the set of interfaces your CoClass implements.
In your object's QueryInterface method, delegate calls for IDD_IMarshal to the free-threaded marshaler (stored
as FMarshaler above).
Warning: The free-threaded marshaler violates the normal rules of COM marshaling to provide additional efficiency.
It should be used with care. In particular, it should only be aggregated with free-threaded objects in an
in-process server, and should only be instantiated by the object that uses it (not another thread).
can do. It also lets you define your object's interface using the Type Library editor. The interfaces you define in the
Type Library editor define what properties, methods, and events your object exposes to clients.
Note: If you selected an existing interface in the COM object wizard, you do not need to add properties and methods.
The definition of the interface is imported from the type library in which it was defined. Instead, simply locate
the methods of the imported interface in the implementation unit and fill in their bodies.
The default interface should be the name of the object preceded by the letter "I." To determine the default, in the
Type Library Editor, click the CoClass and then select the Implements tab, and check the list of implemented
interfaces for the one marked, "Default."
2 To expose a read/write property, click the New Property button on the toolbar; otherwise, click the arrow next
to the New Property button on the toolbar, and then click the type of property to expose.
3 In the Attributes pane, specify the name and type of the property.
4 On the Type Library Editor toolbar, click the Refresh Implementation button.
A definition and skeletal implementations for the property access methods are inserted into the object's
implementation unit.
5 In the implementation unit, locate the access methods for the property. These have names of the form
Get_PropertyName and Set_PropertyName. Add code that gets or sets the property value of your object.
This code may simply call an existing function inside the application, access a data member that you add to the
object definition, or otherwise implement the property.
The default interface should be the name of the object preceded by the letter "I". To determine the default, in the
Type Library Editor, click the CoClass and select the Implements tab, and check the list of implemented
interfaces for the one marked, "Default."
2 Click the New Method button.
3 In the Attributes pane, specify the name of the method.
4 In the Parameters pane, specify the method's return type and add the appropriate parameters.
5 On the Type Library Editor toolbar, click the Refresh Implementation button.
1465
A definition and skeletal implementation for the method is inserted into the object's implementation unit.
6 In the implementation unit, locate the newly inserted method implementation. The method is completely empty.
The wizard creates an object that includes an Events interface as well as the default interface. This Events
interface has a name of the form I CoClassname Events. It is an outgoing (source) interface, which means that
it is not an interface your object implements, but rather is an interface that clients must implement and which your
object calls. (You can see this by selecting your CoClass, going to the Implements page, and noting that the
Source column on the Events interface says true.)
In addition to the Events interface, the wizard adds the IConnectionPointContainer interface to the declaration
of your implementation class, and adds several class members for handling events. Of these new class members,
the most important are FConnectionPoint and FConnectionPoints, which implement the IConnectionPoint and
IConnectionPointContainer interfaces using built-in VCL classes. FConnectionPoint is maintained by another
method that the wizard adds, EventSinkChanged.
2 In the Type Library Editor, select the outgoing Events interface for your object. (This is the one with a name of
Your object implementation now has everything it needs to accept client event sinks and maintain a list of
interfaces to call when the event occurs. To call these interfaces, you can create a method to generate each
event on clients.
6 In the Code Editor, add a method to your object for firing each event. For example,
unit ev;
interface
uses
ComObj, AxCtrls, ActiveX, Project1_TLB;
type
TMyAutoObject = class (TAutoObject,IConnectionPointContainer, IMyAutoObject)
1466
private
.
.
.
public
procedure Initialize; override;
procedure Fire_MyEvent; { Add a method to fire the event}
7 Implement the method you added in the last step so that it iterates through all the event sinks maintained by your
procedure TMyAutoObject.Fire_MyEvent;
var
I: Integer;
EventSinkList: TList;
EventSink: IMyAutoObjectEvents;
begin
if FConnectionPoint <> nil then
begin
EventSinkList :=FConnectionPoint.SinkList; {get the list of client sinks }
for I := 0 to EventSinkList.Count - 1 do
begin
EventSink := IUnknown(FEvents[I]) as IMyAutoObjectEvents;
EventSink.MyEvent;
end;
end;
end;
8 Whenever you need to fire the event so that clients are informed of its occurrence, call the method that dispatches
1467
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.
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.
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.
1468
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.
1469
Custom marshaling
Typically, you use automatic marshaling in out-of-process and remote servers because it is easierCOM 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.
1470
Options
Run.
1471
1472
New
Other.
In the wizard, give your new Active Server Object a name, and specify the instancing and threading models you
want to support. These details influence the way your object can be called. You must write the implementation
so that it adheres to the model (for example, avoiding thread conflicts).
The thing that makes an Active Server Object unique is its ability to access information about the ASP application
and the HTTP messages that pass between the Active Server page and client Web browsers. This information
is accessed using the ASP intrinsics. In the wizard, you can specify how your object accesses these by setting
the Active Server Type:
If you are working with IIS 3 or IIS 4, you use Page Level Event Methods. Under this model, your object
implements the methods, OnStartPage and OnEndPage, which are called when the Active Server page loads
and unloads. When your object is loaded, it automatically obtains an IScriptingContext interface, which it uses
to access the ASP intrinsics. These interfaces are, in turn, surfaced as properties inherited from the base class
(TASPObject).
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 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
As you write the implementation of your object's properties and methods, you can take advantage of the ASP
intrinsics 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.
1473
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:
IApplicationObject interface members
Property, Method, or Event Meaning
Contents property
Lists all the objects that were added to the application using script commands. This interface has
two methods, Remove and RemoveAll, that you can use to delete one or all objects from the list.
StaticObjects property
Lists all the objects that were added to the application with the <OBJECT> tag.
Lock method
Prevents other clients from locking the Application object until you call Unlock. All clients should
call Lock before accessing shared memory (such as the properties).
Unlock method
Releases the lock that was set using the Lock method.
Application_OnEnd event
Occurs when the application quits, after the Session_OnEnd event. The only intrinsics available
are Application and Server. The event handler must be written in VBScript or JScript.
Application_OnStart event
Occurs before the new session is created (before Session_OnStart). The only intrinsics available
are Application and Server. The event handler must be written in VBScript or JScript.
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:
IRequest interface members
Property, Method, or Event Meaning
ClientCertificate property
Indicates the values of all fields in the client certificate that is sent with the HTTP message.
Cookies property
Form property
Indicates the values of form elements in the HTTP body. These can be accessed by name.
QueryString property
Indicates the values of all variables in the query string from the HTTP header.
ServerVariables property
Indicates the values of various environment variables. These variables represent most of the
common HTTP header variables.
TotalBytes property
Indicates the number of bytes in the request body. This is an upper limit on the number of bytes
returned by the BinaryRead method.
1474
BinaryRead method
Retrieves the content of a Post message. Call the method, specifying the maximum number of
bytes to read. The resulting content is returns as a Variant array of bytes. After calling
BinaryRead, you can't use the Form property.
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:
IResponse interface members
Property, Method, or Event Meaning
Cookies property
Buffer property
Indicates whether page output is buffered When page output is buffered, the server does not
send a response to the client until all of the server scripts on the current page are processed.
CacheControl property
Determines whether proxy servers can cache the output in the response.
Charset property
Adds the name of the character set to the content type header.
ContentType property
Expires property
Specifies how long the response can be cached by a browser before it expires.
ExpiresAbsolute property
IsClientConnected property Indicates whether the client has disconnected from the server.
Pics property
Set the value for the pics-label field of the response header.
Status property
Indicates the status of the response. This is the value of an HTTP status header.
AddHeader method
AppendToLog method
Adds a string to the end of the Web server log entry for this request.
BinaryWrite method
Clear method
End method
Stops processing the .asp file and returns the current result.
Flush method
Redirect method
Sends a redirect response message, redirecting the client browser to a different URL.
Write method
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:
ISessionObject interface members
1475
Contents property
Lists all the objects that were added to the session using the <OBJECT> tag. You can access
any variable in the list by name, or call the Contents object's Remove or RemoveAll method to
delete values.
StaticObjects property
Lists all the objects that were added to the session with the <OBJECT> tag.
CodePage property
Specifies the code page to use for symbol mapping. Different locales may use different code
pages.
LCID property
SessionID property
TimeOut property
Specifies the time, in minutes, that the session persists without a request (or refresh) from the
client until the application terminates.
Abandon method
Session_OnEnd event
Occurs when the session is abandoned or times out. The only intrinsics available are Application,
Server, and Session. The event handler must be written in VBScript or JScript.
Session_OnStart event
Occurs when the server creates a new session is created (after Application_OnStart but before
running the script on the Active Server Page). All intrinsics are available. The event handler must
be written in VBScript or JScript.
Server
The Server object is accessed through an IServer interface. It provides various utilities for writing your ASP
application.
IServer includes the following:
IServer interface members
Property, Method, or Event Meaning
ScriptTimeOut property
CreateObject method
Execute method
GetLastError method
HTMLEncode method
Encodes a string for use in an HTML header, replacing reserved characters by the appropriate
symbolic constants.
MapPath method
Maps a specified virtual path (an absolute path on the current server or a path relative to the
current page) into a physical path.
Transfer method
Sends all of the current state information to another Active Server Page for processing.
URLEncode method
1476
(as, for example, it does when you use ActiveX objects). In-process component DLLs are faster and more secure
than out-of-process servers, so they are better suited for server-side use.
Because out-of-process servers are less secure, it is common for IIS to be configured to not allow out-of-process
executables. In this case, creating an out-of-process server for your Active Server Object would result in an error
similar to the following:
Server object error 'ASP 0196'
Cannot launch out of process component
/path/outofprocess_exe.asp, line 11
Also, out-of-process components often create individual server processes for each object instance, so they are
slower than CGI applications. They do not scale as well as component DLLs.
If performance and scalability are priorities for your site, in-process servers are highly recommended. However,
Intranet sites that receive moderate to low traffic may use an out-of-process component without adversely affecting
the site's overall performance.
Options
2 Choose Run
Parameters, type the name of your Web Server in the Host Application box, and choose OK.
3 Choose Run
Run.
1477
5 Use the Web browser to interact with the Active Server Page.
1478
To create a new ActiveX control (other than an Active Form), perform the following steps:
1 Design and create the custom VCL control that forms the basis of your ActiveX control.
2 Use the ActiveX control wizard to create an ActiveX control from the VCL control you created in step 1.
1479
3 Use the Use the ActiveX property page wizard to create one or more property pages for the control (optional).
4 Associate a property page with an ActiveX control(optional).
5 Register an ActiveX control.
6 Test an ActiveX control with all potential target applications.
7 Deploy the ActiveX control on the Web. (optional)
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 RegisterNonActiveXprocedure
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.
1480
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.
In the wizard, select the name of the VCL control that will be wrapped by the new ActiveX control. The dialog
lists all available controls, which are descendants of TWinControl that are not registered as incompatible with
ActiveX using the RegisterNonActiveXprocedure procedure.
1481
Tip: If you do not see the control you want in the drop-down list, check whether you have installed it in the IDE or
added its unit to your project.
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, see 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.
New
The Active Form wizard looks just like the ActiveX control wizard, except that you can't specify the name of the VCL
class to wrap. This is because Active forms are always based on TActiveForm.
As in the ActiveX control wizard, you can change the default names for the CoClass, implementation unit, and ActiveX
library project. Similarly, this wizard lets you indicate whether you want your Active Form to require a license, whether
it should include version information, and whether you want an About box form.
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, see Working with type libraries.
A form that descends from TActiveForm. This form appears in the form designer, where you can use it to visually
design the Active Form that appears to clients. Its implementation appears in the generated implementation
unit. In the initialization section of the implementation unit, a class factory is created, setting up
TActiveFormControl as the ActiveX wrapper for this form.
An About box form and unit if you requested them.
A .LIC file if you enabled licensing.
At this point, you can add controls and design the form as you like.
After you have designed and compiled the ActiveForm project into an ActiveX library (which has the OCX extension),
you can deploy the project to your Web server and Delphi creates a test HTML page with a reference to the
ActiveForm.
1483
<OBJECT CLASSID="clsid:6980CB99-f75D-84cf-B254-55CA55A69452">
<PARAM NAME="LPKPath" VALUE="ctrllic.lpk">
</OBJECT>
The CLSID identifies the object as a license package and PARAM specifies the relative location of the license
package file with respect to the HTML page.
When Internet Explorer tries to display the Web page containing the control, it parses the LPK file, extracts the license
key, and if the license key matches the control's license (returned by GetLicenseString), it renders the control on the
page. If more than one LPK is included in a Web page, Internet Explorer ignores all but the first.
For more information, look for Licensing ActiveX Controls on the Microsoft Web site.
of what Delphi supplies depends on whether you have added a property or method or whether you have added an
event.
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 methodsthe Delphi compiler handles this for you by generating code around the
1485
body of safecall methods to catch Delphi exceptions and to convert them into COM error info structures and
return codes.
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;
updated. The container interacts with the database and then notifies the control whether it succeeded or failed to
update the record.
Note: The container application that hosts your ActiveX control is responsible for connecting the data-aware
properties you enable in the type library to the database. See Using data-aware ActiveX controls for
information on how to write such a container using Delphi.
Description
Bindable
Indicates that the property supports data binding. If marked bindable, the property notifies
its container when the property value has changed.
Request Edit
Indicates that the property supports the OnRequestEdit notification. This allows the control
to ask the container if its value can be edited by the user.
Display Bindable
Indicates that the container can show users that this property is bindable.
Default Bindable
Indicates the single, bindable property that best represents the object. Properties that have
the default bind attribute must also have the bindable attribute. Cannot be specified on more
than one property in a dispinterface.
Immediate Bindable
Allows individual bindable properties on a form to specify this behavior. When this bit is set,
all changes will be notified. The bindable and request edit attribute bits need to be set for this
new bit to have an effect.
4 Click the Refresh button on the toolbar to update the type library.
1487
Note: When adding properties to an ActiveX control or ActiveForm, you must publish the properties that you want
to persist. If they are not published in the underlying VCL control, you must make a custom descendant of
the VCL control that redeclares the properties as published and then create an ActiveX control from the
descendant class.
New
Other.
The wizard creates a new form and implementation unit for the property page. The form is a descendant of
TPropertyPage, which lets you associate the form with the ActiveX control whose properties it edits.
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.
1488
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.
DefinePropertyPages method implementation in the control's implementation for the unit. For example,
procedure TButtonX.DefinePropertyPages(DefinePropertyPage: TDefinePropertyPage);
begin
DefinePropertyPage(Class_PropertyPage1);
end;
The GUID constant, Class_PropertyPage1, of the property page can be found in the property pages unit.
The GUID is defined in the property page's implementation unit.
1489
2 Add the property page unit to the uses clause of the controls implementation unit.
Note: Before you remove an ActiveX control from your system, you should unregister it.
To unregister an ActiveX control: Choose Run
As an alternative, you can use the tregsvr command from the command line or run the regsvr32.exe from the
operating system.
Parameters and type the client name in the Host Application edit box.
2 On the Project page, set the Target Dir to the location of the ActiveX control DLL as a path on the Web server.
This can be a local path name or a UNC path, for example, C:\INETPUB\wwwroot.
3 Set the Target URL to the location as a Uniform Resource Locators (URL) of the ActiveX control DLL (without
the file name) on your Web Server, for example, http://mymachine.borland.com/. See the documentation for your
Web Server for more information on how to do this.
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.
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).
1490
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.
No
No
No
Yes
Yes
No
An INF file, an ActiveX library file, and any additional files and
packages.
Yes
Yes
An INF file, a CAB file containing an ActiveX library, and a CAB file
each for any additional files and packages.
1491
1492
Transactional objects are distinguished from other COM objects in that they use a set of attributes supplied by MTS
or COM+ for handling issues that arise in a distributed computing environment. Some of these attributes require the
transactional object to implement the IObjectControl interface. IObjectControl defines methods that are called when
the object is activated or deactivated, where you can manage resources such as database connections. It also is
required for object pooling.
Note: If you are using MTS, your transactional objects must implement IObjectControl. Under COM+,
IObjectControl is not required, but is highly recommended. The Transactional Object wizard provides an
object that derives from IObjectControl.
A client of a transactional object is called a base client. From a base client's perspective, a transactional object
looks like any other COM object.
Under MTS, the transactional object must be built into a library (DLL), which is then installed in the MTS runtime
environment (the MTS executive, which lives in mtxex.dll). That is, the server object runs in the MTS runtime process
space. The MTS executive can be running in the same process as the base client, as a separate process on the
same machine as the base client, or as a remote server process on a separate machine.
Under COM+, the server application need not be an in-process server. Because the various services are integrated
into the COM libraries, there is no need for a separate MTS process to intercept calls to the server. Instead, COM
itself (or, rather, COM+) provides the resource management, transaction support, and so on. However, the server
application must still be installed, this time into a COM+ application.
See Requirements for a transactional object for a list of requirements for COM objects if they are to act as
transactional objects.
The connection between the base client and the transactional object is handled by a proxy on the client and a stub
on the server, just as with any out-of-process server. Connection information is maintained by the proxy. The
connection between the base client and proxy remains open as long as the client requires a connection to the server,
so it appears to the client that it has continued access to the server. In reality, though, the proxy may deactivate and
reactivate the object, conserving resources so that other clients may use the connection. For details on activating
and deactivating, see Just-in-time activation.
The object must implement the IObjectControl interface. Support for this interface is automatically added by the
Transactional Object wizard.
A server running in the MTS process space cannot aggregate with COM objects not running in MTS.
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.
Another way to access the Object context is to use methods in the TMtsAutoObject object:
if IsCallerInRole ('Manager') ...
You can use either of the above methods. However, there is a slight advantage of using the TMtsAutoObject methods
rather than referencing the ObjectContext property when you are testing your application. For a discussion of the
differences, see Debugging and testing MTS objects.
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
1494
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 justin-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 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.
To enable the resource dispenser, use the BDE administrator to turn on MTS POOLING in the System/Init area
of the configuration.
If you are using the ADO database components to connect to your data, the resource dispenser is provided by
ADO.
Note: There is no built-in resource pooling if you are using InterbaseExpress components for your database access.
For remote transactional data modules, connections are automatically enlisted on an object's transactions, and the
resource dispenser can automatically reclaim and reuse connections.
1496
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.
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.
1497
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.
Initiating transactions
Transaction time-out
Managing transactions in multi-tiered applications
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 transaction
Objects must execute within the scope of a transaction. When a new 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.
Objects must execute within their own transactions. When a new 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 transactions
Objects can execute within the scope of their client's 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 Ignored
Objects do not run within the scope of transactions. When a new 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 transactions The meaning of this setting varies, depending on whether you 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.
1499
Warning: When you set the transaction attribute, Delphi inserts a special GUID for the specified attribute as custom
data in the type library. This value is not recognized outside of Delphi. Therefore, it only has an effect if
you install the transactional object from the IDE. Otherwise, a system administrator must set this value
using the MTS Explorer or COM+ Component Manager.
Note: If the transactional object is already installed, you must first uninstall the object and reinstall it when changing
the transaction attribute. Use Run
Install MTS Objects or Run
Install COM+ Objects to do so.
Description
SetComplete
Indicates that the object has successfully completed its work for the transaction. The object is deactivated
upon return from the method that first entered the context. The object reactivates on the next call that requires
object execution.
SetAbort
Indicates that the object's work can never be committed and the transaction should be rolled back. The object
is deactivated upon return from the method that first entered the context. The object reactivates on the next
call that requires object execution.
EnableCommit Indicates that the object's work is not necessarily done, but that its transactional updates can be committed
in their current form. Use this to retain state across multiple calls from a client while still allowing transactions
to complete. The object is not deactivated until it calls SetComplete or SetAbort.
EnableCommit is the default state when an object is activated. This is why an object should always call
SetComplete or SetAbort before returning from a method, unless you want the object to maintain its internal
state for the next call from a client.
DisableCommit Indicates that the object's work is inconsistent and that it cannot complete its work until it receives further
method invocations from the client. Call this before returning control to the client to maintain state across
multiple client calls while keeping the current transaction active.
DisableCommit prevents the object from deactivating and releasing its resources on return from a method
call. Once an object has called DisableCommit, if a client attempts to commit the transaction before the object
has called EnableCommit or SetComplete, the transaction will abort.
1500
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.
Transactions can occur automatically as a result of the object's transaction attribute. Transactional objects can
be declared so that their objects always execute within a transaction, regardless of how the objects are created.
This way, objects do not need to include any logic to handle transactions. This feature also reduces the burden
on client applications. Clients do not need to initiate a transaction simply because the component that they are
using requires it.
1501
to OleCheck because the methods of IObjectContext are exposed by Windows directly and are therefore not declared
as safecall.
procedure TAccountTransfer.Transfer(DebitAccountId, CreditAccountId: TGuid;
Amount: Currency);
var
CreditAccountIntf, DebitAccountIntf: IAccount;
begin
try
OleCheck(ObjectContext.CreateInstance(DebitAccountId,
IAccount, DebitAccountIntf));
OleCheck(ObjectContext.CreateInstance(CreditAccountId,
IAccount, CreditAccountIntf));
DebitAccountIntf.Debit(Amount);
CreditAccountIntf.Credit(Amount);
except
DisableCommit;
raise;
end;
EnableCommit;
end;
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.
My Computer.
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.
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
1502
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.
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.
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.
New
Other.
1503
A threading model that indicates how client applications can call your object's interface. The threading model
determines how the object is registered. You are responsible for ensuring that the object's implementation
adheres to the selected model. .
A transaction model
An indication of whether your object notifies clients of events. Event support is only provided for traditional
events, not COM+ events.
When you complete this procedure, a new unit is added to the current project that contains the definition for the
transactional object. In addition, the wizard adds a type library to the project and opens it in the Type Library
Editor. Now you can expose the properties and methods of the interface through the type library. You define the
interface as you would define any COM object as described in Defining a COM object's interface.
The transactional object implements a dual interface, which supports both early (compile-time) binding through the
vtable and late (runtime) binding through the IDispatch interface.
The generated transactional object implements the IObjectControl interface methods, Activate, Deactivate, and
CanBePooled.
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.
Description
Single
1504
Both
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:
Call synchronization options
Option
Meaning
Disabled
COM+ does not assign activities to the object but it may inherit them with the caller's context. If the caller has
no transaction or object context, the object is not assigned to an activity. The result is the same as if the object
was not installed in a COM+ application. This option should not be used if any object in the application uses
a resource manager or if the object supports transactions or just-in-time activation.
Not Supported COM+ never assigns the object to an activity, regardless of the status of its caller. This option should not be
used if any object in the application uses a resource manager or if the object supports transactions or just-intime activation.
Supported
COM+ assigns the object to the same activity as its caller. If the caller does not belong to an activity, the object
does not either. This option should not be used if any object in the application uses a resource manager or if
the object supports transactions or just-in-time activation.
1505
Required
COM+ always assigns the object to an activity, creating one if necessary. This option must be used if the
transaction attribute is Supported or Required.
Requires New COM+ always assigns the object to a new activity, which is distinct from its caller's.
1506
Unlike the COM+ event object, a COM+ subscription object does contain its own implementation of an event
interface; this is where the actual work is done to respond to the event when it is generated. Delphi's COM+ Event
Subscription Object wizard can be used to create a project that contains a subscriber component.
The following figure depicts the interaction between publishers, subscribers, and the COM+ Catalog.
New
Other.
1507
In the Event Object wizard, specify the name of the event object, the name of the interface that defines the event
handlers, and (optionally) a brief description of the events.
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.
New
Other.
In the wizard dialog, enter the name of the class that will implement the event interface. Choose the threading model
from the combo box. In the Interface field, you can type the name of your event interface, or click on the Browse
button to bring up a list of all event classes currently installed in the COM+ Catalog. The COM+ Event Interface
Selection dialog also contains a browse button. You can use this button to search for and select a type library
containing the event interface. When you select an existing event class (or type library), the wizard will give you the
option of automatically implementing the interface supported by that event class. If you check the Implement Existing
Interface check box, the wizard will automatically stub out each method in the interface for you. To complete the
wizard, enter a brief description of your event subscriber component, and click on OK.
event to be delivered using multiple threads. This does not guarantee simultaneous delivery; it is simply a request
to the system to permit this to happen.
The value returned to the publisher is an aggregate of all the return codes from each subscriber. There is no direct
way for a publisher to find out which subscriber failed. To do so, a publisher must implement a publisher filter. See
the Microsoft MSDN documentation for more information on this subect. The following table summarizes the possible
return codes.
Event publisher return codes
Return Code
Meaning
S_OK
EVENT_S_SOME_SUBSCRIBERS_FAILED Some subscribers either could not be invoked, or returned a failure code (note
this is not an error condition).
EVENT_E_ALL_SUBSCRIBERS_FAILED
EVENT_S_NOSUBSCRIBERS
There are no subscriptions in the COM+ Catalog (note this is not an error
condition).
Calling SafeRef on a reference that is already safe returns the safe reference unchanged, except that the reference
count on the interface is incremented.
When a client calls QueryInterface on a reference that is safe, the reference returned to the client is also a safe
reference.
An object that obtains a safe reference must release the safe reference when finished with it.
For details on SafeRef see the SafeRef topic in the Microsoft documentation.
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.
The Advanced tab determines whether the server process associated with a package always runs, or whether
it shuts down after a certain period of time.
3 Change the Minutes until idle shutdown value to 0, which shuts down the server as soon as it no longer has a
client to service.
4 Click OK to save the setting.
1510
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.
are installing COM+ objects, click the Application button. Indicate the MTS package or COM+ application into
which you are installing your objects. You can choose Into New Package or Into New Application to create a
new MTS package or COM+ application in which to install the object. You can choose Into Existing Package
or Into Existing Application to install the object into an existing listed MTS package or COM+ application.
4 Choose OK to refresh the catalog, which makes the objects available at runtime.
MTS packages can contain components from multiple DLLs, and components from a single DLL can be installed
into different packages. However, a single component cannot be distributed among multiple packages.
Similarly, COM+ applications can contain components from multiple executables and different components from a
single executable can be installed into different COM+ applications.
Note: You can also install your transactional object using the COM+ Component Manager or MTS Explorer. Be
sure when installing the object with one of these tools that you apply the settings for the object that appear
on the COM+ page of the Type Library editor. These settings are not applied automatically when you do not
install from the IDE.
1511
View properties of components in an package or COM+ application and view the MTS packages or COM+
applications installed on a computer
Monitor and manage transactions for objects that comprise transactions
Move MTS packages or COM+ applications between computers
Make a remote transactional object available to a local client
For more details on these tools, see the appropriate Administrator's Guide from Microsoft.
1512
1513
Class library
Delphi's components reside in the Visual Component Library (VCL). The following figure shows the relationship of
selected classes that make up the VCL hierarchy. For a more detailed discussion of class hierarchies and the
inheritance relationships among classes, see Object-oriented programming for component writers
The TComponent class is the shared ancestor of every component in the component library. TComponent provides
the minimal properties and events necessary for a component to work in the IDE. The various branches of the library
provide other, more specialized capabilities.
1514
When you create a component, you add to the component library by deriving a new class from one of the existing
class types in the hierarchy.
Creating Components
A component can be almost any program element that you want to manipulate at design time. Creating a component
means deriving a new class from an existing one. You can derive a new component in several ways:
Modifying existing controls
Creating windowed controls
Creating graphic controls
Subclassing Windows controls
Creating nonvisual components
The following table summarizes the different kinds of components and the classes you use as starting points for each.
Component creation starting points
To do this
Any existing component, such as TButton or TListBox, or an abstract component type, such
as TCustomListBox
1515
TWinControl
TGraphicControl
Subclassing a control
You can also derive classes that are not components and cannot be manipulated on a form, such as TRegIniFile
and TFont.
1516
The section Creating a graphic control presents an example of creating a graphic control.
Removing Dependencies
One quality that makes components usable is the absence of restrictions on what they can do at any point in their
code. By their nature, components are incorporated into applications in varying combinations, orders, and contexts.
You should design components that function in any situation, without preconditions.
An example of removing dependencies is the Handle property of TWinControl. If you have written Windows
applications before, you know that one of the most difficult and error-prone aspects of getting a program running is
making sure that you do not try to access a windowed control until you have created it by calling the
CreateWindow API function. Delphi windowed controls relieve users from this concern by ensuring that a valid
window handle is always available when needed. By using a property to represent the window handle, the control
can check whether the window has been created; if the handle is not valid, the control creates a window and returns
the handle. Thus, whenever an application's code accesses the Handle property, it is assured of getting a valid
handle.
By removing background tasks like creating the window, Delphi components allow developers to focus on what they
really want to do. Before passing a window handle to an API function, you do not need to verify that the handle exists
or to create the window. The application developer can assume that things will work, instead of constantly checking
for things that might go wrong.
1517
Although it can take time to create components that are free of dependencies, it is generally time well spent. It not
only spares application developers from repetition and drudgery, but it reduces your documentation and support
burdens.
Properties
Properties give the application developer the illusion of setting or reading the value of a variable, while allowing the
component writer to hide the underlying data structure or to implement special processing when the value is
accessed.
There are several advantages to using properties:
Properties are available at design time. The application developer can set or change initial values of properties
without having to write code.
Properties can check values or formats as the application developer assigns them. Validating input at design
time prevents errors.
The component can construct appropriate values on demand. Perhaps the most common type of error
programmers make is to reference a variable that has not been initialized. By representing data with a property,
you can ensure that a value is always available on demand.
Properties allow you to hide data under a simple, consistent interface. You can alter the way information is
structured in a property without making the change visible to application developers.
The section Overview of component creation explains how to add properties to your components.
Methods
Class methods are procedures and functions that operate on a class rather than on specific instances of the class.
For example, every component's constructor method (Create) is a class method. Component methods are
procedures and functions that operate on the component instances themselves. Application developers use methods
to direct a component to perform a specific action or return a value not contained by any property.
Because they require execution of code, methods can be called only at runtime. Methods are useful for several
reasons:
Methods encapsulate the functionality of a component in the same object where the data resides.
Methods can hide complicated procedures under a simple, consistent interface. An application developer can
call a component's AlignControls method without knowing how the method works or how it differs from the
AlignControls method in another component.
Methods allow updating of several properties with a single call.
The section Creating methods explains how to add methods to your components.
1518
Events
An event is a special property that invokes code in response to input or other activity at runtime. Events give the
application developer a way to attach specific blocks of code to specific runtime occurrences, such as mouse actions
and keystrokes. The code that executes when an event occurs is called an event handler.
Events allow application developers to specify responses to different kinds of input without defining new components.
The section Creating events explains how to implement standard events and how to define new ones.
Encapsulating Graphics
Delphi simplifies Windows graphics by encapsulating various graphics tools into a canvas. The canvas represents
the drawing surface of a window or control and contains other classes, such as a pen, a brush, and a font. A canvas
is like a Windows device context, but it takes care of all the bookkeeping for you.
If you have written a graphical Windows application, you are familiar with the requirements imposed by Windows'
graphics device interface (GDI). For example, GDI limits the number of device contexts available and requires that
you restore graphic objects to their initial state before destroying them.
With Delphi, you do not have to worry about these things. To draw on a form or other component, you access the
component's Canvas property. If you want to customize a pen or brush, you set its color or style. When you finish,
Delphi disposes of the resources. Delphi caches resources to avoid recreating them if your application frequently
uses the same kinds of resource.
You still have full access to the Windows GDI, but you will often find that your code is simpler and runs faster if you
use the canvas built into Delphi components.
How graphics images work in the component depends on the canvas of the object from which your component
descends. Graphics features are detailed in the section Using graphics in components.
Registering Components
Before you can install your components in the IDE, you have to register them. Registration tells Delphi where to
place the component on the Tool palette. You can also customize the way Delphi stores your components in the
form file. For information on registering a component, see Registering components.
There are several basic steps that you perform whenever you create a new component.
These steps are described below; other examples in this document assume that you know
how to perform them.
1 Create a unit for the new component.
2 Derive your component from an existing component type.
3 Add properties, methods, and events.
1519
Note: Creating a Help file to instruct component users on how to use the component is optional.
When you finish, the complete component includes the following files:
A package (.BPL) or package collection (.DPC) file
A compiled package (.DCP) file
A compiled unit (.DCU) file
A palette bitmap (.DCR) file
A Help (.HLP) file
You can also create a bitmap to represent your new component. See Creating a bitmap for a component.
Choose Component
Choose File
New
In the Ancestor Type field, specify the class from which you are deriving your new component.
In the Class Name field, specify the name of your new component class.
1520
In the Palette Page field, specify the category on the Tool palette on which you want the new component to be
installed.
In the Unit file name field, specify the name of the unit you want the component class declared in. If the unit is
not on the search path, edit the search path in the Search Path field as necessary.
3 After you fill in the fields in the Component wizard,
Click Install. To place the component in a new or existing package, click Component
Install and use the
dialog box that appears to specify a package. See Installing a component on the Tool palette.
4 Click OK. The IDE creates a new unit.
Warning: If you derive a component from a class whose name begins with "custom" (such as TCustomControl),
do not try to place the new component on a form until you have overridden any abstract methods in the
original component. Delphi cannot create instance objects of a class that has abstract properties or
methods.
To see the source code for your unit, click View
Units... (If the Component wizard is already closed, open the
unit file in the Code editor by selecting File Open.) Delphi creates a new unit containing the class declaration and
the Register procedure, and adds a uses clause that includes all the standard Delphi units.
The unit looks like this:
unit MyControl;
interface
uses
Windows, Messages, SysUtils, Types, Classes, Controls;
type
TMyControl = class(TCustomControl)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TMyControl]); //In CLX, use a different page than
'Samples'
end;
end.
1521
File
New
File New
choose OK.
Unit.
Other to display the New Items dialog box, select Delphi Projects
Delphi Files
Unit, and
The IDE creates a new unit file and opens it in the Code editor.
2 Save the file with a meaningful name.
3 Derive the component class.
Note:
Open and select the source code unit to which you want to add your component.
When adding a component to an existing unit, make sure that the unit contains only component
code. For example, adding component code to a unit that contains a form causes errors in the
Tool palette.
1522
To register a component:
1 Add a procedure named Register to the interface part of the component's unit. Register takes no parameters,
If you are adding a component to a unit that already contains components, it should already have a Register
procedure declared, so you do not need to change the declaration.
Note:
Although Delphi is a case insensitive language, the Register procedure is case sensitive and
must be spelled with an uppercase R.
2 Write the Register procedure in the implementation part of the unit, calling RegisterComponents for each
component you want to register. RegisterComponents is a procedure that takes two parameters: the name of a
Tool palette category and a set of component types. If you are adding a component to an existing registration,
you can either add the new component to the set in the existing statement, or add a new statement that calls
RegisterComponents.
Image Editor.
New
Bitmap.
3 In the Bitmaps Properties dialog box, change both the Width and Height to 24 pixels. Make sure VGA (16 colors)
the same name as the class name for your new component, including the T, using all uppercase letters. For
example, if your new class name is going to be TMyNewButton, name the bitmap TMYNEWBUTTON.
Note:
You must name all uppercase letters, no matter how you spell the class name in the New
Component dialog box.
1523
7 Choose File
Save As and give the resource file (.dcr or .res) the same base name as the unit you want the
component class declared in. For example, name the resource file MyNewButton.dcr.
8 Choose Component
New Component. Follow the instructions for creating a new component using the
Component wizard. Make sure that the component source, MyNewButton.pas, is in the same directory as
MyNewButton.dcr.
The Component wizard, for a class named TMyNewButton, names the component source, or unit, MyNewButton.
pas with a default placement in the LIB directory. Click the Browse button to find the new location for the generated
component unit.
Note:
If you are using a .res file for the bitmap rather than a .dcr file, then add a reference to the
component source to bind the resource. For example, if your .res file is named MyNewButton.
res, after ensuring that the .pas and .res are in the same directory, add the following to
MyNewButton.pas below the type section:
{*R *.res}
9 Choose Component
Install Component to install your component into a new or existing package. Click OK.
Your new package is built and then installed. The bitmap representing your new component appears on the Tool
palette category you designated in the Component wizard.
Install Component.
This compiles/rebuilds the package and installs the component on the Tool palette.
Note: Newly installed components initially appear in the category of the Tool palette that was specified by the
component writer. You can move the components to a different category after they have been installed on
the palette with the Component
Configure Palette dialog box.
For component writers who need to distribute their components to users to install on the Tool palette, see Making
source files available.
1524
This is one of the main differences between the way you add components and the way Delphi does it. You add
the object field to the public part at the bottom of the form's type declaration. Delphi would add it above, in the
part of the type declaration that it manages.
Never add fields to the Delphi-managed part of the form's type declaration. The items in that part of the type
declaration correspond to the items stored in the form file. Adding the names of components that do not exist on
the form can render your form file invalid.
3 Attach a handler to the form's OnCreate event.
4 Construct the component in the form's OnCreate handler.
When you call the component's constructor, you must pass a parameter specifying the owner of the component
(the component responsible for destroying the component when the time comes). You will nearly always pass
Self as the owner. In a method, Self is a reference to the object that contains the method. In this case, in the
form's OnCreate handler, Self refers to the form.
5 Assign the Parent property.
Setting the Parent property is always the first thing to do after constructing a control. The parent is the component
that contains the control visually; usually it is the form on which the control appears, but it might be a group box
or panel. Normally, you'll set Parent to Self, that is, the form. Always set Parent before setting other properties
of the control.
Warning:
If your component is not a control (that is, if TControl is not one of its ancestors), skip this
step. If you accidentally set the form's Parent property (instead of the component's) to
Self, you can cause an operating-system problem.
1525
Options and on the Directories/Conditionals page, set the Debug Source Path to the
component's source file.
Borland Debuggers
Language Exceptions
Parameters and set the Host Application field to the name and location of the Delphi executable file.
5 In the Run Parameters dialog, click the Load button to start a second instance of Delphi.
6 Then drop the components to be tested on the form, which should break on your breakpoints in the source.
1526
1527
1528
Controlling Access
There are five levels of access control - also called visibility - on properties, methods, and fields. Visibility determines
which code can access which parts of the class. By specifying visibility, you define the interface to your components.
The table below shows the levels of visibility, from most restrictive to most accessible:
Levels of visibility within an object
Visibility
Meaning
Used for
private
protected
Accessible to code in the unit(s) where the class and its descendants Defining the component writer's interface.
are defined.
public
published
Declare members as private if you want them to be available only within the class where they are defined; declare
them as protected if you want them to be available only within that class and its descendants. Remember, though,
that if a member is available anywhere within a unit file, it is available everywhere in that file. Thus, if you define two
classes in the same unit, the classes will be able to access each other's private methods. And if you derive a class
in a different unit from its ancestor, all the classes in the new unit will be able to access the ancestor's protected
methods.
1529
Dispatching Methods
Dispatch refers to the way a program determines where a method should be invoked when it encounters a method
call. The code that calls a method looks like any other procedure or function call. But classes have different ways of
dispatching methods.
The three types of method dispatch are
Static
Virtual
Dynamic
Static Methods
All methods are static unless you specify otherwise when you declare them. Static methods work like regular
procedures or functions. The compiler determines the exact address of the method and links the method at compile
time.
1530
The primary advantage of static methods is that dispatching them is very quick. Because the compiler can determine
the exact address of the method, it links the method directly. Virtual and dynamic methods, by contrast, use indirect
means to look up the address of their methods at runtime, which takes somewhat longer.
A static method does not change when inherited by a descendant class. If you declare a class that includes a static
method, then derive a new class from it, the derived class shares exactly the same method at the same address.
This means that you cannot override static methods; a static method always does exactly the same thing no matter
what class it is called in. If you declare a method in a derived class with the same name as a static method in the
ancestor class, the new method simply replaces the inherited one in the derived class.
Virtual Methods
Virtual methods employ a more complicated, and more flexible, dispatch mechanism than static methods. A virtual
method can be redefined in descendant classes, but still be called in the ancestor class. The address of a virtual
method isn't determined at compile time; instead, the object where the method is defined looks up the address at
runtime.
To make a method virtual, add the directive virtual after the method declaration. The virtual directive creates an
entry in the object's virtual method table, or VMT, which holds the addresses of all the virtual methods in an object
type.
When you derive a new class from an existing one, the new class gets its own VMT, which includes all the entries
from the ancestor's VMT plus any additional virtual methods declared in the new class.
Overriding Methods
Overriding a method means extending or refining it, rather than replacing it. A descendant class can override any
of its inherited virtual methods.
To override a method in a descendant class, add the directive override to the end of the method declaration.
Overriding a method causes a compilation error if
The method does not exist in the ancestor class.
The ancestor's method of that name is static.
The declarations are not otherwise identical (number and type of arguments parameters differ).
Dynamic Methods
Dynamic methods are virtual methods with a slightly different dispatch mechanism. Because dynamic methods don't
have entries in the object's virtual method table, they can reduce the amount of memory that objects consume.
However, dispatching dynamic methods is somewhat slower than dispatching regular virtual methods. If a method
is called frequently, or if its execution is time-critical, you should probably declare it as virtual rather than dynamic.
Objects must store the addresses of their dynamic methods. But instead of receiving entries in the virtual method
table, dynamic methods are listed separately. The dynamic method list contains entries only for methods introduced
or overridden by a particular class. (The virtual method table, in contrast, includes all of the object's virtual methods,
both inherited and introduced.) Inherited dynamic methods are dispatched by searching each ancestor's dynamic
method list, working backwards through the inheritance tree.
To make a method dynamic, add the directive dynamic after the method declaration.
instances of a class that contains abstract members. For more information about surfacing inherited parts of classes,
see Creating properties and Creating methods.
1532
Creating properties
Creating Properties: Overview
Properties are the most visible parts of components. The application developer can see and manipulate them at
design time and get immediate feedback as the components react in the Form Designer. Well-designed properties
make your components easier for others to use and easier for you to maintain.
To make the best use of properties in your components, you should understand the following:
Why create properties?
Types of properties
Publishing inherited properties
Defining properties
Creating array properties
Storing and loading properties
1533
A simple example is the Top property of all controls. Assigning a new value to Top does not just change a stored
value; it repositions and repaints the control. And the effects of setting a property need not be limited to an individual
component; for example, setting the Down property of a speed button to True sets Down property of all other speed
buttons in its group to False.
Types of Properties
A property can be of any type. Different types are displayed differently in the Object Inspector, which validates
property assignments as they are made at design time.
How properties appear in the Object Inspector
Property type treatment
Simple
Numeric, character, and string properties appear as numbers, characters, and strings. The application
developer can edit the value of the property directly.
Enumerated
Properties of enumerated types (including Boolean) appear as editable strings. The developer can also cycle
through the possible values by double-clicking the value column, and there is a drop-down list that shows all
possible values.
Set
Properties of set types appear as sets. By double-clicking on the property, the developer can expand the set
and treat each element as a Boolean value (true if it is included in the set).
Object
Properties that are themselves classes often have their own property editors, specified in the component's
registration procedure. If the class held by a property has its own published properties, the Object Inspector
lets the developer to expand the list (by double-clicking) to include these properties and edit them individually.
Object properties must descend from TPersistent.
Interface
Properties that are interfaces can appear in the Object Inspector as long as the value is an interface that is
implemented by a component (a descendant of TComponent). Interface properties often have their own
property editors.
Array
Array properties must have their own property editors; the Object Inspector has no built-in support for editing
them. You can specify a property editor when you register your components.
Defining Properties
This section shows how to declare new properties and explains some of the conventions followed in the standard
components. Topics include:
Property declarations
Internal data storage
Direct access
Access methods
Default property values
1534
Property Declarations
A property is declared in the declaration of its component class. To declare a property, you specify three things:
The name of the property.
The type of the property.
The methods used to read and write the value of the property. If no write method is declared, the property is
read-only.
Properties declared in a published section of the component's class declaration are editable in the Object Inspector
at design time. The value of a published property is saved with the component in the form file. Properties declared
in a public section are available at runtime and can be read or set in program code.
Direct Access
The simplest way to make property data available is direct access. That is, the read and write parts of the property
declaration specify that assigning or reading the property value goes directly to the internal-storage field without
calling an access method. Direct access is useful when you want to make a property available in the Object Inspector
but changes to its value trigger no immediate processing.
It is common to have direct access for the read part of a property declaration but use an access method for the
write part. This allows the status of the component to be updated when the property value changes.
For example, the read method for a property called Count would be GetCount. The read method manipulates the
internal storage data as needed to produce the value of the property in the appropriate type.
The only exceptions to the no-parameters rule are for array properties and properties that use index specifiers (see
Creating array properties), both of which pass their index values as parameters. (Use index specifiers to create a
single read method that is shared by several properties. For more information about index specifiers, see the Delphi
Language Guide.)
If you do not declare a read method, the property is write-only. Write-only properties are seldom used.
Note: Declaring a default value does not set the property to that value. The component's constructor method should
initialize property values when appropriate. However, since objects always initialize their fields to 0, it is not
strictly necessary for the constructor to set integer properties to 0, string properties to null, or Boolean
properties to False.
1536
When you declare a property for the first time, there is no need to include nodefault. The absence of a declared
default value means that there is no default.
1537
Note that the property setter above called the FreeNotification method of the component that is set as the property
value. This call ensures that the component that is the value of the property sends a notification if it is about to be
destroyed. It sends this notification by calling the Notification method. You handle this call by overriding the
Notification method, as follows:
procedure TDemoComponent.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = FTimer) then
FTimer := nil;
end;
1538
Calling ReferenceInterface with a specified interface does the same thing as calling another component's
FreeNotification method. Thus, after calling ReferenceInterface from the property setter, you can override the
Notification method to handle the notifications from the implementor of the interface:
procedure TDemoComponent.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Assigned(MyIntfProp)) and (AComponent.IsImplementorOf(MyInftProp)) then
MyIntfProp := nil;
end;
Note that the Notification code assigns nil to the MyIntfProp property, not to the private field (FIntfField). This ensures
that Notification calls the property setter, which calls ReferenceInterface to remove the notification request that was
established when the property value was set previously. All assignments to the interface property must be made
through the property setter.
1539
1540
1541
No matter which method you use to define the property, you pass it the methods that store and load your property
value as well as a boolean value indicating whether the property value needs to be written. If the value can be
inherited or has a default value, you do not need to write it.
For example, given the LoadCompProperty method of type TReaderProc and the StoreCompProperty method of
type TWriterProc, you would override DefineProperties as follows:
procedure TSampleComponent.DefineProperties(Filer: TFiler);
function DoWrite: Boolean;
begin
if Filer.Ancestor <> nil then { check Ancestor for an inherited value }
begin
if TSampleComponent(Filer.Ancestor).MyCompProperty = nil then
Result := MyCompProperty <> nil
else if MyCompProperty = nil or
TSampleComponent(Filer.Ancestor).MyCompProperty.Name <> MyCompProperty.Name then
Result := True
else Result := False;
end
else { no inherited value -- check for default (nil) value }
Result := MyCompProperty <> nil;
end;
begin
inherited; { allow base classes to define properties }
Filer.DefineProperty('MyCompProperty', LoadCompProperty, StoreCompProperty, DoWrite);
end;
1542
Creating events
Creating Events: Overview
An event is a link between an occurrence in the system (such as a user action or a change in focus) and a piece of
code that responds to that occurrence. The responding code is an event handler, and is nearly always written by
the application developer. Events let application developers customize the behavior of components without having
to change the classes themselves. This is known as delegation.
Events for the most common user actions (such as mouse actions) are built into all the standard components, but
you can also define new events. To create events in a component, you need to understand the following:
What are events?
Implementing the standard events
Defining your own events
Events are implemented as properties, so you should already be familiar with the material in Creating properties
before you attempt to create or change a component's events.
The implementation of Click calls the user's click-event handler, if one exists. If the user has assigned a handler to
a control's OnClick event, clicking the control results in that method being called. If no handler is assigned, nothing
happens.
To learn about TNotifyEvent and other event types, see the next section, Event types are method-pointer types.
As with any other property, you can set or change the value of an event at runtime. The main advantage to having
events be properties, however, is that component users can assign handlers to events at design time, using the
Object Inspector.
1544
Normally, the Key parameter contains the character pressed by the user. Under certain circumstances, however,
the user of the component may want to change the character. One example might be to force all characters to
uppercase in an editor. In that case, the user could define the following handler for keystrokes:
procedure TForm1.Edit1KeyPressed(Sender: TObject; var Key: Char);
begin
Key := UpCase(Key);
end;
You can also use var parameters to let the user override the default handling.
1545
1546
Note: To respond to special keystrokes (such as the Alt key), however, you must respond to the
WM_GETDLGCODE or CM_WANTSPECIALKEYS message from Windows. See Handling messages and
system notifications for information on writing message handlers.
1547
in other controls. When defining your events, you must ensure that all the appropriate occurrences call the proper
events.
Simple notifications
A notification event is one that only tells you that the particular event happened, with no specific information about
when or where. Notifications use the type TNotifyEvent, which carries only one parameter, the sender of the event.
All a handler for a notification "knows" about the event is what kind of event it was, and what component the event
happened to. For example, click events are notifications. When you write a handler for a click event, all you know is
that a click occurred and which component was clicked.
Notification is a one-way process. There is no mechanism to provide feedback or prevent further handling of a
notification.
Event-specific handlers
In some cases, it is not enough to know which event happened and what component it happened to. For example,
if the event is a key-press event, it is likely that the handler will want to know which key the user pressed. In these
cases, you need handler types that include parameters for additional information.
If your event was generated in response to a message, it is likely that the parameters you pass to the event handler
come directly from the message parameters.
1548
1549
Creating methods
Creating Methods: Overview
Component methods are procedures and functions built into the structure of a class. Although there are essentially
no restrictions on what you can do with the methods of a component, Delphi does use some standards you should
follow. These guidelines include:
Avoiding dependencies
Naming methods
Protecting methods
Making methods virtual
Declaring methods
In general, components should not contain many methods and you should minimize the number of methods that an
application needs to call. The features you might be inclined to implement as methods are often better encapsulated
into properties. Properties provide an interface that suits the Delphi and are accessible at design time.
Avoiding Interdependencies
At all times when writing components, minimize the preconditions imposed on the developer. To the greatest extent
possible, developers should be able to do anything they want to a component, whenever they want to do it. There
will be times when you cannot accommodate that, but your goal should be to come as close as possible.
This list gives you an idea of the kinds of dependencies to avoid:
Methods that the user must call to use the component
Methods that must execute in a particular order
Methods that put the component into a state or mode where certain events or methods could be invalid
The best way to handle these situations is to ensure that you provide ways out of them. For example, if calling a
method puts your component into a state where calling another method might be invalid, then write that second
method so that if an application calls it when the component is in a bad state, the method corrects the state before
executing its main code. At a minimum, you should raise an exception in cases when a user calls a method that is
invalid.
In other words, if you create a situation where parts of your code depend on each other, the burden should be on
you to be sure that using the code in incorrect ways does not cause problems. A warning message, for example, is
preferable to a system failure if the user does not accommodate your dependencies.
1550
Naming Methods
Delphi imposes no restrictions on what you name methods or their parameters. There are a few conventions that
make methods easier for application developers, however. Keep in mind that the nature of a component architecture
dictates that many different kinds of people can use your components.
If you are accustomed to writing code that only you or a small group of programmers use, you might not think too
much about how you name things. It is a good idea to make your method names clear because people unfamiliar
with your code (and even unfamiliar with coding) might have to use your components.
Here are some suggestions for making clear method names:
Make names descriptive. Use meaningful verbs. A name like PasteFromClipboard is much more informative
than simply Paste or PFC.
Function names should reflect the nature of what they return.
Although it might be obvious to you as a programmer that a function named X returns the horizontal position of
something, a name like GetHorizontalPosition is more universally understandable.
As a final consideration, make sure the method really needs to be a method. A good guideline is that method names
have verbs in them. If you find that you create a lot of methods that do not have verbs in their names, consider
whether those methods ought to be properties.
Protecting Methods
All parts of classes, including fields, methods, and properties, have a level of protection or "visibility," as explained
in Controlling access. Choosing the appropriate visibility for a method is simple.
Most methods you write in your components are public or protected. You rarely need to make a method private,
unless it is truly specific to that type of component, to the point that even derived components should not have access
to it.
1551
Abstract Methods
Sometimes a method is declared as abstract in a Delphi component. In the component library, abstract methods
usually occur in classes whose names begin with "custom," such as TCustomGrid. Such classes are themselves
abstract, in the sense that they are intended only for deriving descendant classes.
While you can create an instance object of a class that contains an abstract member, it is not recommended. Calling
the abstract member leads to an EAbstractError exception.
The abstract directive is used to indicate parts of classes that should be surfaced and defined in descendant
components; it forces component writers to redeclare the abstract member in descendant classes before actual
instances of the class can be created.
Declaring Methods
Declaring a method in a component is the same as declaring any class method.
To declare a new method in a component, do the following:
Add the declaration to the component's object-type declaration.
Implement the method in the implementation part of the component's unit.
1552
Overview of Graphics
Delphi encapsulates the Windows GDI at several levels. The most important to you as a component writer is the
way components display their images on the screen. When calling GDI functions directly, you need to have a handle
to a device context, into which you have selected various drawing tools such as pens, brushes, and fonts. After
rendering your graphic images, you must restore the device context to its original state before disposing of it.
Instead of forcing you to deal with graphics at a detailed level, Delphi provides a simple yet complete interface: your
component's Canvas property. The canvas ensures that it has a valid device context, and releases the context when
you are not using it. Similarly, the canvas has its own properties representing the current pen, brush, and font.
The canvas manages all these resources for you, so you need not concern yourself with creating, selecting, and
releasing things like pen handles. You just tell the canvas what kind of pen it should use, and it takes care of the rest.
One of the benefits of letting Delphi manage graphic resources is that it can cache resources for later use, which
can speed up repetitive operations. For example, if you have a program that repeatedly creates, uses, and disposes
of a particular kind of pen tool, you need to repeat those steps each time you use it. Because Delphi caches graphic
resources, chances are good that a tool you use repeatedly is still in the cache, so instead of having to recreate a
tool, Delphi uses an existing one.
1553
An example of this is an application that has dozens of forms open, with hundreds of controls. Each of these controls
might have one or more TFont properties. Though this could result in hundreds or thousands of instances of
TFont objects, most applications wind up using only two or three font handles, thanks to a font cache.
Operation
Tools
High
Filling areas
Low
Manipulating pixels
Pixels property.
1554
access them all in the same way through the picture class. For example, the image control has a property called
Picture, of type TPicture, enabling the control to display images from many kinds of graphics.
Keep in mind that a picture class always has a graphic, and a graphic might have a canvas. (The only standard
graphic that has a canvas is TBitmap.) Normally, when dealing with a picture, you work only with the parts of the
graphic class exposed through TPicture. If you need access to the specifics of the graphic class itself, you can refer
to the picture's Graphic property.
Handling Palettes
For VCLcomponents, when running on a palette-based device (typically, a 256-color video mode), Delphi controls
automatically support palette realization. That is, if you have a control that has a palette, you can use two methods
inherited from TControl to control how Windows accommodates that palette.
Palette support for controls has these two aspects:
Specifying a palette for a control
Responding to palette changes
Most controls have no need for a palette, but controls that contain "rich color" graphic images (such as the image
control) might need to interact with Windows and the screen device driver to ensure the proper appearance of the
control. Windows refers to this process as realizing palettes.
Realizing palettes is the process of ensuring that the foremost window uses its full palette, and that windows in the
background use as much of their palettes as possible, then map any other colors to the closest available colors in
the "real" palette. As windows move in front of one another, Windows continually realizes the palettes.
Note: Delphi itself provides no specific support for creating or maintaining palettes, other than in bitmaps. If you
have a palette handle, however, Delphi controls can manage it for you.
1555
Off-screen Bitmaps
When drawing complex graphic images, a common technique in graphics programming is to create an off-screen
bitmap, draw the image on the bitmap, and then copy the complete image from the bitmap to the final destination
onscreen. Using an off-screen image reduces flicker caused by repeated drawing directly to the screen.
The bitmap class in Delphi, which represents bitmapped images in resources and files, can also work as an offscreen image.
There are two main aspects to working with off-screen bitmaps:
Creating and managing off-screen bitmaps.
Copying bitmapped images.
1556
method }
bitmap }
object }
object }
Draw
StretchDraw
CopyRect
Responding to Changes
All graphic objects, including canvases and their owned objects (pens, brushes, and fonts) have events built into
them for responding to changes in the object. By using these events, you can make your components (or the
applications that use them) respond to changes by redrawing their images.
Responding to changes in graphic objects is particularly important if you publish them as part of the design-time
interface of your components. The only way to ensure that the design-time appearance of the component matches
the properties set in the Object Inspector is to respond to changes in the objects.
To respond to changes in a graphic object, assign a method to the class's OnChange event.
1557
Handling messages
Handling Messages and System Notifications: Overview
Components often need to respond to notifications from the underlying operating system. The operating system
informs the application of occurrences such as what the user does with the mouse and keyboard. Some controls
also generate notifications, such as the results from user actions such as selecting an item in a list box. The
component library handles most of the common notifications already. It is possible, however, that you will need to
write your own code for handling such notifications.
For VCL applications, notifications arrive in the form of messages. These messages can come from any source,
including Windows, VCL components, and components you have defined. There are three aspects to working with
messages:
Understanding the message-handling system.
Changing message handling.
Creating new message handlers.
The Visual Component Library defines a message-dispatching system that translates all Windows messages
(including user-defined messages) directed to a particular class into method calls. You should never need to alter
this message-dispatch mechanism. All you will need to do is create message-handling methods. See the section
Declaring a new message-handling method for more on this subject.
One parameter contains 16 bits, the other 32 bits. You often see Windows code that refers to those values as wParam
and lParam, for word parameter and long parameter. Often, each parameter will contain more than one piece of
information, and you see references to names such as lParamHi, which refers to the high-order word in the long
parameter.
Originally, Windows programmers had to remember or look up in the Windows APIs what each parameter contained.
Now Microsoft has named the parameters. This so-called message cracking makes it much simpler to understand
what information accompanies each message. For example, the parameters to the WM_KEYDOWN message are
now called nVirtKey and lKeyData, which gives much more specific information than wParam and lParam.
For each type of message, Delphi defines a record type that gives a mnemonic name to each parameter. For
example, mouse messages pass the x- and y-coordinates of the mouse event in the long parameter, one in the highorder word, and the other in the low-order word. Using the mouse-message structure, you do not have to worry about
which word is which, because you refer to the parameters by the names XPos and YPos instead of lParamLo and
lParamHi.
Dispatching Messages
When an application creates a window, it registers a window procedure with the Windows kernel. The window
procedure is the routine that handles messages for the window. Traditionally, the window procedure contains a huge
case statement with entries for each message the window has to handle. Keep in mind that "window" in this sense
means just about anything on the screen: each window, each control, and so on. Every time you create a new type
of window, you have to create a complete window procedure.
The VCL simplifies message dispatching in several ways:
Each component inherits a complete message-dispatching system.
The dispatch system has default handling. You define handlers only for messages you need to respond to
specially.
You can modify small parts of the message handling and rely on inherited methods for most processing.
The greatest benefit of this message dispatch system is that you can safely send any message to any component
at any time. If the component does not have a handler defined for the message, the default handling takes care of
it, usually by ignoring the message.
1559
Trapping Messages
Under some circumstances, you might want your components to ignore messages. That is, you want to keep the
component from dispatching the message to its handler. To trap a message, you override the virtual method
WndProc.
For VCL components, the WndProc method screens messages before passing them to the Dispatch method, which
in turn determines which method gets to handle the message. By overriding WndProc, your component gets a chance
to filter out messages before dispatching them. An override of WndProc for a control derived from TWinControl looks
like this:
procedure TMyControl.WndProc(var Message: TMessage);
begin
{ tests to determine whether to continue processing }
inherited WndProc(Message);
end;
The TControl component defines entire ranges of mouse messages that it filters when a user is dragging and
dropping controls. Overriding WndProc helps this in two ways:
It can filter ranges of messages instead of having to specify handlers for each one.
1560
It can preclude dispatching the message at all, so the handlers are never called.
The constant WM_APP represents the starting number for user-defined messages. When defining message
identifiers, you should base them on WM_APP.
Be aware that some standard Windows controls use messages in the user-defined range. These include list boxes,
combo boxes, edit boxes, and command buttons. If you derive a component from one of these and want to define
a new message for it, be sure to check the Messages unit to see which messages Windows already defines for that
control.
Or
Define the next four bytes to correspond to the Longint parameter.
4 Add a final field called Result, of type Longint.
Sending Messages
Typically, an application sends message to send notifications of state changes or to broadcast information. Your
component can broadcast messages to all the controls in a form, send messages to a particular control (or to the
application itself), or even send messages to itself.
1562
There are several different ways to send a Windows message. Which method you use depends on why you are
sending the message. The following topics describe the different ways to send Windows messages:
Broadcasting a message to all controls in a form.
Calling a control's message handler directly.
Sending a message using the Windows message queue.
Sending a message that does not execute immediately.
Then, pass this message record to the parent of all the controls you want to notify. This can be any control in the
application. For example, it can be the parent of the control you are writing:
Parent.Broadcast(Msg);
1563
You want to trigger the same response that the control makes to a standard Windows (or other) message. For
example, when a grid control receives a keystroke message, it creates an inline edit control and then sends the
keystroke message on to the edit control.
You may know what control you want to notify, but not know what type of control it is. Because you don't know
the type of the target control, you can't any of its specialized methods, but all controls have message-handling
capabilities so you can always send a message. If the control has a message handler for the message you
send, it will respond appropriately. Otherwise, it will ignore the message you send and return 0.
To call the Perform method, you do not need to create a message record. You need only pass the message identifier,
WParam, and LParam as parameters. Perform returns the message result.
For more information on the SendMessage function, see the Microsoft MSDN documentation. For more information
on writing multiple threads that may be executing simultaneously, see Coordinating threads.
Responding to Signals
The underlying widget layer emits a variety of signals, each of which represents a different type of notification. These
signals include system events (the event signal) as well as notifications that are specific to the widget that generates
them. For example, all widgets generate a destroyed signal when the widget is freed, trackbar widgets generate
a valueChanged signal, header controls generate a sectionClicked signal, and so on.
Each CLX component responds to signals from its underlying widget by assigning a method as the handler for the
signal. It does this using a special hook object that is associated with the underlying widget. The hook object is a
lightweight object that is really just a collection of method pointers, each method pointer specific to a particular signal.
1564
When a method of the CLX component has been assigned to the hook object as the handler for a specific signal,
then every time the widget generates the specific signal, the method on the CLX component gets called.
Note: The methods for each hook object are declared in the Qt unit. The methods are flattened into global routines
with names that reflect the hook object to which they belong. For example, all methods on the hook object
associated with the application widget (QApplication) begin with QApplication_hook. This flattening is
necessary so that the Delphi CLX object can access the methods of the C++ hook object.
Then for each method you want to assign to the hook object as a signal handler, do the
following:
1 Initialize the variable of type TMethod to represent a method handler for the signal.
2 Assign this variable to the hook object. You can access the hook object using the Hooks property that your
1565
Description
BeginAutoDrag
Called when the user clicks the left mouse button if the control has a DragMode of dmAutomatic.
Click
Called when the user releases the mouse button over the control.
DblClick
Called when the user double-clicks with the mouse over the control.
DoMouseWheel
DragOver
Called when the user drags the mouse cursor over the control.
KeyDown
Called when the user presses a key while the control has focus.
KeyPress
KeyString
Called when the user enters a keystroke when the system uses a multibyte character system.
1566
KeyUp
Called when the user releases a key while the control has focus.
MouseDown
Called when the user clicks the mouse button over the control.
MouseMove
Called when the user moves the mouse cursor over the control.
MouseUp
Called when the user releases the mouse button over the control.
PaintRequest
In the override, call the inherited method so that any default processes still take place.
Note: In addition to the methods that respond to system events, controls include a number of similar methods that
originate with TControl or TWidgetControl to notify the control of various events.Although these do not
respond to system events, they perform the same task as many Windows messages that are sent to VCL
controls. The following table lists some of these methods.
TWidgetControl protected methods for responding to events from controls
Method
Description
BoundsChanged
ColorChanged
CursorChanged
Called when the cursor changes shape. The mouse cursor assumes this shape when it's over this widget.
EnabledChanged
FontChanged
PaletteChanged
TabStopChanged
TextChanged
VisibleChanged
1567
QEventType_Resize,
QEventType_FocusIn,
QEventType_FocusOut:
UpdateMask;
end;
end;
Generating Qt Events
Similar to the way a VCL control can define and send custom Windows messages, you can make your CLX control
define and generate Qt system events. The first step is to define a unique ID for the event (similar to the way you
must define a message ID when defining a custom Windows message):
const
MyEvent_ID = Integer(QCLXEventType_ClxUser) + 50;
In the code where you want to generate the event, use the QCustomEvent_create function (declared in the Qt unit)
to create an event object with your new event ID. An optional second parameter lets you supply the event object
with a data value that is a pointer to information you want to associate with the event:
var
MyEvent: QCustomEventH;
begin
MyEvent := QCustomEvent_create(MyEvent_ID, self);
Once you have created the event object, you can post it by calling the QApplication_postEvent method:
QApplication_postEvent(Application.Handle, MyEvent);
For any component to respond to this notification, it need only override its EventFilter method, checking for an event
type of MyEvent_ID. The EventFilter method can retrieve the data you supplied to the constructor by calling the
QCustomEvent_data method that is declared in the Qt unit.
1568
Registering Components
Registration works on a compilation unit basis, so if you create several components in a single compilation unit, you
can register them all at once.
To register a component, add a Register procedure to the unit. Within the Register procedure, you register the
components and determine where to install them on the Tool palette.
Note: If you create your component by choosing Component
to register your component is added automatically.
The steps for manually registering a component are:
Declaring the Register procedure
Writing the Register procedure
1569
Within the Register procedure, call RegisterComponents for each component you want to add to the Tool palette. If
the unit contains several components, you can register them all in one step.
You could also register several components on the same page at once, or register components on different pages,
as shown in the following code:
1570
procedure Register;
begin
RegisterComponents('Miscellaneous', [TFirst, TSecond]);
{ two on this page... }
RegisterComponents('Assorted', [TThird]);
{ ...one on another... }
RegisterComponents(LoadStr(srStandard), [TFourth]);
{ ...and one on the Standard page }
end;
{add to system
{ new category}
1571
When linking to a property, method, or event, precede the name of the property, method, or event by the name of
the object that implements it and an underscore. For example, to link to the Text property which is implemented by
TControl, use the following:
!AL(TControl_Text,1)
To see an example of the secondary navigation topics, display the Help for any component and click on the links
labeled hierarchy, properties, methods, or events.
Each property, method, and event that is declared within the component should have a topic:
1572
A property, event, or method topic should show the declaration of the item and describe its use. Application
developers see these topics either by highlighting the item in the Object Inspector and pressing F1 or by placing the
cursor in the Code editor on the name of the item and pressing F1. To see an example of a property topic, select
any item in the Object Inspector and press F1.
The property, event, and method topics should include a K footnote that lists the name of the property, method, or
event, and its name in combination with the name of the component. Thus, the Text property of TControl has the
following K footnote:
Text,TControl;TControl,Text;Text,
The property, method, and event topics should also include a $ footnote that indicates the title of the topic, such as
TControl.Text.
All of these topics should have a topic ID that is unique to the topic, entered as a # footnote.
where ComponentClass is the name of the component class, Element is the name of the property, method, or event,
and Type is the either Property, Method, or Event
For example, for a property named BackgroundColor of a component named TMyGrid, the A footnote is
TMyGrid_BackgroundColor;BackgroundColor_Property;BackgroundColor
1573
Properties edited
TOrdinalProperty
All ordinal-property editors (those for integer, character, and enumerated properties) descend from
TOrdinalProperty.
TIntegerProperty
TCharProperty
TEnumProperty
TFloatProperty
TStringProperty
Strings.
All sets. Sets are not directly editable, but can expand into a list of set-element properties.
TClassProperty
Classes. Displays the name of the class and allows expansion of the class's properties.
TMethodProperty
TComponentProperty Components in the same form. The user cannot edit the component's properties, but can point to a
specific component of a compatible type.
TColorProperty
Component colors. Shows color constants if applicable, otherwise displays hexadecimal value. Drop
down list contains the color constants. Double-click opens the color-selection dialog box.
TFontNameProperty
Font names. The drop down list displays all currently installed fonts.
TFontProperty
Fonts. Allows expansion of individual font properties as well as access to the font dialog box.
Related method
Meaning if included
paValueList
GetValues
paSubProperties
GetProperties
paDialog
Edit
The editor can display a dialog box for editing the entire property.
paMultiSelect
N/A
The property should display when the user selects more than one
component.
paAutoUpdate
SetValue
Updates the component after every change instead of waiting for approval
of the value.
paSortList
N/A
paReadOnly
N/A
paRevertable
N/A
Enables the Revert to Inherited menu item on the Object Inspector's context
menu. The menu item tells the property editor to discard the current property
value and return to some previously established default or standard value.
paFullWidthName
N/A
The value does not need to be displayed. The Object Inspector uses its full
width for the property name instead.
paVolatileSubProperties GetProperties
The Object Inspector re-fetches the values of all subproperties any time the
property value changes.
1575
paReference
GetComponentValue The value is a reference to something else. When used in conjunction with
paSubProperties the referenced object should be displayed as sub
properties to this property.
Property Categories
In the IDE, the Object Inspector lets you selectively hide and display properties based on property categories. The
properties of new custom components can be fit into this scheme by registering properties in categories. Do this at
the same time you register the component by calling RegisterPropertyInCategory or
RegisterPropertiesInCategory. Use RegisterPropertyInCategory to register a single property. Use
RegisterPropertiesInCategory to register multiple properties in a single function call. These functions are defined in
the unit DesignIntf.
Note that it is not mandatory that you register properties or that you register all of the properties of a custom
component when some are registered. Any property not explicitly associated with a category is included in the
TMiscellaneousCategory category. Such properties are displayed or hidden in the Object Inspector based on that
default categorization.
In addition to these two functions for registering properties, there is an IsPropertyInCategory function. This function
is useful for creating localization utilities, in which you must determine whether a property is registered in a given
property category.
Registering one property at a time
Registering multiple properties at once
Specifying property categories
Using the IsPropertyInCategory function
1576
The second variation is much like the first, except that it limits the category to only those properties of the given
name that appear on components of a given type. The example below registers (into the 'Help and Hints' category)
a property named "HelpContext" of a component of the custom class TMyButton.
RegisterPropertyInCategory('Help and Hints', TMyButton, 'HelpContext');
The third variation identifies the property using its type rather than its name. The example below registers a property
based on its type, Integer.
RegisterPropertyInCategory('Visual', TypeInfo(Integer));
The final variation uses both the property's type and its name to identify the property. The example below registers
a property based on a combination of its type, TBitmap, and its name, "Pattern."
RegisterPropertyInCategory('Visual', TypeInfo(TBitmap), 'Pattern');
See the section Specifying property categories for a list of the available property categories and a brief description
of their uses.
The second variation lets you limit the registered properties to those that belong to a specific component. The list of
properties to register include only names, not types. For example, the following code registers a number of properties
into the 'Help and Hints' category for all components:
RegisterPropertiesInCategory('Help and Hints', TComponent, ['HelpContext', 'Hint',
'ParentShowHint', 'ShowHint']);
The third variation lets you limit the registered properties to those that have a specific type. As with the second
variation, the list of properties to register can include only names:
1577
See the section Specifying property categories for a list of the available property categories and a brief description
of their uses.
Purpose
Action
Properties related to runtime actions; the Enabled and Hint properties of TEdit are in this category.
Database
Properties related to database operations; the DatabaseName and SQL properties of TQuery are
in this category.
Drag, Drop, and Docking Properties related to drag-and-drop and docking operations; the DragCursor and DragKind
properties of TImage are in this category.
Help and Hints
Properties related to using online Help or hints; the HelpContext and Hint properties of TMemo are
in this category.
Layout
Properties related to the visual display of a control at design-time; the Top and Left properties of
TDBEdit are in this category.
Legacy
Properties related to obsolete operations; the Ctl3D and ParentCtl3D properties of TComboBox are
in this category.
Linkage
Properties related to associating or linking one component to another; the DataSet property of
TDataSource is in this category.
Locale
Localizable
Properties that may require modification in localized versions of an application. Many string
properties (such as Caption) are in this category, as are properties that determine the size and
position of controls.
Visual
Properties related to the visual display of a control at runtime; the Align and Visible properties of
TScrollBox are in this category.
Input
Properties related to the input of data (need not be related to database operations); the Enabled
and ReadOnly properties of TEdit are in this category.
Miscellaneous
Properties that do not fit a category or do not need to be categorized (and properties not explicitly
registered to a specific category); the AllowAllUp and Name properties of TSpeedButton are in this
category.
1578
The first variation lets you base the comparison criteria on a combination of the class type of the owning component
and the property's name. In the command line below, for IsPropertyInCategory to return True, the property must
belong to a TCustomEdit descendant, have the name "Text," and be in the property category 'Localizable'.
IsItThere := IsPropertyInCategory('Localizable', TCustomEdit, 'Text');
The second variation lets you base the comparison criteria on a combination of the class name of the owning
component and the property's name. In the command line below, for IsPropertyInCategory to return True, the
property must be a TCustomEdit descendant, have the name "Text", and be in the property category 'Localizable'.
IsItThere := IsPropertyInCategory('Localizable', 'TCustomEdit', 'Text');
The following code overrides the GetVerbCount and GetVerb methods to add two commands to the context menu.
function TMyEditor.GetVerbCount: Integer;
begin
Result := 2;
end;
function TMyEditor.GetVerb(Index: Integer): String;
begin
case Index of
0: Result := '&DoThis ...';
1: Result := 'Do&That';
end;
end;
Note: Be sure that your GetVerb method returns a value for every possible index indicated by GetVerbCount.
Implementing Commands
When the command provided by GetVerb is selected in the designer, the ExecuteVerb method is called. For every
command you provide in the GetVerb method, implement an action in the ExecuteVerb method. You can access
the component that is being edited using the Component property of the editor.
For example, the following ExecuteVerb method implements the commands for the GetVerb method in the previous
example.
procedure TMyEditor.ExecuteVerb(Index: Integer);
var
MySpecialDialog: TMyDialog;
begin
case Index of
0: begin
MyDialog := TMySpecialDialog.Create(Application);
{ instantiate the editor
if MySpecialDialog.Execute then;
{ if the user OKs the dialog...
MyComponent.FThisProperty := MySpecialDialog.ReturnValue;
{ ...use the value
MySpecialDialog.Free;
{ destroy the editor
end;
1: That;
{ call the That method
end;
end;
}
}
}
}
}
1580
procedure TMyEditor.Edit;
var
FontDlg: TFontDialog;
begin
FontDlg := TFontDialog.Create(Application);
try
if FontDlg.Execute then
MyComponent.FFont.Assign(FontDlg.Font);
finally
FontDlg.Free
end;
end;
Note: If you want a double-click on the component to display the Code editor for an event handler, use
TDefaultEditor as a base class for your component editor instead of TComponentEditor. Then, instead of
overriding the Edit method, override the protected TDefaultEditor.EditProperty method instead.
EditProperty scans through the event handlers of the component, and brings up the first one it finds. You can
change this to look a particular event instead. For example:
1581
component editor class that you have defined. For example, the following statement registers a component editor
class named TMyEditor to work with all components of type TMyComponent:
RegisterComponentEditor(TMyComponent, TMyEditor);
Place the call to RegisterComponentEditor in the Register procedure where you register your component. For
example, if a new component named TMyComponent and its component editor TMyEditor are both implemented in
the same unit, the following code registers the component and its association with the component editor.
procedure Register;
begin
RegisterComponents('Miscellaneous', [TMyComponent);
RegisterComponentEditor(classes[0], TMyEditor);
end;
1582
1583
implementation
procedure Register;
begin
RegisterComponents('Samples', [TWrapMemo]);
end;
end.
If you compile and install the new component now, it behaves exactly like its ancestor, TMemo. In the next section,
you will make a simple change to your component.
Now you can install the new component on the Tool palette and add it to a form. Note that the WordWrap property
is now initialized to False.
If you change an initial property value, you should also designate that value as the default. If you fail to match the
value set by the constructor to the specified default value, Delphi cannot store and restore the proper value.
1584
Specifying the default property value does not affect the workings of the component. You must still initialize the value
in the component's constructor. Redeclaring the default ensures that Delphi knows when to write WordWrap to the
form file.
1585
For this example, follow the general procedure for creating a component, with these
specifics:
1 Call the component's unit Shapes.
2 Derive a new component type called TSampleShape, descended from TGraphicControl.
3 Register TSampleShape on the Samples category of the Tool palette.
1586
implementation
procedure Register;
begin
RegisterComponent('Samples', [TSampleShape]);
end;
end.
The sample shape control now makes mouse and drag-and-drop interactions available to its users.
In addition, for the shape control example, you will add some properties that enable application developers to
customize the appearance of the shape at design time.
1587
In general, the appearance of a graphic control depends on some combination of its properties. The gauge control,
for example, has properties that determine its shape and orientation and whether it shows its progress numerically
as well as graphically. Similarly, the shape control has a property that determines what kind of shape it should draw.
To give your control a property that determines the shape it draws, add a property called Shape. This requires
1 Declaring the property type.
2 Declaring the property.
3 Writing the implementation method.
You can now use this type to declare a new property in the class.
In this example, the shape control sets its size to a square 65 pixels on each side.
1 Add the overridden constructor to the declaration of the component class:
type
TSampleShape = class(TGraphicControl)
public
constructor Create(AOwner: TComponent); override
end;
2 Redeclare the Height and Width properties with their new default values:
type
TSampleShape = class(TGraphicControl)
.
.
.
published
property Height default 65;
property Width default 65;
end;
3 Write the new constructor in the implementation part of the unit:
constructor TSampleShape.Create(AOwner: TComponent);
begin
inherited Create(AOwner); { always call the inherited constructor }
Width := 65;
Height := 65;
end;
1589
1590
Then, write the SetBrush and SetPen methods in the implementation part of the unit:
procedure TSampleShape.SetBrush(Value: TBrush);
begin
FBrush.Assign(Value);
end;
procedure TSampleShape.SetPen(Value: TPen);
begin
FPen.Assign(Value);
end;
- would overwrite the internal pointer for FBrush, lose memory, and create a number of ownership problems.
Because you have added a pen and a brush to the shape control, you need to initialize
them in the shape control's constructor and destroy them in the control's destructor:
1 Construct the pen and brush in the shape control constructor:
constructor TSampleShape.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
{ always call the inherited constructor }
Width := 65;
Height := 65;
FPen := TPen.Create;
{ construct the pen }
FBrush := TBrush.Create;
{ construct the brush }
end;
2 Add the overridden destructor to the declaration of the component class:
type
TSampleShape = class(TGraphicControl)
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
3 Write the new destructor in the implementation part of the unit:
destructor TSampleShape.Destroy;
begin
1591
FPen.Free;
FBrush.Free;
inherited Destroy;
end;
With these changes, the component redraws to reflect changes to either the pen or the brush.
1592
}
}
}
}
}
For the shape control, add the following declaration to the class declaration:
type
TSampleShape = class(TGraphicControl)
.
.
.
protected
procedure Paint; override;
.
.
.
end;
}
}
}
}
}
Paint is called whenever the control needs to update its image. Controls are painted when they first appear or when
a window in front of them goes away. In addition, you can force repainting by calling Invalidate, as the
StyleChanged method does.
1593
W := Width;
H := Height;
if W < H then S := W else S := H;
case FShape of
sstRectangle, sstRoundRect, sstEllipse:
begin
X := 0;
{
Y := 0;
end;
sstSquare, sstRoundSquare, sstCircle:
begin
X := (W - S) div 2;
Y := (H - S) div 2;
W := S;
{
H := S;
end;
end;
case FShape of
sstRectangle, sstSquare:
Rectangle(X, Y, X + W, Y + H);
sstRoundRect, sstRoundSquare:
RoundRect(X, Y, X + W, Y + H, S div 4, S div
sstCircle, sstEllipse:
Ellipse(X, Y, X + W, Y + H);
end;
end;
end;
1594
}
}
}
}
}
}
}
}
Customizing a grid
Customizing a Grid: Overview
The component library provides abstract components you can use as the basis for customized components. The
most important of these are grids and list boxes. The following topics describe how to create a small one month
calendar from the basic grid component, TCustomGrid:
Creating and registering the component
Publishing inherited properties
Changing initial values
Resizing the cells
Filling in the cells
Navigating months and years
Navigating days
In VCL applications, the resulting component is similar to the TCalendar component on the Samples category of the
Tool palette. See Specifying the palette page or Adding pages to the Component palette.
For this example, follow the general procedure for creating a component, with these
specifics:
1 Save the component's unit as CalSamp.
2 Derive a new component type called TSampleCalendar, descended from TCustomGrid.
3 Register TSampleCalendar on the Samples category of the Tool palette.
The resulting unit descending from TCustomGrid in a VCL application should look like this:
unit CalSamp;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Grids;
1595
type
TSampleCalendar = class(TCustomGrid)
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TSampleCalendar]);
end;
end.
If you install the calendar component now, you will find that it appears on the Samples category. The only properties
available are the most basic control properties. The next step is to make some of the more specialized properties
available to users of the calendar.
Note: While you can install the sample calendar component you have just compiled, do not try to place it on a form
yet. The TCustomGrid component has an abstract DrawCell method that must be redeclared before instance
objects can be created. Overriding the DrawCell method is described in Filling in the cells
There are a number of other properties you could also publish, but which do not apply to a calendar, such as the
Options property that would enable the user to choose which grid lines to draw.
If you install the modified calendar component to the Tool palette and use it in an application, you will find many
more properties and events available in the calendar, all fully functional. You can now start adding new capabilities
of your own design.
1596
The calendar now has seven columns and seven rows, with the top row fixed, or nonscrolling.
1597
.
end;
.
.
.
procedure TSampleCalendar.WMSize(var Message: TWMSize);
var
GridLines: Integer;
{ temporary local variable
begin
GridLines := 6 * GridLineWidth;
{ calculate combined size of all lines
DefaultColWidth := (Message.Width - GridLines) div 7;
{ set new default cell width
DefaultRowHeight := (Message.Height - GridLines) div 7;
{ and cell height
end;
}
}
}
}
Now when the calendar is resized, it displays all the cells in the largest size that will fit in the control.
In this case, the calendar control needs to override BoundsChanged so that it calculates the proper cell size to allow
all cells to be visible in the new size:
type
TSampleCalendar = class(TCustomGrid)
protected
procedure BoundsChanged; override;
.
.
.
end;
.
.
.
procedure TSampleCalendar.BoundsChanged;
var
GridLines: Integer;
{ temporary local variable }
begin
GridLines := 6 * GridLineWidth;
{ calculate combined size of all lines }
DefaultColWidth := (Width - GridLines) div 7;
{ set new default cell width }
DefaultRowHeight := (Height - GridLines) div 7;
{ and cell height }
inherited; {now call the inherited method }
end;
1598
.
procedure TSampleCalendar.DrawCell(ACol, ARow: Longint; ARect: TRect;
AState: TGridDrawState);
begin
if ARow = 0 then
Canvas.TextOut(ARect.Left, ARect.Top, ShortDayNames[ACol + 1]);
end;
1599
You'll need a method for setting the date, because setting the date requires updating the onscreen image of the
control:
type
TSampleCalendar = class(TCustomGrid)
private
procedure SetCalendarDate(Value: TDateTime);
public
property CalendarDate: TDateTime read FDate write SetCalendarDate;
.
.
.
procedure TSampleCalendar.SetCalendarDate(Value: TDateTime);
begin
FDate := Value;
{ set new date value }
Refresh;
{ update the onscreen image }
end;
To provide design-time access to the day, month, and year, you do the following:
1 Declare the three properties, assigning each a unique index number:
type
TSampleCalendar = class(TCustomGrid)
public
property Day: Integer index 3 read GetDateElement write SetDateElement;
property Month: Integer index 2 read GetDateElement write SetDateElement;
property Year: Integer index 1 read GetDateElement write SetDateElement;
.
.
.
2 Declare and write the implementation methods, setting different elements for each index value:
type
TSampleCalendar = class(TCustomGrid)
private
function GetDateElement(Index: Integer): Integer;
{ note the Index parameter }
procedure SetDateElement(Index: Integer; Value: Integer);
.
.
.
function TSampleCalendar.GetDateElement(Index: Integer): Integer;
var
1600
}
}
}
}
}
Now you can set the calendar's day, month, and year at design time using the Object Inspector or at runtime using
code. Of course, you have not yet added the code to paint the dates into the cells, but now you have the needed data.
1601
protected
procedure UpdateCalendar; virtual;
end;
.
.
.
procedure TSampleCalendar.UpdateCalendar;
var
AYear, AMonth, ADay: Word;
FirstDate: TDateTime;
{ date of the first day of the month
begin
if FDate <> 0 then
{ only calculate offset if date is valid
begin
DecodeDate(FDate, AYear, AMonth, ADay);
{ get elements of date
FirstDate := EncodeDate(AYear, AMonth, 1);
{ date of the first
FMonthOffset := 2 - DayOfWeek(FirstDate);
{ generate the offset into the grid
end;
Refresh;
{ always repaint the control
end;
}
}
}
}
}
}
2 Add statements to the constructor and the SetCalendarDate and SetDateElement methods that call the new
}
}
}
}
}
}
}
3 Add a method to the calendar that returns the day number when passed the row and column coordinates of a cell:
function TSampleCalendar.DayNum(ACol, ARow: Integer): Integer;
begin
Result := FMonthOffset + ACol + (ARow - 1) * 7;
{ calculate day for this cell }
if (Result < 1) or (Result > MonthDays[IsLeapYear(Year), Month]) then
Result := -1;
{ return -1 if invalid }
end;
1602
Now if you reinstall the calendar component and place one on a form, you will see the proper information for the
current month.
Note that you are now reusing the ADay variable previously set by decoding the date.
1603
For the calendar, add the following four methods for next and previous month and year. Each of these methods uses
the IncMonth function in a slightly different manner to increment or decrement CalendarDate, by increments of a
month or a year.
procedure TCalendar.NextMonth;
begin
CalendarDate := IncMonth(CalendarDate, 1);
end;
procedure TCalendar.PrevMonth;
begin
CalendarDate := IncMonth(CalendarDate, -1);
end;
procedure TCalendar.NextYear;
begin
CalendarDate := IncMonth(CalendarDate, 12);
end;
procedure TCalendar.PrevYear;
begin
CalendarDate := DecodeDate(IncMonth(CalendarDate, -12);
end;
Be sure to add the declarations of the new methods to the class declaration.
Now when you create an application that uses the calendar component, you can easily implement browsing through
months or years.
Navigating Days
Within a given month, there are two obvious ways to navigate among the days. The first is to use the arrow keys,
and the other is to respond to clicks of the mouse. The standard grid component handles both as if they were clicks.
That is, an arrow movement is treated like a click on an adjacent cell.
The process of navigating days consists of
Moving the selection
Providing an OnChange event
Excluding blank cells
1604
1605
Applications using the calendar component can now respond to changes in the date of the component by attaching
handlers to the OnChange event.
Now if the user clicks a blank cell or tries to move to one with an arrow key, the calendar leaves the current cell
selected.
1606
If you install the calendar component now, you will find that it appears on the Samples page. The only properties
available are the most basic control properties. The next step is to make some of the more specialized properties
available to users of the calendar.
Note: While you can install the sample calendar component you have just compiled, do not try to place it on a form
yet. The TCustomGrid component has an abstract DrawCell method that must be redeclared before instance
objects can be created. Overriding the DrawCell method is described in Filling in the cells.
1608
public
constructor Create(AOwner: TComponent); override;
{ must override to set default }
published
property ReadOnly: Boolean read FReadOnly write FReadOnly default True;
end;
.
.
.
constructor TDBCalendar.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
{ always call the inherited constructor! }
FReadOnly := True;
{ set the default value }
end;
2 Override the SelectCell method to disallow selection if the control is read-only. Use of SelectCell is explained in
Remember to add the declaration of SelectCell to the type declaration of TDBCalendar, and append the override
directive.
If you now add the calendar to a form, you will find that the component ignores clicks and keystrokes. It also fails to
update the selection position when you change the date.
1609
}
}
FUpdating := True;
try
inherited UpdateCalendar;
finally
FUpdating := False;
end;
end;
The calendar still disallows user changes, but now correctly reflects changes made in the date by changing the date
properties. Now that you have a true read-only calendar control, you are ready to add the data browsing ability.
Before you can compile the application, you need to add DB and DBCtrls to the unit's uses clause.
Declare the DataSource and DataField properties and their implementation methods, then write the methods as
"pass-through" methods to the corresponding properties of the data link class.
Now you have a complete data link, but you have not yet told the control what data it should read from the linked
field. The next section explains how to do that.
1611
1612
When MouseDown responds to a mouse-down message, the inherited MouseDown method is called only if the
control's ReadOnly property is False and the data link object is in edit mode, which means the field can be edited.
If the field cannot be edited, the code the programmer put in the OnMouseDown event handler, if one exists, is
executed.
1613
.
.
end;
2 Implement the KeyDown method:
procedure KeyDown(var Key: Word; Shift: TShiftState);
var
MyKeyDown: TKeyEvent;
begin
if not ReadOnly and (Key in [VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_END,
VK_HOME, VK_PRIOR, VK_NEXT]) and FDataLink.Edit then
inherited KeyDown(Key, Shift)
else
begin
MyKeyDown := OnKeyDown;
if Assigned(MyKeyDown) then MyKeyDown(Self, Key, Shift);
end;
end;
When KeyDown responds to a mouse-down message, the inherited KeyDown method is called only if the control's
ReadOnly property is False, the key pressed is one of the cursor control keys, and the data link object is in edit
mode, which means the field can be edited. If the field cannot be edited or some other key is pressed, the code the
programmer put in the OnKeyDown event handler, if one exists, is executed.
To reflect a change made to the value in the calendar in the field value:
1 Add an UpdateData method to the private section of the calendar component:
type
TDBCalendar = class(TSampleCalendar);
private
procedure UpdateData(Sender: TObject);
.
.
.
end;
2 Implement the UpdateData method:
1614
3 Within the constructor for TDBCalendar, assign the UpdateData method to the OnUpdateData event:
constructor TDBCalendar.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FReadOnly := True;
FDataLink := TFieldDataLink.Create;
FDataLink.OnDataChange := DataChange;
FDataLink.OnUpdateData := UpdateData;
end;
1615
To update the dataset when the user exits the control, follow these steps:
1 Add an override for the DoExit method to the TDBCalendar component:
type
TDBCalendar = class(TSampleCalendar);
private
procedure DoExit; override;
.
.
.
end;
2 Implement the DoExit method so it looks like this:
1616
1617
The Delphi "wrapper" component associated with the dialog box creates and executes the dialog box at runtime,
passing along the data the user specified. The dialog-box component is therefore both reusable and customizable.
In this section, you will see how to create a wrapper component around the generic About Box form provided in the
Delphi Object Repository.
Note: Copy the files ABOUT.PAS and ABOUT.DFM into your working directory.
There are not many special considerations for designing a dialog box that will be wrapped into a component. Nearly
any form can operate as a dialog box in this context.
1618
In the case of the About box, you do not need to return any information, so the wrapper's properties only have to
contain the information needed to display the About box properly. Because there are four separate fields in the About
box that the application might affect, you will provide four string-type properties to provide for them.
The new component now has only the capabilities built into TComponent. It is the simplest nonvisual component.
In the next section, you will create the interface between the component and the dialog box.
1619
Forms,
About;
The form unit always declares an instance of the form class. In the case of the About box, the form class is
TAboutBox, and the About unit includes the following declaration:
var
AboutBox: TAboutBox;
So by adding About to the uses clause, you make AboutBox available to the wrapper component.
The minimum implementation for Execute needs to construct the dialog box form, show it as a modal dialog box,
and return either True or False, depending on the return value from ShowModal.
1620
For example, if you created an About dialog box, made it a component, and added it to the
Tool palette, you can test it with the following steps:
1 Create a new project.
2 Place an About box component on the main form.
3 Place a command button on the form.
4 Double-click the command button to create an empty click-event handler.
5 In the click-event handler, type the following line of code:
AboutBoxDlg1.Execute;
6 Run the application.
When the main form appears, click the command button. The About box appears with the default project icon and
the name Project1. Choose OK to close the dialog box.
You can further test the component by setting the various properties of the About box component and again running
the application.
1621
which you can use to write your wizards without concerning yourself with the implementation of the interfaces. The
various services offer access to the source editor, form designer, debugger, and so on. See Obtaining Tools API
services for information about using the interfaces that expose services to your wizard.
The service and other interfaces fall into two basic categories. You can tell them apart by the prefix used for the type
name:
The NTA (native tools API) grants direct access to actual IDE objects, such as the IDE's TMainMenu object.
When using these interfaces, the wizard must use Borland packages, which also means the wizard is tied to a
specific version of the IDE. The wizard can reside in a design-time package or in a DLL that uses runtime
packages.
The OTA (open tools API) does not require packages and accesses the IDE only through interfaces. In theory,
you could write a wizard in any language that supports COM-style interfaces, provided you can also work with
the Delphi calling conventions and Delphi types such as AnsiString. OTA interfaces do not grant full access to
the IDE, but almost all the functionality of the Tools API is available through OTA interfaces. If a wizard uses
only OTA interfaces, it is possible to write a DLL that is not dependent on a specific version of the IDE.
The Tools API has two kinds of interfaces: those that you, the programmer, must implement and those that the IDE
implements. Most of the interfaces are in the latter category: the interfaces define the capability of the IDE but hide
the actual implementation. The kinds of interfaces that you must implement fall into three categories: wizards,
notifiers, and creators:
As mentioned earlier in this topic, a wizard class implements the IOTAWizard interface and possibly derived
interfaces.
A notifier is another kind of interface in the Tools API. The IDE uses notifiers to call back to your wizard when
something interesting happens. You write a class that implements the notifier interface, register the notifier with
the Tools API, and the IDE calls back to your notifier object when the user opens a file, edits source code,
modifies a form, starts a debugging session, and so on. Notifiers are covered in Notifying a wizard of IDE events .
A creator is another kind of interface that you must implement. The Tools API uses creators to create new units,
projects, or other files, or to open existing files. See Creating forms and projects for information about creator
interfaces.
Other important interfaces are modules and editors. A module interface represents an open unit, which has one or
more files. An editor interface represents an open file. Different kinds of editor interfaces give you access to different
aspects of the IDE: the source editor for source files, the form designer for form files, and project resources for a
resource file. See Working with files and editors for information about module and editor interfaces.
Description
IOTAFormWizard
IOTAMenuWizard
The four kinds of wizards differ only in how the user invokes the wizard:
A menu wizard is added to the IDE's Help menu. When the user picks the menu item, the IDE calls the wizard's
Execute function. Plain wizards offer much more flexibility, so menu wizards are typically used only for
prototypes and debugging.
1623
Form and project wizards are called repository wizards because they reside in the Object Repository. The user
invokes these wizards from the New Items dialog box. The user can also see the wizards in the object repository
(by choosing the Tools
Repository menu item). The user can check the New Form check box for a form
wizard, which tells the IDE to invoke the form wizard when the user chooses the File
New
Form menu
item. The user can also check the Main Form check box. This tells the IDE to use the form wizard as the default
form for a new application. The user can check the New Project check box for a project wizard. When the user
chooses File
New
Application, the IDE invokes the selected project wizard.
The fourth kind of wizard is for situations that don't fit into the other categories. A plain wizard does not do
anything automatically or by itself. Instead, you must define how the wizard is invoked.
The Tools API does not enforce any restrictions on wizards, such as requiring a project wizard to create a project.
You can just as easily write a project wizard to create a form and a form wizard to create a project (if that's something
you really want to do).
The following topics provide details on how to implement and install a wizard:
Implementing the wizard interfaces
Installing the wizard package
You can also register property editors, components, and so on, as part of the same package.
Remember that a design-time package is part of the main Delphi application, which means any form names must
be unique throughout the entire application and all other design-time packages. This is the main disadvantage to
using packages: you never know what someone else might name their forms.
1624
During development, install the wizard package the way you would any other design-time package: click the Install
button in the package manager. The IDE will compile and link the package and attempt to load it. The IDE displays
a dialog box telling you whether it successfully loaded the package.
Description
INTAServices
Provides access to native IDE objects: main menu, action list, image list, and tool bars.
IOTAActionServices
Performs basic file actions: open, close, save, and reload a file.
IOTACodeCompletionServices Provides access to code completion, allowing a wizard to install a custom code completion
manager.
IOTADebuggerServices
IOTAEditorServices
IOTAKeyBindingServices
IOTAKeyboardServices
IOTAKeyboardDiagnostics
IOTAMessageServices
IOTAModuleServices
IOTAPackageServices
IOTAServices
Miscellaneous services.
IOTAToDoServices
Provides access to the To-Do list, allowing a wizard to install a custom To-Do manager.
IOTAToolsFilter
IOTAWizardServices
To use a service interface, cast the BorlandIDEServices variable to the desired service using the global Supports
function, which is defined in the SysUtils unit. For example,
procedure set_keystroke_debugging(debugging: Boolean);
var
diag: IOTAKeyboardDiagnostics
begin
if Supports(BorlandIDEServices, IOTAKeyboardDiagnostics, diag) then
diag.KeyTracing := debugging;
end;
If your wizard needs to use a specific service often, you can keep a pointer to the service as a data member of your
wizard class.
The following topics discuss special considerations when working with the Tools API service interfaces:
Using native IDE objects
Debugging a wizard
1625
Be sure to load the resource by the name or ID you specify in the resource file. You must choose a color that will be
interpreted as the background color for the image. If you don't want a background color, choose a color that does
not exist in the bitmap.
1626
The menu item sets its Action property to the newly created action. The tricky part of creating the menu item is
knowing where to insert it. The example below looks for theView menu, and inserts the new menu item as the first
item in the View menu. (In general, relying on absolute position is not a good idea: you never know when another
wizard might insert itself in the menu. Future versions of Delphi are likely to reorder the menu, too. A better approach
is to search the menu for a menu item with a specific name. The simplistic approach follows for the sake of clarity.)
for I := 0 to Services.MainMenu.Items.Count - 1 do
begin
with Services.MainMenu.Items[I] do
begin
if CompareText(Name, 'ViewsMenu') = 0 then
begin
NewItem := TMenuItem.Create(nil);
NewItem.Action := NewAction;
Insert(0, NewItem);
end;
end;
end;
By adding the action to the IDE's action list, the user can see the action when customizing the toolbars. The user
can select the action and add it as a button to any toolbar. This causes a problem when your wizard is unloaded: all
the tool buttons end up with dangling pointers to the non-existent action and OnClick event handler. To prevent
access violations, your wizard must find all tool buttons that refer to its action, and remove those buttons.
1627
Debugging a Wizard
The Tools API provides you with a lot of flexibility in how your wizard interacts with the IDE. With the flexibility comes
responsibility, however. It is easy to wind up with dangling pointers or other access violations.
When writing wizards that use the native tools API, you can write code that causes the IDE to crash. It is also possible
that you write a wizard that installs but does not act the way you want it to. One of the challenges of working with
design-time code is debugging. It's an easy problem to solve, however. Because the wizard is installed in Delphi
itself, you simply need to set the package's Host Application to the Delphi executable from the Run
Parameters... menu item.
When you want (or need) to debug the package, don't install it. Instead, choose Run
Run from the menu bar.
This starts up a new instance of Delphi. In the new instance, install the already-compiled package by
choosingComponents Install Package... from the menu bar. Back in the original instance of Delphi, you should
now see the telltale blue dots that tell you where you can set breakpoints in the wizard source code. (If not, doublecheck your compiler options to be sure you enabled debugging; make sure you loaded the right package; and doublecheck the process modules to make extra sure that you loaded the .bpl file you wanted to load.)
You cannot debug into the VCL or RTL code this way, but you have full debug capabilities for the wizard itself, which
might be enough to tell what is going wrong.
The Tools API also changes interface names to try to preserve source-code compatibility. To see how this works, it
is important to distinguish between the two kinds of interfaces in the Tools API: Borland-implemented and userimplemented. If the IDE implements the interface, the name stays with the most recent version of the interface. The
new functionality does not affect existing code. The old interfaces have the old version number appended.
For a user-implemented interface, however, new member functions in the base interface require new functions in
your code. Therefore, the name tends to stick with the old interface, and the new interface has a version number
tacked onto the end.
For example, consider the message services. Delphi 6 introduced a new feature: message groups. Therefore, the
basic message services interface required new member functions. These functions were declared in a new interface
class, which retained the name IOTAMessageServices. The old message services interface was renamed to
IOTAMessageServices50 (for version 5). The GUID of the old IOTAMessageServices is the same as the GUID of
the new IOTAMessageServices50 because the member functions are the same.
Consider
IOTAIDENotifier as an example of a user-implemented interface. Delphi 5 added new overloaded functions:
AfterCompile and BeforeCompile. Existing code that used IOTAIDENotifier did not need to change, but new code
that required the new functionality had to be modified to override the new functions inherited from
IOTAIDENotifier50. Version 6 did not add any more functions, so the current version to use is IOTAIDENotifier50.
The rule of thumb is to use the most-derived class when writing new code. Leave the source code alone if you are
merely recompiling an existing wizard under a new release of Delphi.
1629
begin
Module := Svc.Modules[I];
if Supports(Module, IOTAProjectGroup, Result) then
Exit;
end;
Result := nil;
end;
The editor interfaces give you access to the editor's internal state. You can examine the source code or components
that the user is editing, make changes to the source code, components, or properties, change the selection in the
source and form editors, and carry out almost any editor action that the end user can perform.
Using a form editor interface, a wizard can access all the components on the form. Each component (including the
root form or data module) has an associated IOTAComponent interface. A wizard can examine or change most of
the component's properties. If you need complete control over the component, you can cast the IOTAComponent
interface to INTAComponent. The native component interface enables your wizard to access the TComponent
pointer directly. This is important if you need to read or modify a class-type property, such as TFont, which is possible
only through NTA-style interfaces.
can be saved) and is not backed by an existing file (so the user must save the file even if the user does not make
any changes). Other methods of the creator tell the IDE what kind of file is being created (e.g., project, unit, or form),
provide the contents of the file, or return the form name, ancestor name, and other important information. Additional
callbacks let a wizard add modules to a newly created project, or add components to a newly created form.
To create a new file, which is often required in a form or project wizard, you usually need to provide the contents of
the new file. To do so, write a new class that implements the IOTAFile interface. If your wizard can make do with
the default file contents, you can return nil from any function that returns IOTAFile.
For example, suppose your organization has a standard comment block that must appear at the top of each source
file. You could do this with a static template in the Object Repository, but the comment block would need to be
updated manually to reflect the author and creation date. Instead, you can use a creator to dynamically fill in the
comment block when the file is created.
The first step is to write a wizard that creates new units and forms. Most of the creator's functions return zero, empty
strings, or other default values, which tells the Tools API to use its default behavior for creating a new unit or form.
Override GetCreatorType to inform the Tools API what kind of module to create: a unit or a form. To create a unit,
return sUnit. To create a form, return sForm. To simplify the code, use a single class that takes the creator type as
an argument to the constructor. Save the creator type in a data member, so that GetCreatorType can return its value.
Implement NewImplSource and NewIntfSource to return the desired file contents.
TCreator = class(TInterfacedObject, IOTAModuleCreator)
public
constructor Create(const CreatorType: string);
{ IOTAModuleCreator }
function GetAncestorName: string;
function GetImplFileName: string;
function GetIntfFileName: string;
function GetFormName: string;
function GetMainForm: Boolean;
function GetShowForm: Boolean;
function GetShowSource: Boolean;
function NewFormFile(const FormIdent, AncestorIdent: string): IOTAFile;
function NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
function NewIntfSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
procedure FormCreated(const FormEditor: IOTAFormEditor);
{ IOTACreator }
function GetCreatorType: string;
function GetExisting: Boolean;
function GetFileSystem: string;
function GetOwner: IOTAModule;
function GetUnnamed: Boolean;
private
FCreatorType: string;
end;
Most of the members of TCreator return zero, nil, or empty strings. The boolean methods return true, except
GetExisting, which returns false. The most interesting method is GetOwner, which returns a pointer to the current
project module, or nil if there is no project. There is no simple way to discover the current project or the current
project group. Instead, GetOwner must iterate over all open modules. If a project group is found, it must be the only
project group open, so GetOwner returns its current project. Otherwise, the function returns the first project module
it finds, or nil if no projects are open.
function TCreator.GetOwner: IOTAModule;
var
I: Integer;
Svc: IOTAModuleServices;
Module: IOTAModule;
Project: IOTAProject;
1631
Group: IOTAProjectGroup;
begin
{ Return the current project. }
Supports(BorlandIDEServices, IOTAModuleServices, Svc);
Result := nil;
for I := 0 to Svc.ModuleCount - 1 do
begin
Module := Svc.Modules[I];
if Supports(Module, IOTAProject, Project) then
begin
{ Remember the first project module}
if Result = nil then
Result := Project;
end
else if Supports(Module, IOTAProjectGroup, Group) then
begin
{ Found the project group, so return its active project}
Result := Group.ActiveProject;
Exit;
end;
end;
end;
The creator returns nil from NewFormSource, to generate a default form file. The interesting methods are
NewImplSource and NewIntfSource, which create an IOTAFile instance that returns the file contents.
The TFile class implements the IOTAFile interface. It returns -1 as the file age (which means the file does not exist),
and returns the file contents as a string. To keep the TFile class simple, the creator generates the string, and the
TFile class simply passes it on.
TFile = class(TInterfacedObject, IOTAFile)
public
constructor Create(const Source: string);
function GetSource: string;
function GetAge: TDateTime;
private
FSource: string;
end;
constructor TFile.Create(const Source: string);
begin
FSource := Source;
end;
function TFile.GetSource: string;
begin
Result := FSource;
end;
function TFile.GetAge: TDateTime;
begin
Result := TDateTime(-1);
end;
You can store the text for the file contents in a resource to make it easier to modify, but for the sake of simplicity,
this example hardcodes the source code in the wizard. The example below generates the source code, assuming
there is a form. You can easily add the simpler case of a plain unit. Test FormIdent, and if it is empty, create a plain
unit; otherwise create a form unit. The basic skeleton for the code is the same as the IDE's default (with the addition
of the comments at the top, of course), but you can modify it any way you desire.
function TCreator.NewImplSource(
const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
1632
var
FormSource: string;
begin
FormSource :=
'{ ----------------------------------------------------------------- ' +
'%s - description'+
#13#10 +
'Copyright %y Your company, inc.'+
#13#10 +
'Created on %d'+
#13#10 +
'By %u'+
#13#10 +
' ----------------------------------------------------------------- }' +
#13#10;
return TFile.Create(Format(FormSource, ModuleIdent, FormIdent,
AncestorIdent));
}
#13#10 +
#13#10 +
The final step is to create two form wizards: one uses sUnit as the creator type, and the other uses sForm. As an
added benefit for the user, you can use INTAServices to add a menu item to the File
New menu to invoke each
wizard. The menu item's OnClick event handler can call the wizard's Execute function.
Some wizards need to enable or disable the menu items, depending on what else is happening in the IDE. For
example, a wizard that checks a project into a source code control system should disable its Check In menu item
if no files are open in the IDE. You can add this capability to your wizard by using notifiers.
Description
IOTANotifier
IOTABreakpointNotifier
IOTADebuggerNotifier
IOTAEditLineNotifier
IOTAEditorNotifier
IOTAFormNotifier
Saving a form, or modifying the form or any components on the form (or data module)
IOTAIDENotifier
IOTAMessageNotifier
IOTAModuleNotifier
IOTAThreadNotifier
IOTAToolsFilterNotifier
1633
To see how to use notifiers, consider the example in Creating forms and projects. Using module creators, the example
creates a wizard that adds a comment to each source file. The comment includes the unit's initial name, but the user
almost always saves the file under a different name. In that case, it would be a courtesy to the user if the wizard
updated the comment to match the file's true name.
To do this, you need a module notifier. The wizard saves the module interface that CreateModule returns, and uses
it to register a module notifier. The module notifier receives notification when the user modifies the file or saves the
file, but these events are not important for this wizard, so the AfterSave and related functions all have empty bodies.
The important function is ModuleRenamed, which the IDE calls when the user saves the file under a new name. The
declaration for the module notifier class is shown below:
TModuleIdentifier = class(TNotifierObject, IOTAModuleNotifier)
public
constructor Create(const Module: IOTAModule);
destructor Destroy; override;
function CheckOverwrite: Boolean;
procedure ModuleRenamed(const NewName: string);
procedure Destroyed;
private
FModule: IOTAModule;
FName: string;
FIndex: Integer;
end;
One way to write a notifier is to have it register itself automatically in its constructor. The destructor unregisters the
notifier. In the case of a module notifier, the IDE calls the Destroyed method when the user closes the file. In that
case, the notifier must unregister itself and release its reference to the module interface. The IDE releases its
reference to the notifier, which reduces its reference count to zero and frees the object. Therefore, you need to write
the destructor defensively: the notifier might already be unregistered.
constructor TModuleNotifier.Create( const Module: IOTAModule);
begin
FIndex := -1;
FModule := Module;
{ Register this notifier. }
FIndex := Module.AddNotifier(self);
{ Remember the module's old name. }
FName := ChangeFileExt(ExtractFileName(Module.FileName), '');
end;
destructor TModuleNotifier.Destroy;
begin
{ Unregister the notifier if that hasn't happened already. }
if Findex >= 0 then
FModule.RemoveNotifier(FIndex);
end;
procedure TModuleNotifier.Destroyed;
begin
{ The module interface is being destroyed, so clean up the notifier. }
if Findex >= 0 then
begin
{ Unregister the notifier. }
FModule.RemoveNotifier(FIndex);
FIndex := -1;
end;
FModule := nil;
end;
The IDE calls back to the notifier's ModuleRenamed function when the user renames the file. The function takes the
new name as a parameter, which the wizard uses to update the comment in the file. To edit the source buffer, the
1634
wizard uses an edit position interface. The wizard finds the right position, double checks that it found the right text,
and replaces that text with the new name.
procedure TModuleNotifier.ModuleRenamed(const NewName: string);
var
ModuleName: string;
I: Integer;
Editor: IOTAEditor;
Buffer: IOTAEditBuffer;
Pos: IOTAEditPosition;
Check: string;
begin
{ Get the module name from the new file name. }
ModuleName := ChangeFileExt(ExtractFileName(NewName), '');
for I := 0 to FModule.GetModuleFileCount - 1 do
begin
{ Update every source editor buffer. }
Editor := FModule.GetModuleFileEditor(I);
if Supports(Editor, IOTAEditBuffer, Buffer) then
begin
Pos := Buffer.GetEditPosition;
{ The module name is on line 2 of the comment.
Skip leading white space and copy the old module name,
to double check we have the right spot. }
Pos.Move(2, 1);
Pos.MoveCursor(mmSkipWhite or mmSkipRight);
Check := Pos.RipText('', rfIncludeNumericChars or rfIncludeAlphaChars);
if Check = FName then
begin
Pos.Delete(Length(Check));
// Delete the old name.
Pos.InsertText(ModuleName);
// Insert the new name.
FName := ModuleName;
// Remember the new name.
end;
end;
end;
end;
What if the user inserts additional comments above the module name? In that case, you need to use an edit line
notifier to keep track of the line number where the module name sits. To do this, use the IOTAEditLineNotifier
and IOTAEditLineTracker interfaces.
You need to be cautious when writing notifiers. You must make sure that no notifier outlives its wizard. For example,
if the user were to use the wizard to create a new unit, then unload the wizard, there would still be a notifier attached
to the unit. The results would be unpredictable, but most likely, the IDE would crash. Thus, the wizard needs to keep
track of all of its notifiers, and must unregister every notifier before the wizard is destroyed. On the other hand, if the
user closes the file first, the module notifier receives a Destroyed notification, which means the notifier must
unregister itself and release all references to the module. The notifier must remove itself from the wizard's master
notifier list, too.
Below is the final version of the wizard's Execute function. It creates the new module, uses the module interface and
creates a module notifier, then saves the module notifier in an interface list (TInterfaceList).
procedure DocWizard.Execute;
var
Svc: IOTAModuleServices;
Module: IOTAModule;
Notifier: IOTAModuleNotifier;
begin
{ Return the current project. }
1635
The wizard's destructor iterates over the interface list and unregisters every notifier in the list. Simply letting the
interface list release the interfaces it holds is not sufficient because the IDE also holds the same interfaces. You
must tell the IDE to release the notifier interfaces in order to free the notifier objects. In this case, the destructor tricks
the notifiers into thinking their modules have been destroyed. In a more complicated situation, you might find it best
to write a separate Unregister function for the notifier class.
destructor DocWizard.Destroy; override;
var
Notifier: IOTAModuleNotifier;
I: Integer;
begin
{ Unregister all the notifiers in the list. }
for I := list.Count - 1 downto 0 do
begin
Supports(list.Items[I], IOTANotifier, Notifier);
{ Pretend the associated object has been destroyed.
That convinces the notifier to clean itself up. }
Notifier.Destroyed;
list.Delete(I);
end;
list.Free;
FItem.Free;
end;
The rest of the wizard manages the mundane details of registering the wizard, installing menu items, and the like.
1636
Index
designing, 1481
elements of, 1480
for Web deployment, 1482
importing, 1443
licensing, 1483
registering, 1490
setting Web deployment options, 1491
testing, 1490
adding files to source control project, 223
ADO
connection object, 1196
asynchronous fetching, 1202
batch updates, 1202
connecting to data stores, 1200
connection modes, 1197
recordset objects, 1201
ADO.NET
ASP.NET, 304
Adapter Preview Editor, 473
architecture, 342
Command Text Editor, 474
CommandText Editor, 475
database applications, 538
namespace, 344
Windows Forms, 334
ADO.NET application, 400
building, 444 444
ADO command components
SQL commands, 1206
ADO components
databases, 1193
ADO connection components
connections, 1195 1196
associated commands, 1198
ADO connections
events, 1198
ADO datasets, 1200
ADT fields, 1143
aggregate fields
defining, 1131
aggregation
interfaces, 764
COM objects, 1407
ancestor classes, 1528
descendant classes, 1529
animation
AVI, 917
application files
identifying, 992
file name extensions, 992
applications
designing, 740
.NET Assemblies
using, 1454
.Net Assemblies
type libraries, 1455
and user-defined components, 1456
.NET data types
BDP.NET, 348
abstract methods
methods, 1531
action bands
creating dynamic menus, 878
creating most recently used lists, 879
hiding unused items, 879
action editor
action items, 1317
action items, 1318
default, 1320
enabling, 1319
HTTP requests, 1320
properties, 1318
action lists
actions, 880
actions
action lists, 873 884 902
action bands, 875 876
component writing using, 882 883
executing, 881
predefined, 883
using, 880
active documents, 1411
Active Forms
client applications, 1290
Active Server Objects
out-of-process servers, 1476
registering, 1477
testing and debugging, 1477
Active server page
overview, 1472
Active Server Page
creating, 1473
intrinsic objects, 1474
Active Server Pages
overview, 1410
ActiveX controls, 994
adding additional properties, 1484
connecting with property page, 1489
creating, 1481
creating a property page, 1487
customizing, 1484
deploying on the Web, 1490
description, 994
1637
compiling, 741
console, 837
creating, 740 835 835
debugging, 109
deploying, 111 991
application servers
providing data, 1261
access and launch permissions, 1292
creating, 1273
registering, 1281
structure, 1269
Web clients, 1290
architecture
database applications, 1007
BDE-based, 1148
single-tiered applications, 1009 1010
array
fields, 1144
properties, 1537
as operator
operators, 762
ASP.NET
architecture, 303
DB Web Controls, 406 406
ASP.NET application, 399
ASP.NET errors
HTTP messages, 415
ASP.NET lifecycle
ASP.NET processing, 316
Assembly Metadata Explorer, 127
audio
video, 941 942
Automation
Servers, 1407
managing events, 1467
Automation controller
dispatch interface, 1451
Automation controllers
importing a type library, 1442 1444
example, 1447
writing, 1450
Automation server
creating, 1461
Automation servers
connecting to, 1451
debugging, 1471
avi
animation, 940 940
BatchMove component
batch operations, 632
adding, 1187
error handling, 1190
batch operations
modes, 1188
mapping data types, 1189
running, 1189
batch updates
canceling, 1204
BDE
utilities, 1191
BDP.NET
data providers, 346
BDP.NET components
BDP.NET, 346
BeforeUpdateRecord event
OnGetTableName event, 1242
bevels, 917
bitmap buttons, 909
bitmap images, 557
bitmaps
setting size, 931
drawing on, 931
offscreen bitmaps, 1556
BLOBs
caching, 1150
blocking connections, 1399
bookmarks, using, 203
Borland Database Engine
deploying, 996
aliases, 1158 1159 1167
direct calls, 1150
briefcase model
mobile computing, 1015
Broadcast method
sending messages, 1563
browsing a database, 439
brush bitmap property, 924
brush color, 924
brushes, 923
building
VCL Forms hello world, 545
VCL forms applications with XML components, 547
VCL Forms menus, 546
button controls, 908
cached updates
BDE, 1173
applying, 1175
BDE-based, 1174
error handling, 1178
calculated fields, 1095
assigning values, 1129
defining, 1129
callback functions
1638
functions, 378
canceling data changes, 1094
canvas
properties and methods, 920
canvases
graphics, 1554
canvas methods
graphic objects, 925
canvas object
properties, 921
cascading deletes
database, 312
cascading updates
database, 313
Change method, 1615
check boxes, 909
data-aware, 1027
checking files into source control, 224
checking files out of source control, 225
Class diagrams
Adding shortcuts, 140
Adding mulitple elements, 141
Annotating diagrams, 142
Automated diagram layout, 143
Compartment controls, 144
Creating Associations, 146
Diagram elements, 156
Diagram options, 145
Export diagram to image, 154
Hiding and showing elements, 151
Hyperlinking diagrams, 152
links, 149
links with bending points, 150
Model View, 155
Overview window, 157
Placing node elements, 158
Printing diagrams, 159
Resizing elements, 160
Selecting diagram elements, 161
Synchronizing with Model View, 162
UML, 147
Zooming the diagram, 163
classes
defining, 757
defining new, 757
deriving, 1522
deriving new, 1528
removing dependencies, 1517
class factories
COM objects, 1404
class library
TObject branch, 746
class members
visibility, 755
ClearSessionChanges method, 309
clicks
responding to, 901
client applications
multi-tiered, 1268
calling server interfaces, 1286
connecting to servers, 1286
dropping connections, 1286
client connections, 1392
client datasets
providers, 1011 1245
adding indexes, 1231
architecture, 1012
cached updates, 1238 1239
calculated values, 1233
constraints, 1230
copying data, 1236 1236
creating, 1252
data manipulation, 1226
data packets, 1246
deleting indexes, 1232
editing, 1229
file-based applications, 1251
grouping data, 1232
internally calculated fields, 1233
limiting records in data packets, 1248
maintained aggregates, 1234 1234 1236
navigating, 1227
optional parameters, 1237
parameters, 1248
query parameters, 1248
saving changes, 1230
sharing data, 1237
sorting and indexing, 1231
types, 1239 1240
undoing changes, 1229
using providers, 1244
with internal source dataset, 1253
clients
multi-tiered applications, 1281
COM, 1407
client sockets
ClientSocket component, 1395
events, 1397
Clipboard, 827
code
snippets, 208
editing, 741
Code Editor
customizing, 194
code folding, 193
1639
1640
connections
ADO, 1194
asynchronous, 1196
timing out, 1197
constraints
fields, 1141
client datasets, 1249
custom, 1141
data integrity, 1265
server, 1141
constructors
overriding, 1584
control placement, 930
controls
up-down controls, 908
ancestor classes, 1516
data-aware, 1017 1607
graphic controls, 1516
subclassing Windows controls, 1517
windowed, 1516
conversions
string types, 791
string to PChar, 792
conversion utilities
measurements, 795 795
cool bars
adding, 900
setting appearance, 900
CORBA, 385
Janeva, 387
terminology, 385
creating
graphic component, 1586
data browsing control, 1607
data editing controls, 1612
Creating a new IntraWeb application, 1359
critical sections, 950
crosstabs
crosstabulated data, 1049
Crystal Reports
creating new reports, 391
adding to projects, 532
creating new object, 535
requirements, 392
selecting ActiveX components, 533
using ActiveX components, 392
custom interfaces
interfaces, 1469
customizing a grid, 1595
custom variants
defining, 803
binary operations, 805
data binding
DB Web Control binding, 316
Data Dictionary
field attributes, 1134
data dictionary
field attributes, 1190
data explorer
definition, 354
Data Explorer
executing SQL, 455
modifying connections, 459
data formats
assigning, 1134
data grids
default columns, 1029
customizing, 1030
drawing, 1037
event handling, 1037
runtime options DBGrid component, 1036
data links
adding to components, 1610
data modules
overview, 847
accessing from a form, 850
business rules, 850
creating and editing, 848
naming, 848
placing components, 849
remote, 851
data packets
field attributes, 1258
converting to XML documents, 1304
optional parameters, 1260
persistent fields, 1259
provider options, 1259
data providers
architecture, 345
DataRequest method
client datasets, 1250
DataSet
table mappings, 450
dataset fields, 1145
datasets
Dataset component, 1077
associating with databases, 1149
BDE-enabled, 1149
cached updates, 1176
HTML representation, 1330 1330
opening, 1079
queries, 1112 1113
resolving, 1258
states, 1078
stored procedures, 1119
tables, 1097
types, 1078 1096
unidirectional, 1211
updating, 1616
DataSnap
multi-tier database support, 997
data sources
associating with datasets, 1019
disabling and enabling, 1019
events, 1019
data types
BDP.NET, 347
DataView limitations
inserting records, 309
DataViews
runtime properties, 309
dates
calendar components, 912
DB2
BDP.NET, 348
dBASE index
specifying, 1152
DBCtrlGrid component, 1038
dbExpress
debugging, 1223
dbExpress database applications
deploying, 995 996
DBGrid component
DBGridColumns component, 1028
DB Web Controls
architecture, 306
library, 430 430
namespace, 307
DB Web interfaces, 318
DCOM
advantages, 1272
debugging
adding a watch, 174
attaching to a process, 175
breakpoints, 176
inspecting data elements, 179
modifying expressions, 183
preparation, 184
Web Application Debugger, 592
Decision Cube editor, 1053
design time information, 1054
dimension settings, 1054
memory control, 1054
decision cubes
DecisionCube component, 1053
activating dimensions, 1064
binning, 1064
1642
OnUpdateRecord, 1177
triggering events, 1547
user-defined events, 1547
exception handlers
keyword, 959
reraising, 962
scope, 961
exception handling
exceptions, 957 957
VCL, 964 965
exception objects
classes, 961
defining, 966
VCL, 964
exceptions
try blocks, 958
finally blocks, 962
handlers, 959
raising, 958
silent, 966
executable files
internationalizing, 988 989
field attributes
removing, 1135
field datalink class, 1614
field objects
fields, 1124
dynamic vs. persistent, 1125
events, 1136
methods, 1136
properties, 1132 1132 1133
fields
updating values, 1021
default values, 1140
restricting input, 1135
files, 772
copying, 776
date-time routines, 776
deleting, 774
finding, 774
manipulating, 774
reading and writing, 772
renaming, 776
TFileStream, 772
filters
specifying, 1088 1089
bookmark-based, 1201
client datasets, 1227
ranges, 1102
find references sample, 93
flat files
loading, 1252
saving, 1253
fonts
deploying, 1001
formats
internationalizing, 987
formatting data
data formats, 1135
forms
adding, 863
creating, 864
creating dynamically, 865
creating modeless, 866
displaying an auto-created, 865
layout, 863
memory management, 864
passing additional arguments, 866
retrieving data from, 867 867
retrieving data from modal, 868
using a local variable to create an instance, 866
frame
published properties, 814
frames, 871
creating, 871
sharing, 872 873
functions
conversion, 798 799
general applications
deploying, 991
Getting more information, 1047
Getting started with IntraWeb, 1358
global routines
helper objects, 768
graphics
VCL Forms applications, 537
adding to controls, 831
canvases, 1519
copying to clipboard, 934
cutting to clipboard, 934
displaying, 916
drawing on, 930
internationalizing, 986
object types, 919
overview, 918
pasting from clipboard, 935
scrollable, 930
shapes, 917
using the clipboard, 934
graphics files
loading and saving, 932
grids
non-database, 915
draw grids, 915
string grids, 916
value list editors, 916
group boxes
radio groups, 913
header controls, 914
headers
HTTP, 1309
SOAP, 1384 1390
hello world
VCL Forms, 674 680 723
help
Delphi for .NET, 63
.NET Framework, 64
borland site, 64
quick start guide, 64
typographic, 64
helper applications, 994
Help files for components, 1572
Help system
Man pages, 853 854
customizing, 860
Help Manager, 857 858
Help viewers, 854 855 855 856 856
IHelpSystem, 859
TApplication, 859
TApplication (VCL), 859
TControl (VCL), 859
hints
help, 915
History Manager, 209
host environments
programming, 999
hot key controls, 908
HTML
producing, 1325
HTML-transparent tags, 1326
HTML commands
database information, 1330
HTML documents
databases and, 1329
HTML tag editor
editing HTML tags, 434
HTTP
advantages, 1272
overview, 1310
requests, 1310 1311
HTTP messages
processing, 1318
content, 1323 1324
headers, 1321 1323
responding to, 1323
response content, 1325
sending, 1325
types, 1319
1645
BDP.NET, 349
components, 360
InterBase components
getting started, 360
Interfaces
BDP.NET, 347
interfaces, 759
Automation, 1468
dispatch, 1468
invokable, 1374
polymorphism, 760
properties, 1538
remote data modules, 1277
reusing code, 763
TInterfacedObject, 762
internal errors, 181
international applications, 981
bi-directional, 984 984 985 985
localizing, 107
internationalization
definition, 981
Internationalization, 982
InternetExpress page producers
Web pages, 1294
templates, 1296
Internet palette page
Web server applications, 1307
invokable interfaces
non-scalar types, 1375 1376
calling, 1387
IObjectContext
resource management, 1494
IP addresses
hosts vs., 1393
ISAPI applications
debugging, 1314
javascript libraries, 1291
key-down messages, 1613
labels
static text controls, 906
displaying data, 1023
license requirements
software license requirements, 1001
line drawing, 925
refining, 938
lines and polylines drawing, 925
list boxes, 911
data-aware, 1025
data-aware data, 1024
listening connections, 1392
lists, 779
1646
controls, 910
operations, 779
persistant, 780
string, 781
list views, 912
loading images
files, 1555
localization
definition, 981
locking objects, 950
logical data types
BDP.NET, 348
logins
requiring, 1347
long string routines
strings, 787
lookup fields
defining, 1130
lookup list boxes
lookup combo boxes, 1026
macro
recording, 198
main form
hiding, 862
marshaling
mechanism, 1406
master-detail
DataViews, 309
master/detail relationships
tables, 1106
multi-tiered applications, 1278
queries, 1116
master/detail tables
unidirectional datasets, 1219
MDI applications
SDI applications, 835
multiple document interface, 836
measurements
adding units, 796 796
memo fields
displaying and editing, 1023
displaying with format information, 1024
memory management
interfaced objects, 765 765 766
Menu Designer
opening, 885
context menu, 890
menu items
disabling, 829
menus
VCL Forms, 543 546 686 690
1648
Perform method
sending messages, 1563
persistent columns
creating, 1031
adding buttons, 1034
deleting in Columns editor, 1031
reordering, 1032
persistent connections, 1162
persistent fields
field objects, 1125
creating, 1126
defining, 1127
deleting, 1131
ordering, 1127
picture
loading from a file, 932
saving to file, 932
pictures
replacing, 933
graphics, 1554
pixels
reading and setting, 925
placing bitmap images, 720
placing project into source control, 228
pointers
classes, 1532
polygons, 926
polylines, 926
popup menus, 830
porting
VCL.NET porting, 339
ports
services and, 1392
PostMessage method
sending messages, 1564
procedure
Register, 1570
procedures
interfaces, 761
progress bars, 915
projects
type of, 57
additional projects, 59
properties
methods, 744
component writing, 1533 1534
events, 1571
setting, 132 815
storing properties, 1539
types, 1534
property editors, 814
creating, 1573
1649
property page
adding controls, 1488
associating with ActiveX controls, 1488
updating, 1489
property page wizard
creating a new property page, 1488
protected methods
methods, 1551
protocols
Internet, 1308
providers
fetching data, 1257
custom events, 1264
error handling, 1264
updating data, 1261
XML, 1304
queries
parameters, 1114
datasets, 635
executing, 1118
heterogenous, 1155 1155
preparing, 1117
setting parameters at design time, 1115
setting parameters at runtime, 1116
unidirectional cursors, 1118
update objects, 1180 1181
updating a read-only result set, 1156
radio buttons, 910
data-aware, 1027
ranges
records, 1102
applying, 1105
modifying, 1105
Rave component overview, 1044
Rave Reports
creating new reports, 391
creating reports, 391
getting started, 1043
overview, 1043
Tools, 650
Rave Visual Designer, 1044
read
write, 1535
ReadOnly property
FReadOnly, 1612
records
navigating and manipulating, 1040
adding, 1092
changing, 1094
deleting, 1093
filters, 1087
finding, 1085
iterating through, 1082
marking, 1084
navigating, 1080 1090
posting, 1093
refreshing, 1250
specifying ranges, 1102
updating, 1241
rectangle drawing, 926
refactoring
preview, 169 169
procedures, 185 185
reference fields, 1146
references
projects, 116
referential integrity
stored procedures, 1007
registering components
Tool palette, 1519
RegisterPropertyEditor
registering property editors, 1576
Registry
system registry, 776
remotable objects
using, 1377
remote connections
using DCOM, 1283
brokering, 1285
managing, 1285
using HTTP, 1284
using SOAP, 1284
using TCP/IP, 1283
remote data modules
setting up, 1275
creating, 1275 1276 1276
rename
renaming, 722
rename symbol
refactoring, 171 171
resizing
dynamic, 1000
resolving
multiple tables, 467
influencing generated SQL, 1262
resource dispensers, 1495
resource DLLs
locales, 987
resource files
menus, 895
resources
isolating, 987
releasing, 1497
Responding to data changes, 1611
response messages
1650
status, 1324
response templates
HTML templates, 1325
Resume method
Suspend method, 953
reusing code
techniques, 870
rich edit controls
memo controls, 905
rounded rectangles, 926
rubber banding example, 936
Running the completed application, 1362
SafeArrays, 1427
safecall
properties, 1485
events, 1486
sample
ASP.NET hello world, 403
SCC API
source control systems, 262
scope
objects, 754
screen refreshing, 919
scroll bars, 826
controls, 907
scroll boxes, 913
SDI applications
single document interface, 836
search
Goto methods, 1100
Find methods, 1101
partial keys, 1101
repeating or extending, 1101
specifying current record, 1101
search criteria
index-based searches, 1085
searching for data, 1086
SelectAll, 828
sending messages
overview, 1562
SendMessage method
sending messages, 1564
server connections, 1393
server sockets
ServerSocket component, 1396
events, 1397
service applications
Application object, 838
debugging, 843
threads, 840
services
sockets, 1391
sessions
Session component, 1160
activating, 1161
getting information, 1169
in Web applications, 1329
multiple, 1170
naming, 1170
shape drawing, 926
Shared property manager, 1496
signal handlers
custom, 1565
signals
responding to, 1564
SOAP
advantages, 1273
servers, 1379
SOAP application wizard
using, 1380
socket components
Windows socket objects, 1394
Sockets
advantages, 1272
sockets
TCP/IP protocol, 1391
describing, 1393
errors, 1397
event handling, 1397
reading and writing, 1398
types of connections, 1392
sort order
specifying, 1099
source control, 71
Commit Browser, 260
configuring, 74
configuring providers, 226
pulling project from, 230
removing files from, 232
synchronizing files, 75
undoing check out, 259
speed button
adding to a panel, 897
assigning a glyph, 897
creating a group, 898
setting the initial condition, 897
speed buttons, 909
splitter control
resizing, 908
SQL
executing commands, 1072 1216
metadata commands, 1218
specifying commands, 1217
1651
SQL statements
passthrough, 1172
standard events
events, 1546
StarTeam
embedded client, 71
active process item, 243
added features, 72
adding files, 233
advanced features, 72
checking in files, 235
checking out files, 237
comparing file revisions, 239
configuring, 240
finding files, 244
locking and unlocking files, 246
merging files, 247
migrating from SCC, 248
placing projects, 250
pulling projects from, 252
removing files, 253
reverting files, 254
updating projects, 255
version control support, 72
StarTeam Client
launching, 245
status bars, 915
stored procedures
parameters, 1119
binding parameters, 1157
datasets, 643
executing, 1122
multiple result sets, 1122
Oracle overloaded, 1157
preparing, 1122
streams, 768
copying data, 770
position, 771
reading and writing data, 769
string lists
loading and saving, 781
adding to, 784
associated objects, 785
copying, 682
counting, 784
creating, 782
deleting from, 785
finding strings, 784
graphical objects, 832
iterating through, 704
manipulating, 783
strings
adding and sorting, 682 684 688 704
accessing in a string list, 784
selecting, 828
setting alignment, 826
text controls, 904
text viewing controls
controls, 905
TForm
forms, 862
thread-local variables
threadvar, 948
thread functions
Execute method, 719
thread objects
New Thread Object dialog, 944
threads
multi-threaded applications, 944
avoiding simultaneous access, 949
clean-up code, 949
constructors, 945
exceptions, 948
Executing, 953
priority, 953
sharing memory, 951
termination, 948
VCL objects, 715
waiting, 951 951 952
TIniFile
using, 777
TNotifyEvent
click events, 1548
to-do lists
overview, 61
planning, 137
toggle buttons, 898
toolbar
Type Library editor, 1419
toolbars
customizing, 123
adding hidden, 901
adding using a panel component, 896
adding using the toolbar component, 898
designing, 896
hiding and showing, 901
tool buttons
adding, 899
assigning a menu to, 901
assigning images, 899
creating groups, 899 900
setting appearance and initial conditions, 899
tool changing with speed buttons, 928
Tool Palette
components, 115
Tool palette
tasks, 1425
type library
creating a new, 1433
adding a CoClass, 1436
adding a module, 1438
adding an alias, 1437
adding an enumeration, 1437
adding an interface, 1434
adding a record or union, 1437
adding CoClass members, 1436
adding properties and methods, 1435
apply updates dialog, 1438
deploying, 1440
enabling simple data binding in an ActiveX control,
1486
modifying an interface, 1434
opening an existing, 1433
refresh, 1439
registering, 1439
saving, 1439
saving and registering information, 1438
Type Library editor, 1418
description, 1419
information pages, 1421 1423
Object list pane, 1420
status bar, 1421
supported types, 1426
UDDI
Web Services, 1383
unidirectional datasets
datasets, 1210
accessing metadata, 1220
defining queries, 1214
defining record sets, 1213
executing SQL commands, 1217
opening, 1215
representing tables, 1214
stored procedures, 1215
units
linking, 168 168
unit tests, 264
unmanaged code, 59
unmanaged functions
Win32 API, 374
unnamed threads
naming, 954
update errors
reconciling, 1243
UpdateObject
property pages, 1489
update objects
TUpdateSQL component, 1179
accessing queries, 1186
1654
methods, 1552
visual feedback, 914
Web Application Debugger, 1313
Web Application object
Web Broker, 1316
Web applications
deploying, 997 997
multi-tiered, 1288
Web Application Support
Win32, 591
WebBroker, 591
web browser
VCL Forms, 730
Web client, 1322
Web data modules
Web applications, 1336
Web items
properties, 1295
Web modules
data modules, 1315 1315 1316 1334
Web dispatcher, 1317
Web page editor, 1294
Web page modules
Web applications, 1335
Web request properties, 1322
Web server applications
Web Broker applications, 845
adapters, 1336
applications, 846
architecture, 1317
creating, 1333 1335 1337 1338 1339 1340
1341 1357
debugging, 1313
types, 1311
WebSnap, 1307
WebSnap applications, 846
web service clients
porting, 340
Web Services
ASP.NET, 304
adding, 1381
client, 562
clients, 1387
exceptions, 1386
importing, 1382
using, 1373
web services
ASP.NET, 304
architecture, 325
client support, 330
discovery, 329
files, 327
1655
namespaces, 331
porting Win32 to .NET, 422 422
prerequisites, 325
protocol, 328
scenarios, 326
server support, 331
service description, 329
service transport, 329
web references, 395 395
xml messaging, 329
WebSnap, 591
access rights, 1347
adapter dispatcher, 1352
adapter requests and responses, 1353
adding login support, 1343
dispatcher components, 1351
dispatching action items, 1355
dispatching requests and responses, 1351
hello world, 734
login pages, 1345
login support, 1343
page dispatcher, 1355
script objects, 1350
server-side scripting, 1349
sessions service, 1344
tutorial, 1342
WebSnap components, 1334
wide character routines, 786
wide characters
Unicode characters, 983
windowed controls
docking, 824
Windows Forms
architecture, 333
hello world, 655
menus, 579
namespace, 334
Windows Forms application, 577
building, 654
Windows Forms hello world
building, 578
Windows versions, 1001
Windows XP
themes, 902
wizards
transactional object, 1503
debugging, 1628
implementing, 1624
installing, 1624
working with files and editors, 1629
writing, 1623
Writing an event handler for the button, 1361
WSDL
generating, 1386
importing, 1387
XML
database applications, 1298
Data Binding wizard, 1369 1370
XML and authentication
XML caching, 314
XML brokers, 1292
XML Document
using Data Binding wizard, 1371
XML documents
data packets, 1298
components, 1367
converting to data packets, 1302
XML files
XML advantages, 314
DBWeb XML files, 411 411
XML mapper
defining, 1300
XML Nodes
working with, 1367
1656