skip to main content
OpenEdge Development: ADM and SmartObjects
SmartObject Interactions : ADM properties
 
ADM properties
A basic requirement of SmartObjects is that each SmartObject can expose its properties to other objects, either Progress procedures or non‑Progress application objects communicating through the Open4GL interface. Within a Progress session, it is not practical to use SHARED variables or buffers to share values between SmartObjects. SmartObjects are run as persistent procedures, which means they run as peers of one another and have no fixed execution hierarchy; therefore, the SHARED mechanism does not work well. Also, there is no way to use SHARED values between a client and non‑Progress application components.
The ADM supports properties through these mechanisms:
*The ADMProps temp-table for object property definitions and storage
*Property‑specific get and set functions
*The setUserProperty and getUserProperty functions for dynamic properties
The first two mechanisms, which establish static properties, share a common syntax and can be used interchangeably. They return property values with the native data type, so their use is recommended when you need properties in custom super procedures that will become part of an object type. Dynamic properties, in contrast, return property values as strings that might need conversion to other data types, so this mechanism is most appropriate for properties that will be used only once in your application. The rest of this chapter describes these mechanisms, as well as related topics.
Some special property considerations apply when you are building new SmartObject classes and or extending existing classes. For details, see Chapter 8, “Developing ADM Extensions.”
ADMProps temp-table and object properties
One mechanism that supports getting and setting many SmartObject properties is the ADMProps temp-table. An ADMProps temp-table is defined for each object; it defines all standard object properties for that object type.
Each SmartObject super procedure uses a Progress include file that defines its own set of properties, and each SmartObject type that uses that super procedure also includes the same list. For example, the super procedure smart.p defines functions and procedures that all SmartObjects use and is at the top of the hierarchy of super procedures. This procedure includes smrtprop.i, which defines the basic properties that apply to all SmartObjects. It defines a preprocessor constant for each basic property and also puts a FIELD definition for that property in the ADMProps temp-table. In addition, all SmartObjects use smart.p as a super procedure and include the supporting include file smart.i. The smart.i include file also includes smrtprop.i, as well as adding smart.p as a super procedure.
The smrtprop.i (1 of 2) file defines these properties:
 
smrtprop.i
. . .
 /* These preprocessors let the get and set methods know at compile time which
     property values are located in the temp-table and which must be accessed
     through the property functions.
 */
 &GLOB xpObjectVersion
 &GLOB xpObjectType
 &GLOB xpContainerType
 &GLOB xpPropertyDialog
 &GLOB xpQueryObject
 &GLOB xpContainerHandle
 &GLOB xpInstanceProperties
 &GLOB xpSupportedLinks
 &GLOB xpContainerHidden
 &GLOB xpObjectInitialized
 &GLOB xpObjectHidden
 &GLOB xpContainerSource
 &GLOB xpContainerSourceEvents
 &GLOB xpDataSource
 &GLOB xpDataSourceEvents
 &GLOB xpTranslatableProperties
 &GLOB xpObjectPage
 &GLOB xpDBAware
 /* This temp-table defines all the property fields for an object. This
    include file contributes the DEFINE statement header and all basic smart
    object properties. Each other property class include file adds its own
    fields and then the parent object ends the statement. Define the fields
    for smart objects only, not for their super procedures.
 */
 /* ObjectHidden is here because "hidden" is a logical concept. */
&IF "{&ADMSuper}":U = "":U &THEN
  CREATE TEMP-TABLE ghADMProps.
  ghADMProps:UNDO = FALSE.
  ghADMProps:ADD-NEW-FIELD(’ObjectVersion’:U, ’CHAR’:U, 0, ?,
    ’{&ADM-VERSION}’:U).
  ghADMProps:ADD-NEW-FIELD(’ObjectType’:U, ’CHAR’:U, 0, ?,
    ’{&PROCEDURE-TYPE}’:U).
  ghADMProps:ADD-NEW-FIELD(’ContainerType’:U, ’CHAR’:U, 0, ?,
    ’{&ADM-CONTAINER}’:U).
  ghADMProps:ADD-NEW-FIELD(’PropertyDialog’:U, ’CHAR’:U, 0, ?,
    ’{&ADM-PROPERTY-DLG}’:U).
  ghADMProps:ADD-NEW-FIELD(’QueryObject’:U, ’LOGICAL’:U, 0, ?, no).
  ghADMProps:ADD-NEW-FIELD(’ContainerHandle’:U, ’HANDLE’:U).
  ghADMProps:ADD-NEW-FIELD(’InstanceProperties’:U, ’CHAR’:U, 0, ?,
    ’{&xcInstanceProperties}’:U ).
  /* NOTE: Any need to support &User-Supported-Links??*/
  ghADMProps:ADD-NEW-FIELD(’ContainerHidden’:U, ’LOGICAL’:U, 0, ?, yes).
  ghADMProps:ADD-NEW-FIELD(’ObjectInitialized’:U, ’LOGICAL’:U, 0, ?, no).
  ghADMProps:ADD-NEW-FIELD(’ObjectHidden’:U, ’LOGICAL’:U, 0, ?, yes).
  ghADMProps:ADD-NEW-FIELD(’UIBMode’:U, ’CHAR’:U, 0, ?, ’’:U).
  ghADMProps:ADD-NEW-FIELD(’ContainerSource’:U, ’HANDLE’:U).
  ghADMProps:ADD-NEW-FIELD(’ContainerSourceEvents’:U, ’CHAR’:U, 0, ?,
    ’initializeObject,hideObject,viewObject,destroyObject,enableObject,
confirmExit’:U).
  ghADMProps:ADD-NEW-FIELD(’DataSource’:U, ’HANDLE’:U).
  ghADMProps:ADD-NEW-FIELD(’DataSourceEvents’:U, ’CHAR’:U, 0, ?,
    ’dataAvailable,queryPosition,deleteComplete,fetchDataSet’:U).
  ghADMProps:ADD-NEW-FIELD(’TranslatableProperties’:U, ’CHAR’:U, 0, ?,
    ’{&xcTranslatableProperties}’:U).
  ghADMProps:ADD-NEW-FIELD(’ObjectPage’:U, ’INT’:U, 0, ?, 0).
  ghADMProps:ADD-NEW-FIELD(’DBAware’:U, ’LOGICAL’:U, 0, ?,
  &IF DEFINED (DB-AWARE) NE 0 &THEN
    {&DB-AWARE}).
  &ELSE
    no).
  &ENDIF
&ENDIF.
. . .
If a preprocessor has a name of the form xppropname, you can access the associated property value directly from its property temp-table FIELD. The {get} and {set} pseudo‑syntax uses this mechanism to optimize references to properties, primarily in super procedures. If it is necessary for either get or set to invoke the corresponding property function (because of some other action it performs), the preprocessor constant is not defined for that property, and references always go through the get and set functions. For more information, see the “{get} and {set} pseudo-syntax for object properties” section.
Now consider a SmartObject defined as a subclass of smart.p. For example, a SmartDataObject adds smart.p as its first super procedure and then adds query.p and data.p as the super procedures that define behavior specific to SmartDataObjects. The data.p procedure includes the property include file dataprop.i, which appends more basic values to the list already started by smrtprop.i and continued by qryprop.i. Each property include file adds FIELD definitions to the ADMProps temp-table definition.
The dataprop.i (1 of 2) file defines these additional basic properties:
 
dataprop.i
. . .
 /* Preprocessor definitions which tell at compile time which
    properties can be retrieved directly from the property temp-table. */
 &GLOB xpEnabledTables
 &GLOB xpAutoCommit
 &GLOB xpDataHandle
 &GLOB xpCurrentRowid
 &GLOB xpAppService
 &GLOB xpASUsePrompt
 &GLOB xpASInfo
 &GLOB xpASHandle
 &GLOB xpASDivision
 &GLOB xpUpdateSource
 &GLOB xpCommitSource
 &GLOB xpCommitSourceEvents
 &GLOB xpCommitTarget
 &GLOB xpCommitTargetEvents
 &GLOB xpDataModified
 &GLOB xpRowsToBatch
 &GLOB xpCheckCurrentChanged
 
{src/adm2/qryprop.i}
&IF "{&ADMSuper}":U = "":U &THEN
  ghADMProps:ADD-NEW-FIELD(’RowObject’:U, ’HANDLE’:U, 0, ?, ?).
  ghADMProps:ADD-NEW-FIELD(’RowObjUpd’:U, ’HANDLE’:U, 0, ?, ?).
  ghADMProps:ADD-NEW-FIELD(’FirstRowNum’:U, ’INT’:U, 0, ?, ?).
  ghADMProps:ADD-NEW-FIELD(’LastRowNum’:U, ’INT’:U, 0, ?, ?).
  ghADMProps:ADD-NEW-FIELD(’EnabledTables’:U, ’CHAR’:U, 0, ?, ’’:U).
  ghADMProps:ADD-NEW-FIELD(’AutoCommit’:U, ’LOGICAL’:U, 0, ?, yes).
  ghADMProps:ADD-NEW-FIELD(’DataHandle’:U, ’HANDLE’:U).
  ghADMProps:ADD-NEW-FIELD(’CurrentRowid’:U, ’ROWID’:U).
  ghADMProps:ADD-NEW-FIELD(’AppService’:U, ’CHAR’:U, 0, ?, ’’:U).
  ghADMProps:ADD-NEW-FIELD(’ASUsePrompt’:U, ’LOGICAL’:U, 0, ?, ?).
  ghADMProps:ADD-NEW-FIELD(’ASInfo’:U, ’CHAR’:U, 0, ?, ’’:U).
  ghADMProps:ADD-NEW-FIELD(’ASHandle’:U, ’HANDLE’:U).
  ghADMProps:ADD-NEW-FIELD(’ASDivision’:U, ’CHAR’:U, 0, ?, ’’:U).
  ghADMProps:ADD-NEW-FIELD(’UpdateSource’:U, ’HANDLE’:U).
  ghADMProps:ADD-NEW-FIELD(’CommitSource’:U, ’HANDLE’:U).
  ghADMProps:ADD-NEW-FIELD(’CommitSourceEvents’:U, ’CHAR’:U, 0, ?,
    ’commitTransaction,undoTransaction’:U).
  ghADMProps:ADD-NEW-FIELD(’CommitTarget’:U, ’CHAR’:U, 0, ?, ’’:U).
  ghADMProps:ADD-NEW-FIELD(’CommitTargetEvents’:U, ’CHAR’:U, 0, ?, ’rowObjectState’:U).
  ghADMProps:ADD-NEW-FIELD(’DataModified’:U, ’LOGICAL’:U, 0, ?, no).
  ghADMProps:ADD-NEW-FIELD(’RowsToBatch’:U, ’INT’:U, 0, ?, 200).  /* Rows per AppServer xfer */
  ghADMProps:ADD-NEW-FIELD(’CheckCurrentChanged’:U, ’LOGICAL’:U, 0, ?, yes).
  ghADMProps:ADD-NEW-FIELD(’NextBatchRow’:U, ’CHAR’:U, 0, ?, ?).   /* Next row to return in a batch */
  ghADMProps:ADD-NEW-FIELD(’DataQueryBrowsed’:U, ’LOGICAL’:U, 0, ?, no).
  ghADMProps:ADD-NEW-FIELD(’FirstResultRow’:U, ’CHAR’:U, 0, ?, ?).
  ghADMProps:ADD-NEW-FIELD(’LastResultRow’:U, ’CHAR’:U, 0, ?, ?).
  ghADMProps:ADD-NEW-FIELD(’RebuildOnRepos’:U, ’LOGICAL’:U, 0, ?, no).
  ghADMProps:ADD-NEW-FIELD(’RowObjectState’:U, ’CHAR’:U, 0, ?, ’NoUpdates’:U).
  ghADMProps:ADD-NEW-FIELD(’ServerOperatingMode’:U, ’CHAR’:U, 0, ?, ?).
  ghADMProps:ADD-NEW-FIELD(’StatelessSavedProperties’:U, ’CHAR’:U, 0, ?,
    ’CheckCurrentChanged,RowObjectState,LastResultRow,FirstResultRow,
QueryRowIdent’:U).
  ghADMProps:ADD-NEW-FIELD(’DestroyStateless’:U, ’LOGICAL’:U, 0, ?, no).
  ghADMProps:ADD-NEW-FIELD(’DisconnectAppServer’:U, ’LOGICAL’:U, 0, ?, no).
&ENDIF
. . .
As this example shows, each SmartObject type appends more basic properties to the end of the existing list. The process of appending basic properties to the existing list can be nested to any number of levels of object definition. In this case, both the data.p super procedure for SmartDataObjects, and individual SmartObjects themselves, include dataprop.i. The dataprop.i include file in turn includes qryprop.i, which includes smrtprop.i. Basic properties are then initialized in each corresponding object include file (such as smart.i for all SmartObjects and data.i for SmartDataObjects). This initialization both allocates FIELDs for each basic property and, where appropriate, assigns specific initial values. Because these preprocessor values are included in both the super procedures and in master objects of the type, both have access to them. This allows a super procedure to retrieve a property value directly from the ADMProps temp-table of its TARGET-PROCEDURE, which is extremely fast, instead of using function calls.
Get and set functions for object properties
A second mechanism that supports retrieving and setting property values is a naming convention that defines two groups of Progress functions called get and set functions. These functions are available for all properties that can be read or written by other objects:
*The get functions, which have the format getpropname, retrieve the values of properties. They take no input parameters and each returns the associated property value with the native data type. If a property is write only, no get function is available.
*The set functions, which have the format setpropname, set property values. They take as their only INPUT parameter the value of the associated property (which can be of any Progress data type) and each returns the type LOGICAL: TRUE or FALSE depending on whether the set operation was successful. If a property is read only, no set function is available.
If no special processing is needed to get or set a property, the get and set functions simply use the special include file syntax defined in the “ADMProps temp-table and object properties” section to access the appropriate field in the properties temp-table record for the SmartObject. For example:
 
FUNCTION getMyProp RETURNS CHARACTER:
  DEFINE VARIABLE cProp AS CHARACTER NO-UNDO.
  {get MyProp cProp}.
  RETURN cProp.
END.
 
FUNCTION setMyProp RETURNS LOGICAL (pcMyValue AS CHARACTER):
  {set MyProp pcMyValue}.
  RETURN TRUE.
END.
The get and set functions are not restricted to such simple operations; they can perform whatever actions you need to set and get values, such as verifying the validity of values. For example, you could use the following pair of functions to get and set the value of the BackGround Color property (BGColor) of the default Progress Frame in an object such as a SmartDataViewer:
 
FUNCTION getBGColor RETURNS INTEGER:
  RETURN {&FRAME-NAME}:BGCOLOR.
END.
 
FUNCTION setBGColor RETURNS LOGICAL (piBGC AS INTEGER):
  IF piBGC <= SomeMaxBGColorValue THEN DO:
   {&FRAME-NAME}:BGCOLOR = piBGC.
    RETURN TRUE.
  END.
  ELSE RETURN FALSE.
END.
{get} and {set} pseudo-syntax for object properties
Progress supplies a {get} and {set} pseudo‑syntax in the ADM that can be used to access property values. It is implemented using Progress include files named get and set, located in the gui and tty directories. This pseudo‑syntax makes accessing property values as simple as possible, and it provides transparency to whether a property can be retrieved or set directly, or must be accessed through its get and set functions. (The normal .i extension is removed to make these references look as much like standard syntax as possible.)
Get include file logic
The get include files use the following logic:
 
{get propname target-variable [ object-handle ] }.
The get syntax first checks to see whether the preprocessor xppropname exists. If it does, the get syntax returns the value in the TARGET-PROCEDURE’s ADMProps temp-table into target-variable. If the optional object-handle is specified, the get syntax searches that procedure’s ADMProps temp-table instead of the TARGET-PROCEDURE.
If there is no xppropname preprocessor constant, the getpropname function is executed in the TARGET-PROCEDURE (or in the object-handle, if specified).
This syntax is appropriate as an optimization for super procedures. In your application code, you should invoke the real functions.
Set include file logic
The set include files operate similarly, as follows:
 
{set propname value [ object-handle ] }.
If xppropname is defined, that entry in the TARGET-PROCEDURE’s or the object-handle’s ADMProps temp-table is set to value. If xppropname is not defined, the setpropname function is executed and passed the value (in its native data type) as an input parameter. The function returns TRUE or FALSE, depending on whether the operation was successful.
The following example illustrates interpreting the {get} logic:
 
{get DataColumns cColumns}.
How this translates depends on whether DataColumns has an xp preprocessor defined for it. If it does, the statement translates into the actual 4GL code:
 
ASSIGN 
  ghProp   = WIDGET-HANDLE(ENTRY(1, TARGET-PROCEDURE:ADM-DATA, CHR(1)))
  ghProp   = ghProp:BUFFER-FIELD(’DataColumns’)
  cColumns = ghProp:BUFFER-VALUE.
This retrieves the handle of the ADMProps temp-table buffer for the TARGET-PROCEDURE (stored in the procedure’s ADM-DATA attribute) and then retrieves the field value using the dynamic BUFFER-FIELD attribute.
If the preprocessor is not defined for DataColumns, the statement translates to the following:
 
cColumns = DYNAMIC-FUNCTION("getDataColumns":U IN TARGET-PROCEDURE).
The ghProp variable is defined in smart.i for use by these include files. The buffer handle of the ADMProps property temp-table record is stored in the ADM-DATA procedure property of each SmartObject, so it can be located by these include files. These include files resolve during compilation to a single executable Progress statement, making them extremely fast.
Using the get and set include files in other Progress code
You can use the get and set include files from any Progress code. When you use them either from a SmartObject of a given type or one of its super procedures, the syntax locates and uses the basic properties in the ADMProps property temp-table. If you use the syntax in other Progress application code (for example, in an object of a different type that does not share that property), it will not have a value for the preprocessor index and so will always use the get and set property functions.
The get and set include files are not actual 4GL syntax and represent a fairly small optimization, so you should restrict their use to super procedures and use the standard get and set function invocations in other Progress application code. Further, any non‑Progress code that uses the Open 4GL interfaces will be able to access the property only by invoking the appropriate get or set functions directly. For this reason, you should always define getpropname and setpropname functions for each basic property intended to be read and written from outside the object’s class. These property functions can simply access the ADMProps value directly.
Special properties
In some cases, it is desirable to store a property value in the ADMProps temp-table, to allow direct access to it from its super procedure and elsewhere, but also to have an action of some type take place when the value is set. For example, the QueryPosition property holds information about the cursor position of a query (FirstRecord, LastRecord, and so on). Whenever its value is set, a message must be also PUBLISHed so other objects can react. In cases of this type, you should not define an xppropertyname preprocessor constant, so the get and set functions that have the additional behavior will run instead of getting and setting the value directly in the ADMProps temp-table.
Instance properties
Each object type has certain properties that are appropriate to set as part of the object’s initialization and can be assigned values when SmartWindows and other containers using that object are being assembled. For example, the properties HideOnInit, DisableOnInit, and ObjectLayout can be set for any visual object to indicate whether this particular instance of the object should be hidden when it is first realized or disabled when it is first realized, or which of multiple visual layout names should be used for the object. These properties are referred to as instance properties. These can be differentiated from properties such as ObjectInitialized, ObjectHidden, QueryPosition, and many others that are set during the course of application execution but have no meaningful initial value that would specialize how that object is used in a particular case.
For each object include file, there is defined a list of instance properties using the xcInstanceProperties preprocessor value. These properties can be passed down to subclasses of a class. For example, all visual objects (including SmartDataViewers, SmartPanels, etc.) have the three Instance Properties defined in visual.i, and can append more to that list.
These properties are supported by instance properties dialog procedures for each object type. Any class can define an instance properties dialog procedure by defining the preprocessor ADM-PROPERTY-DIALOG to be the name of the procedure file that contains the instance properties dialog box. This initializes the PropertyDialog property at object startup time. When the object is dropped into a container at application assembly time, selecting the Instance Properties choice from the object’s pop‑up menu runs this dialog box procedure and sets these property values in that instance of the object. The values also are specified in AppBuilder‑generated code in adm-create-objects, in calls to the constructObject procedure.
Translatable properties
In the ADM, Progress maintains a list of properties that are translatable; that is, those properties whose literal values should not be tagged with “:U” when specified in adm-create-objects code generated by the AppBuilder. Most properties are not translatable, but a few should be; for example, the FolderLabels property for a Folder. The list of these properties is stored in the TranslatableProperties property.
Functions for accessing properties
The following property functions are useful for accessing SmartObject properties from application code:
*The assignLinkProperty function sets a property value in one or more objects at the other end of the specified link. It is defined as follows:
 
assignLinkProperty RETURNS LOGICAL
  (pcLink AS CHARACTER, pcPropName AS CHARACTER, 
   pcPropValue AS CHARACTER):
For example, the following code in a SmartDataObject uses assignLinkProperty to set the DataModified property to yes in all its Data-Targets:
 
DYNAMIC-FUNCTION(‘assignLinkProperty’:U,
  INPUT "DATA-TARGET":U,
  INPUT "DataModified":U,
  INPUT "yes":U).
*The linkProperty function returns the value of the specified property in the single object at the other end of the specified link. It is defined as follows:
 
linkProperty RETURNS CHARACTER
  (pcLink AS CHARACTER, pcPropName AS CHARACTER):
For example, the following code in a visual object (a SmartDataViewer or a SmartDataBrowser) uses linkProperty to get the current page of the object’s container:
 
iCurrentPage = DYNAMIC-FUNCTION(‘linkProperty’:U,
  INPUT "CONTAINER-SOURCE":U, INPUT "CurrentPage":U).
*The propertyType function returns the data type of the specified property. It is defined as follows:
 
propertyType RETURNS CHARACTER
  (pcPropName AS CHARACTER):
For example, the following code in a SmartDataObject gets the data type of its DataQueryBrowsed property:
 
cDataType = DYNAMIC-FUNCTION(‘propertyType’:U IN h_dcustomer,
  INPUT "DataQueryBrowsed":U).
This function returns data types only for properties for which a setpropname function is defined.
Dynamic properties
Dynamic properties are object properties that are defined on an as‑needed basis. Two functions, setUserProperty and getUserProperty, support dynamic properties in SmartObjects. They allow you to define dynamic properties without using either the set and get functions or a temp-table FIELD.
The setUserProperty function sets a named property. It is defined in an application as follows:
 
setUserProperty RETURNS LOGICAL
  ( pcPropName AS CHARACTER, pcPropValue AS CHARACTER ) :
The getUserProperty function returns the value of a specified property that was previously set using the setUserProperty function. The getUserProperty function is defined as follows:
 
getUserProperty RETURNS CHARACTER
  ( pcPropName AS CHARACTER ) :
Because the property values managed by getUserProperty and setUserProperty are stored as part of the ADM-DATA procedure attribute for the SmartObject, they always are stored and returned as CHARACTER strings.
This example illustrates how to set up a dynamic property called NewLimit. It assumes that you have a SmartDataViewer, and a SmartWindow that contains a button that sets a NewLimit attribute in the SmartDataViewer.
To set up the NewLimit dynamic property:
1. Add the following to the SmartWindow button trigger code, to set the property:
 
DYNAMIC-FUNCTION(’setUserProperty’:U IN h_v-customer,
  INPUT "NewLimit", INPUT dNewLimit:SCREEN-VALUE).
2. Add the following code to the SmartDataViewer, to get the property:
 
DECIMAL(DYNAMIC-FUNCTION(’getUserProperty’:U,
  INPUT "NewLimit")).