This chapter describes the properties, events, and methods common to the TField object and its descendants. Descendants of TField represent individual database columns in datasets. This chapter also describes how to use descendant field components to control the display and editing of data in your applications.
You never use a TField component directly in your applications. By default, when you first place a dataset in your application and open it, Delphi automatically assigns a dynamic, data-type-specific descendant of TField to represent each column in the database table(s). At design time, you can override dynamic field defaults by invoking the Fields editor to create persistent fields that replace these defaults.
The following table lists each descendant field component, its standard purpose, and, where appropriate, the range of values it can represent:
This chapter discusses the properties and methods all field components inherit from TField. In many cases, TField declares or implements standard functionality that the descendant objects override. When several descendant objects share overridden functionality, that functionality is also described in this chapter and noted for your convenience. For complete information about individual field components, see the online VCL Reference.
Like all Delphi data access components, field components are nonvisual. Field components are also not directly visible at design time. Instead they are associated with a dataset component and provide data-aware components such as TDBEdit and TDBGrid access to database columns through that dataset.
Generally speaking, a single field component represents the characteristics of a single column in a database field, such as its data type and size. It also represents the field's display characteristics, such as alignment, display format, and edit format. Finally, as you scroll from record to record within a dataset, a field component also enables you to view and change the value for that field in the current record. For example, a TFloatField component has four properties that directly affect the appearance of its data:
Field components have many properties in common with one another (such as DisplayWidth and Alignment), and they have properties specific to their data types (such as Precision for TFloatField). Each of these properties affect how data appears to an application's users on a form. Some properties, such as Precision, can also affect what data values the user can enter in a control when modifying or entering data.
All field components for a dataset are either dynamic (automatically generated for you based on the underlying structure of database tables), or persistent (generated based on specific field names and properties you set in the Fields editor). Dynamic and persistent fields have different strengths and are appropriate for different types of applications. The following sections describe dynamic and persistent fields in more detail and offer advice on choosing between them.
Dynamically generated field components are the default. In fact, all field components for any dataset start out as dynamic fields the first time you place a dataset on a data module, associate the dataset with a database, and open it. A field component is dynamic if it is created automatically based on the underlying physical characteristics of the columns in one or more database tables accessed by a dataset. Delphi generates one field component for each column in the underlying tables or query. The exact TField descendant created for each column in an underlying database table is determined by field type information received from the Borland Database Engine (BDE) or (in multi-tiered applications) a provider component.
A field component's type determines its properties and how data associated with that field is displayed in data-aware controls on a form. Dynamic fields are temporary. They exist only as long as a dataset is open.
Each time you reopen a dataset that uses dynamic fields, Delphi rebuilds a completely new set of dynamic field components for it based on the current structure of the database tables underlying the dataset. If the columns in those database tables are changed, then the next time you open a dataset that uses dynamic field components, the automatically generated field components are also changed to match.
Use dynamic fields in applications that must be flexible about data display and editing. For example, to create a database exploration tool like the SQL Explorer, you must use dynamic fields because every database table has different numbers and types of columns. You might also want to use dynamic fields in applications where user interaction with data mostly takes place inside grid components and you know that the database tables used by the application change frequently.
To use dynamic fields in an application:
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.
By default, dataset fields are dynamic. Their properties and availability are automatically set and cannot be changed in any way. To gain control over a field's properties and events so that you can set or change the field's visibility or display characteristics at design time or runtime, create new fields based on existing fields in a dataset, or validate data entry, you must create persistent fields for the dataset.
At design time, you can--and should--use 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.
Creating persistent field components offers the following advantages. You can:
A persistent field is one that Delphi generates based on field names and properties you specify in the Fields editor. 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 choose Add Fields from 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 data appearance in other ways. For example, you can use the Data Dictionary to assign field attributes to a field component. You can also control the appearance of columns in data-aware grids. For more information about the Data Dictionary, see "Creating attribute sets for field components". To learn about controlling column appearance in grids, see "Creating a customized grid".
Persistent field components created with the Fields editor provide efficient, readable, and type-safe programmatic access to underlying data. Using persistent field components guarantees that each time your application runs, it always uses and displays the same columns, in the same order even if the physical structure of the underlying database has changed. Data-aware components and program code that rely on specific fields always work as expected. If a column on which a persistent field component is based is deleted or changed, Delphi generates an exception rather than running the application against a nonexistent column or mismatched data.
To create persistent fields for a dataset:
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 enable you to 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.
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.
The Add Fields dialog box closes, and the fields you selected appear in the Fields editor list box. Fields in the Fields editor list box are persistent. If the dataset is active, note, too, that the Next and Last navigation buttons above the list box are enabled.
From now on, each time you open the dataset, Delphi no longer creates dynamic field components for every column in the underlying database. Instead it only creates persistent components for the fields you specified.
Each time you open the dataset, Delphi verifies that each non-calculated persistent field exists or can be created from data in the database. If it cannot, it raises an exception warning you that the field is not valid, and does not open the dataset.
The order in which persistent field components are listed in the Fields editor list box is the default order in which the fields appear in a data-aware grid component. You can change field order by dragging and dropping fields in the list box.
To change the order of fields:
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.
Besides making existing dataset fields into persistent fields, you can also create special persistent fields as additions to or replacements of the other persistent fields in a dataset. The following table lists the types of additional fields you can create:
These types of persistent fields are only for display purposes. The data they contain at runtime are not retained either because they already exist elsewhere in your database, or because they are temporary. The physical structure of the table and data underlying the dataset is not changed in any way.
To create a new persistent field component, invoke the context menu for the Fields editor and choose New field. The New Field dialog box appears.
The New Field dialog box contains three group boxes: Field properties, Field type, and Lookup definition.
The Field type radio group enables you to 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're working with a TClientDataSet component, you can also create InternalCalc fields.
The Field properties group box enables you to enter general field component information. Enter the component's field name in the Name edit box. The name you enter here corresponds to the field component's FieldName property. Delphi 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 contains the identifier by which you refer to the field component in your source code). Delphi discards anything you enter directly in the Component edit box.
The Type combo box in the Field properties group enables you to 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. The Size edit box enables you 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 Lookup definition group box is only used to create lookup fields. For more information, see "Defining a lookup field".
A data field replaces an existing field in a dataset. For example, for programmatic reasons you might want to replace a TSmallIntField with a TIntegerField. Because you cannot change a field's data type directly, you must define a new field to replace it.
Important: Even though you define a new field to replace an existing field, the field you define must derive its data values from an existing column in a table underlying a dataset.
To create a replacement data field for a field in a table underlying a dataset, follow these steps:
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".
A calculated field displays values calculated at runtime by a dataset's OnCalcFields event handler. For example, you might create a string field that displays concatenated values from other fields.
To create a calculated field in the New Field dialog box:
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".
If you are working with a client dataset or query component, you can also create an InternalCalc field. You create and program an internally calculated field just like you do a calculated field. For 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. To create an InternalCalc field, select the InternalCalc radio button in the Field type group.
After you define a calculated field, you must write code to calculate its value. Otherwise, it always has a null value. Code for a calculated field is placed in the OnCalcFields event for its dataset.
To program a value for a calculated field:
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;
A lookup field is a read-only field that displays values at runtime based on search criteria you specify. In its simplest form, a lookup field is passed the name of an existing field to search on, a field value to search for, and a different field in a lookup dataset whose value it should display.
For example, consider a mail-order application that enables an operator to use a lookup field to determine automatically the city and state that correspond to the zip code a customer provides. The column to search on might be called ZipTable.Zip, the value to search for is the customer's zip code as entered in Order.CustZip, and the values to return would be those for the ZipTable.City and ZipTable.State columns of the record where the value of ZipTable.Zip matches the current value in the Order.CustZip field.
To create a lookup field in the New Field dialog box:
When you design and run your application, lookup field values are determined before calculated field values are calculated. You can base calculated fields on lookup fields, but you cannot base lookup fields on calculated fields. You can use the LookupCache property to hone this behavior. LookupCache determines whether the values of a lookup field are cached in memory when a dataset is first opened, or looked up dynamically every time the current record in the dataset changes.
Set LookupCache to True to cache the values of a lookup field when the LookupDataSet is unlikely to change and the number of distinct lookup values is small. Caching lookup values can speed performance, because the lookup values for every set of LookupKeyFields values are preloaded when the DataSet is opened. When the current record in the DataSet changes, the field object can locate its Value in the cache, rather than accessing the LookupDataSet. This performance improvement is especially dramatic if the LookupDataSet is on a network where access is slow.
Tip: You can use a lookup cache to provide lookup values programmatically rather than from a secondary dataset. Create a TLookupList object at runtime, and use its Add method to fill it with lookup values. Set the LookupList property of the lookup field to this TLookupList object and set its LookupCache property to True. If the other lookup properties of the field are not set, the field will use the supplied lookup list without overwriting it with values from a lookup dataset.
If every record of DataSet has different values for KeyFields, the overhead of locating values in the cache can be greater than any performance benefit provided by the cache. The overhead of locating values in the cache increases with the number of distinct values that can be taken by KeyFields.
If LookupDataSet is volatile, caching lookup values can lead to inaccurate results. Call tRefreshLookupLis to update the values in the lookup cache. RefreshLookupList regenerates the LookupList property, which contains the value of the LookupResultField for every set of LookupKeyFields values.
When setting LookupCache at runtime, call RefreshLookupList to initialize the cache.
An aggregate field displays values from a maintained aggregate in a client dataset. An aggregate is a calculation that summarizes the data in a set of records.
To create an aggregate field in the New Field dialog box:
Once a persistent TAggregateField is created, a TDBText control can be bound to the aggregate field. The TDBText control will then display the value of the aggregate field that is relevant to the current record of the underlying client data set.
Deleting a persistent field component is useful for accessing a subset of available columns in a table, and for defining your own persistent fields to replace a column in a table. To remove one or more persistent field components for a dataset:
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 re-create persistent field components 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.
You can set properties and customize events for persistent field components at design time. Properties control the way a field is displayed by a data-aware component, for example, whether it can appear in a TDBGrid, or whether its value can be modified. Events control what happens when data in a field is fetched, changed, set, or validated.
To set the properties of a field component or write customized event handlers for it, select the component in the Fields editor, or select it from the component list in the Object Inspector.
To edit the display properties of a selected field component, switch to the Properties page on the Object Inspector window. The following table summarizes display properties that can be edited.
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".
You can use and manipulate the properties of field component at runtime. For example, the following code sets the ReadOnly property for the CityStateZip field in the Customers table to True:
CustomersCityStateZip.ReadOnly := True;
And this statement changes field ordering by setting the Index property of the CityStateZip field in the Customers table to 3:
CustomersCityStateZip.Index := 3;
When several fields in the datasets used by your application share common formatting properties (such as Alignment, DisplayWidth, DisplayFormat, EditFormat, MaxValue, MinValue, and so on), it is more convenient to set the properties for a single field, then store those properties as an attribute set in the Data Dictionary. Attribute sets stored in the data dictionary can be easily applied to other fields.
To create an attribute set based on a field component in a dataset:
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.
Note: You can also create attribute sets directly from the SQL Explorer. When you create an attribute set from the data dictionary, it is not applied to any fields, but you can specify two additional attributes: a field type (such as TFloatField, TStringField, and so on) and a data-aware control (such as TDBEdit, TDBCheckBox, and so on) that is automatically placed on a form when a field based on the attribute set is dragged to the form. For more information, see the online help for the SQL Explorer.
When several fields in the datasets used by your application share common formatting properties (such as Alignment, DisplayWidth, DisplayFormat, EditFormat, MaxValue, MinValue, and so on), and you have saved those property settings as attribute sets in the Data Dictionary, you can easily apply the attribute sets to fields without having to recreate the settings manually for each field. In addition, if you later change the attribute settings in the Data Dictionary, those changes are automatically applied to every field associated with the set the next time field components are added to the dataset.
To apply an attribute set to a field component:
Important: 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 to multi-select field components within a dataset to which to reapply attributes.
If you change your mind about associating an attribute set with a field, you can remove the association by following these steps:
Important: 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.
The EditMask property provides a way to control the type and range of values a user can enter into a data-aware component associated with TStringField, TDateField, TTimeField, and TDateTimeField components. You can use existing masks, or create your own. The easiest way to use and create edit masks is with the Input Mask editor. You can, however, enter masks directly into the EditMask field in the Object Inspector.
Note: For TStringField components, the EditMask property is also its display format.
To invoke the Input Mask editor for a field component:
The Input Mask edit box enables you to create and edit a mask format. The Sample Masks grid lets you select from predefined masks. If you select a sample mask, the mask format appears in the Input Mask edit box where you can modify it or use it as is. You can test the allowable user input for a mask in the Test Input edit box.
The Masks button enables you to load a custom set of masks--if you have created one--into the Sample Masks grid for easy selection.
Delphi provides built-in display and edit format routines and intelligent default formatting for TFloatField, TCurrencyField, TIntegerField, TSmallIntField, TWordField, TDateField, TDateTimeField, and TTimeField components. To use these routines, you need do nothing.
Default formatting is performed by the following routines:
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. For more information about setting field component properties at runtime, see "Setting field component properties at runtime".
Like most components, field components have event handlers associated with them. By writing these handlers you can control events that affect data entered in fields through data-aware controls. The following table lists the events associated with field components:
OnGetText and OnSetText events are primarily useful to programmers who want to do custom formatting that goes beyond the built-in formatting functions. OnChange is useful for performing application-specific tasks associated with data change, such as enabling or disabling menus or visual controls. OnValidate is useful when you want to control data-entry validation in your application before returning values to a database server.
To write an event handler for a field component:
Field components methods available at runtime enable you to convert field values from one data type to another, and enable you to set focus to the first data-aware control in a form that is associated with a field component.
Controlling the focus of data-aware components associated with a field is important when your application performs record-oriented data validation in a dataset event handler (such as BeforePost). Validation may be performed on the fields in a record whether or not its associated data-aware control has focus. Should validation fail for a particular 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 the entries for TField and its descendants in the online VCL Reference.
Data-aware controls such as TDBEdit and TDBGrid automatically display the values associated with field components. If editing is enabled for the dataset and the controls, data-aware controls can also send new and changed values to the database. In general, the built-in properties and methods of data-aware controls enable them to connect to datasets, display values, and make updates without requiring extra programming on your part. Use them whenever possible in your database applications. For more information about data-aware control, see "Using data controls."
Standard controls can also display and edit database values associated with field components. Using standard controls, however, may require additional programming on your part.
An application can access the value of a database column through the Value property of a field component. For example, the following statement assigns the value of the CustomersCompany field to the text in a TEdit control:
Edit3.Text := CustomersCompany.Value;
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 functions for handling conversions.
Note: You can also use variants to access and set field values. Variants are a new and flexible data type. For more information about using variants to access and set field values, see "Accessing field values with the default dataset property".
Conversion functions attempt to convert one data type to another. For example, the AsString function converts numeric and Boolean values to string representations. The following table lists field component conversion functions, and which functions are recommended for field components by field-component type:
Note that the AsVariant method is recommended to translate among all data types. When in doubt, use AsVariant.
In some cases, conversions are not always possible. For example, AsDateTime can be used to convert a string to a date, time, or datetime format only if the string value is in a recognizable datetime format. A failed conversion attempt raises an exception.
In some other cases, conversion is possible, but the results of the conversion are not always intuitive. For example, what does it mean to convert a TDateTimeField value into a float format? AsFloat converts the date portion of the field to the number of days since 12/31/1899, and it converts the time portion of the field to a fraction of 24 hours. Table 19.9 lists permissible conversions that produce special results:
Converts "True," "False," "Yes," and "No" to Boolean. Other values raise exceptions. | |
Converts date to number of days since 12/31/1899, time to a fraction of 24 hours. | |
In other cases, conversions are not possible at all. In these cases, attempting a conversion also raises an exception.
You use a conversion function as you would use any method belonging to a component: append the function name to the end of the component name wherever it occurs in an assignment statement. Conversion always occurs before an actual 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);
An exception occurs if an unsupported conversion is performed at runtime.
The preferred method for accessing a field's value is to use variants with the FieldValues property. For example, the following statement puts the value of an edit box into the CustNo field in the Customers table:
Customers.FieldValues['CustNo'] := Edit2.Text;
For more information about variants, see the online help.
You can access the value of a field with the Fields property of the dataset component to which the field belongs. Accessing field values with a dataset's Fields property is useful when you need to iterate over a number of columns, or if your application works with tables that are not available to you at design time.
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 the field component's conversion routine. For more information about field component conversion functions, see "Converting field values".
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;
You can also access the value of a field with a dataset's FieldByName method. This method is useful when you know the name of the field you want to access, but do not have access to the underlying table at design time.
To use FieldByName, you must know the dataset and name of the field you want to access. You pass the field's name as an argument to the method. To access or change the field's value, convert the result with the appropriate field component conversion function, such as AsString or AsInteger. For example, the following statement assigns the value of the CustNo field in the Customers dataset to an edit control:
Edit2.Text := Customers.FieldByName('CustNo').AsString;
Conversely, you can assign a value to a field:
begin
Customers.Edit;
Customers.FieldByName('CustNo').AsString := Edit2.Text;
Customers.Post;
end;
If your application uses TClientDataSet or administers a dataset that is the source dataset for a TProvider component on an application server, and you encounter difficulties when updating records, you can use the CurValue property to examine the field value in the record causing problems. CurValue represents the current value of the field component including changes made by other users of the database.
Use CurValue to examine the value of a field when a problem occurs in posting a value to the database. If the current field value is causing a problem, such as a key violation, when posting the value to the database, an OnReconcileError occurs. In an OnReconcileError event handler, NewValue is the unposted value that caused the problem, OldValue is the value that was originally assigned to the field before any edits were made, and CurValue is the value that is currently assigned to the field. CurValue may differ from OldValue if another user changed the value of the field after OldValue was read.
You can specify how a default value for a field should be calculated at runtime using the DefaultExpression property. DefaultExpression can be any valid SQL value expression that does not refer to field values. If the expression contains literals other than numeric values, they must appear in quotes. For example, a default value of noon for a time field would be
'12:00:00'
including the quotes around the literal value.
Field components can use SQL server constraints. In addition, your applications can create and use custom constraints that are local to your application. All constraints are rules or conditions that impose a limit on the scope or range of values that a field can store. The following sections describe working with constraints at the field component level.
A custom constraint is not imported from the server like other constraints. It is a constraint that you declare, implement, and enforce in your local application. As such, custom constraints can be useful for offering a pre-validation enforcement of data entry, but a custom constraint cannot be applied against data received from or sent to a server application.
To create a custom constraint, set the CustomConstraint property to specify a constraint condition, and set ConstraintErrorMessage to the message to display when a user violates the constraint at runtime.
CustomConstraint is an SQL string that specifies any application-specific constraints imposed on the field's value. Set CustomConstraint to limit the values that the user can enter into a field. CustomConstraint can be any valid SQL search expression such as
x > 0 and x < 100
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.
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.
Most production SQL databases use constraints to impose conditions on the possible values for a field. For example, a field may not permit NULL values, may require that its value be unique for that column, or that its values be greater than 0 and less than 150. While you could replicate such conditions in your client applications, Delphi offers the ImportedConstraint property to propagate a server's constraints locally.
ImportedConstraint is a read-only property that specifies an SQL clause that limits field values in some manner. For example:
Value > 0 and Value < 100
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.
Object field (TObjectField) descendants support the field types ADT (Abstract Data Type), Array, DataSet, and Reference. All of these field types either contain or reference child fields or other data sets.
ADT fields and reference fields map to fields that contain child fields. An ADT field contains child fields, which themselves can be any scalar or object type. An array field contains an array of child fields, all of the same type.
Dataset and reference fields map to fields that access other data sets. A dataset field provides access to a nested data set and a reference field stores a pointer (reference) to another persistent object (ADT).
When you add fields with the Fields editor to a dataset that contains object fields, persistent object fields of the correct type are automatically created for you. Adding persistent object fields to a dataset automatically sets the dataset's ObjectView property to True, which instructs the fields to be stored hierarchically rather than flattened out.
The following properties are common to all object field descendants and provide the functionality to handle child fields and datasets.
Both ADT and array fields contain child fields that can be displayed through data-aware controls. Data-Aware controls such as TDBEdit and TDBGrid automatically display ADT and array field types.
Data-aware controls with a DataField property automatically displays any ADT and array fields and their child fields in the drop-down list. When an ADT or array field is bound to a data-aware control, the child fields appear in an uneditable comma delimited string in the control. A child field is bound to the control as a normal data field.
A TDBGrid control displays ADT and array field data differently, depending on the value of the dataset's ObjectView property. When ObjectView is False, each child field appears in a single column. When ObjectView is True, an ADT or array field can be expanded and collapsed by clicking on the arrow in the title bar of the column. When the field is expanded, each child field appears in its own column and title bar, all below the title bar of the ADT or array itself. When the ADT or array is collapsed, only one column appears with an uneditable comma delimited string containing the child fields.
ADTs are user-defined types created on the server, and are similar to structures. An ADT can contain most scalar field types, array fields, reference fields, and nested ADTs.
There are a variety of ways to access the data in ADT field types. Creating and using persistent fields is strongly recommended. The following examples assign a child field value to an edit box called CityEdit, and uses the following ADT structure,
Address Street City State Zip
and the following persistent fields created for the Customer table component,
CustomerAddress: TADTField; CustomerAddrStreet: TStringField; CustomerAddrCity: TStringField; CustomerAddrState: TStringField; CustomerAddrZip: TStringField;
This line of code uses a persistent field and demonstrates the recommended method of accessing data in ADT fields.
CityEdit.Text := CustomerAddrCity.AsString;
The following code examples require that the dataset's ObjectView property be set to True in order to compile. They don't require persistent fields.
This example uses a fully qualified name with the FieldByName method on the dataset.
CityEdit.Text := Customer.FieldByName('Address.City').AsString;
You can access the value of a child field with the TADTField's FieldValues property. FieldValues accepts and returns a Variant, so it can handle and convert fields of any type. The index parameter takes an integer value which specifies the offset of the field. It is also the default property on TObjectField, and can therefore be omitted. For example,
CityEdit.Text := TADTField(Customer.FieldByName('Address'))[1];
CityEdit.Text := TADTField(Customer.FieldByName('Address')).FieldValues[1];
This code uses the Fields property of the TADTField component.
CityEdit.Text := TADTField(Customer.FieldByName('Address')).Fields[1].AsString;
This code uses the Fields property of the TADTField component with FieldByName of both the dataset and the TFields object.
CityEdit.Text :=
TADTField(Customer.FieldByName('Address')).Fields.FieldByName('City').AsString;
As you can see from this last example, accessing the field's data through persistent fields is much simpler. These additional access methods are primarily useful when the structure of the database table is not fixed or known at design time.
ADT field values can also be accessed with a dataset's FieldValues property:
Customer.Edit; Customer['Address.City'] := CityEdit.Text; Customer.Post;
The next statement reads a string value from the City child field of the ADT field Address into an edit box:
CityEdit.Text := Customer['Address.City'];
Note: The dataset's ObjectView property can be either True or False for these lines of code to compile.
Array fields consist of a set of fields of the same type. The field types can be scalar (e.g. float, string), or non-scalar (an ADT), but an array field of arrays is not permitted. The SparseArrays property of TDataSet determines whether a unique TField object is created for each element of the array field.
There are a variety of ways to access the data in array field types. The following example populates a list box with all of the non-null array elements.
var OrderDates: TArrayField; I: Integer; begin for I := 0 to OrderDates.Size - 1 do begin if OrderDates.Fields[I].IsNull then Break; OrderDateListBox.Items.Add(OrderDates[I]); end; end;
The following examples assign a child field value to an edit box called TelEdit, and uses the array TelNos_Array, which is a six element array of strings. The following persistent fields created for the Customer table component are used by the following examples:
CustomerTelNos_Array: TArrayField; CustomerTelNos_Array0: TStringField; CustomerTelNos_Array1: TStringField; CustomerTelNos_Array2: TStringField; CustomerTelNos_Array3: TStringField; CustomerTelNos_Array4: TStringField; CustomerTelNos_Array5: TStringField;
This line of code uses a persistent field to assign an array element value to an edit box.
TelEdit.Text := CustomerTelNos_Array0.AsString;
The following code examples require that the dataset's ObjectView property be set to True in order to compile. They don't require persistent fields.
You can access the value of a child field with the dataset's FieldValues property. FieldValues accepts and returns a Variant, so it can handle and convert fields of any type. For example,
TelEdit.Text := TArrayField(Customer.FieldByName('TelNos_Array'))[1];
TelEdit.Text := TArrayField(Customer.FieldByName('TelNos_Array')).FieldValues[1];
This next code example uses the Fields property of the TArrayField component.
TelEdit.Text := TArrayField(Customer.FieldByName('TelNos_Array')).Fields[1].AsString;
Dataset fields provide access to data stored in a nested dataset. The NestedDataSet property references the nested dataset. The data in the nested dataset is then accessed through the field objects of the nested dataset.
TDBGrid controls enable the display of data stored in data set fields. In a TDBGrid control, a dataset field is indicated in each cell of a dataset column with a "(DataSet)", and at runtime an ellipsis button also exists to the right. Clicking on the ellipsis brings up a new form with a grid displaying the dataset associated with the current record's dataset field. This form can also be brought up programmatically with the DB grid's ShowPopupEditor method. For example, if the seventh column in the grid represents a dataset field, the following code will display the dataset associated with that field for the current record.
DBGrid1.ShowPopupEditor(DBGrid1.Columns[7]);
A dataset field is not normally bound directly to a data aware control. Rather, since a nested data set is just that, a data set, the means to get at its data is via a TDataSet descendant. This particular TDataSet descendant is TNestedTable, and it provides the specific functionality needed to access data stored in nested datasets. Once a TDataSetField is associated with a dataset field, persistent fields can be created for the fields of the nested dataset.
To access the data in a dataset field you first create a persistent TDataSetField object by invoking the table's Fields editor, and then link to this field using the DatasetField property on a TNestedTable or TClientDataSet object. If the nested dataset field for the current record is assigned, the nested dataset will contain records with the nested data; otherwise, the nested 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.
Reference fields store a pointer or reference to another ADT object. This ADT object is a single record of another object table. Reference fields always refer to a single record in a dataset (object table). The data in the referenced object is actually returned in a nested dataset, but can also be accessed via the Fields property on the TReferenceField.
In a TDBGrid control a reference field is designated in each cell of the dataset column, with (Reference) and, at runtime, an ellipsis button to the right. At runtime, clicking on the ellipsis brings up a new form with a grid displaying the object associated with the current record's reference field.
This form can also be brought up programmatically with the DB grid's ShowPopupEditor method. For example, if the seventh column in the grid represents a reference field, the following code will display the object associated with that field for the current record.
DBGrid1.ShowPopupEditor(DBGrid1.Columns[7]);
To access the data in a reference field you first create a persistent TDataSetField, and then link to this field using the DatasetField property on a TNestedTable or TClientDataSet. If the reference is assigned, the reference will contain a single record with the referenced data. If the reference is null, the reference will be empty.
The following examples 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;
pubsweb@inprise.com
Copyright © 1999, Inprise Corporation. All rights reserved.