This document covers the following topics:
This section refers to the association of arbitrary user-defined information ("client data") with a (dialog or) dialog element. There are various complementary ways of achieving this, which will be discussed in detail in the following sections. The attributes and actions relating to the manipulation of client data in Natural are (in the order they are discussed in this document):
CLIENT-DATA
attribute
CLIENT-HANDLE
attribute
CLIENT-KEY
attribute
CLIENT-VALUE
attribute
SET-CLIENT-VALUE
action
GET-CLIENT-VALUE
action
ENUM-CLIENT-KEYS
action
For a number of dialog element types, the
CLIENT-DATA
attribute
may be used to associate a single arbitrary 4-byte integer value with the
dialog element. This may be useful for linking data to a specific dialog
element. A list box item, for example, can receive and pass on the ISN of a
database record. The CLIENT-DATA
attribute
value may be changed at any time.
In Natural code, this might look like this:
DEFINE DATA LOCAL 1 #LBITEM-1 HANDLE OF LISTBOXITEM 1 #ISN (I4) ... END-DEFINE ... READ... #LBITEM-1.CLIENT-DATA:= #ISN END-READ ...
Note:
The CLIENT-DATA
attribute
of a dialog is reserved for its dialog ID, and should not be used for the
storage of user-defined client data.
Similarly, for all dialog element types, the
CLIENT-HANDLE
attribute
may be used to associate a single arbitrary GUI object handle with the dialog
element. For example, in the section
Working with Dialog Bar
Controls, sample generic code is provided for building up
a context menu containing entries for each tool bar control and dialog bar
control in use by the dialog, allowing the user to individually show and hide
them. In this example, the
CLIENT-HANDLE
attribute
of each such menu item is set to the handle of the respective tool or dialog
bar, allowing it to be both simply and directly retrieved when the menu item is
clicked.
Note:
The term "keyed" refers to the ability to store
multiple items of information for a given dialog element, each item being
stored under a unique retrieval key.
Client data may also be set and retrieved as an alphanumeric string
of up to 253 characters by using the
CLIENT-KEY
and
CLIENT-VALUE
attributes
in combination.
To update a dialog element with a particular string
You first assign a value to the dialog element's
CLIENT-KEY
attribute,
if this attribute does not already contained the desired value. This determines
the key under which the string is to be stored for a dialog element.
You then assign an alphanumeric string to the
CLIENT-VALUE
attribute
of the dialog element.
This enables you to store a number of key/value pairs for one dialog element.
Example:
#LB-1.CLIENT-KEY:= 'ANYKEY' #LB-1.CLIENT-VALUE:= 'ANYSTRING' /* The string to be stored
Note:
In this and all following examples, the handle variable
#LB-1
is used, which (by convention) normally refers to a list
box. However, with the exception of the
CLIENT-DATA
attribute,
client data can be associated with GUI objects of any type, even those without
a user interface, such as timers or signals.
To query a dialog element for a particular string
You first assign a
CLIENT-KEY
value to the
dialog element, if this attribute does not already contained the desired
value.
Then you query the
CLIENT-VALUE
attribute
for the dialog element to retrieve the corresponding value.
If you query the
CLIENT-VALUE
of a
CLIENT-KEY
and there is
no such key among the key/value pairs of the dialog element, an empty string is
returned.
Example:
#LB-1.CLIENT-KEY:= 'ANYKEY' IF #LB-1.CLIENT-VALUE EQ 'ANYSTRING' THEN ... END-IF
If non-alphanumeric data is to be stored and retrieved, getting the data back into the original format may be a little more complicated, as shown below.
Example:
DEFINE DATA LOCAL 01 #DATE (D) ... END-DEFINE #LB-1.CLIENT-KEY := 'ANYKEY' /* Store the current date #LB-1.CLIENT-VALUE := *DATX /* Retrieve it as a date (D) field STACK TOP DATA #LB-1.CLIENT-VALUE INPUT #DATE
The STACK
statement retrieves the client value in alphanumeric form and places it one the
Natural stack, from which the INPUT
statement unstacks it into
the specified variable, #DATE
, implicitly converting the data from
alphanumeric to date form. Alternatively, it would be possible to retrieve the
client value into an alphanumeric variable, followed by explicitly converting
it to the date field via a MOVE
EDITED
statement. However, the above approach has the
advantage that it is not dependent on the date format (DTFORM
), as well as
not requiring the above-mentioned alphanumeric variable.
For some data types, such as dates and times, the default
alphanumeric representation of the type (as used by the
CLIENT-VALUE
attribute)
does not contain all the information contained in the original data type. For
example, the default alphanumeric representation for time (T) values only
contains the hours, minutes and seconds, and does not contain either the date
component or tenths of a second. Similarly, the default alphanumeric
represention for date (D) values does not contain century information. Thus, in
order for the correct century to be assumed in the above example, it may be
necessary to set the "Sliding Window" (YSLW
) parameter
correctly before running the program.
If a dynamic alpha variable is used to directly receive the
CLIENT-VALUE
attribute
value, the resulting value will have a length of 253 characters, being padded
with blanks if necessary. This is due to the use of an attribute buffer of
format A253 internally, and will be discussed later. The same effect is
obtained when assigning an explicitly-defined A253 field to a dynamic variable.
In either case, to prevent these trailing blanks from being stored in the
dynamic variable, a COMPRESS
statement should be
used instead of a simple MOVE
or assignment, as shown
below.
DEFINE DATA LOCAL 01 #DYN (A) DYNAMIC ... END-DEFINE #DYN := 'ANYSTRING' /* Set the client data #LB-1.CLIENT-KEY := 'ANYKEY' #LB-1.CLIENT-VALUE := #DYN /* Retrieve value as 253-character string: #DYN := #LB-1.CLIENT-VALUE /* Retrieve value without trailing blanks: COMPRESS #LB-1.CLIENT-VALUE INTO #DYN
Regardless of which of these approaches are used, any trailing
blanks in dynamic alphanumeric variables are effectively lost if stored and
retrieved via the CLIENT-VALUE
attribute.
To delete a particular string for a dialog element
You first assign a CLIENT-KEY
value to the dialog element, if this attribute does not already contained the
desired value.
Then you RESET
(or explicitly assign an
all-blank value to) the CLIENT-VALUE
attribute
for the dialog element to delete the corresponding value.
Example:
#LB-1.CLIENT-KEY:= 'ANYKEY' RESET #LB-1.CLIENT-VALUE
As an alternative to setting client data in alphanumeric string form
using the CLIENT-KEY
and
CLIENT-VALUE
attributes
in combination, the
SET-CLIENT-VALUE
and
GET-CLIENT-VALUE
actions may be used to
store and retrieve client data directly in the supplied format, with no
conversion. The value may, however, be stored in compressed form. In
particular, trailing blanks in non-dynamic alphanumeric data are not stored, in
order to save space. For example, if you supply an A253 field containing the
value FRED
followed by 249 filler blanks, only the A4 value
FRED
will be stored as client data internally. This latter
optimization also applies to client data stored via the
CLIENT-VALUE
attribute.
The two techniques may be intermixed (i.e., one technique used to set the data and the other technique used to retrieve it). However, the use of the actions provides a number of advantages over the use of the attributes, as will become clear in the following sections.
To update client data for a dialog element using the action-based technique
Call the
SET-CLIENT-VALUE
action, passing the
handle of the dialog element, the (client) key under which the value is to be
stored, and the value itself. Alternatively, the key parameter can be omitted,
in which case the current value of the dialog element's
CLIENT-KEY
attribute is
implicitly used as the key.
Example:
#LB-1.CLIENT-KEY := 'ANYKEY' /* The following three statements are equivalent ways of setting the same /* information: /* (1) attribute-based approach: #LB-1.CLIENT-VALUE := 'ANYVALUE' /* (2) action-based approach, with explicitly-specified key PROCESS GUI ACTION SET-CLIENT-VALUE WITH #LB-1 'ANYVALUE' 'ANYKEY' GIVING *ERROR /* (3) action-based approach without key; CLIENT-KEY attribute implicitly used PROCESS GUI ACTION SET-CLIENT-VALUE WITH #LB-1 'ANYVALUE' GIVING *ERROR
A significant advantage of storing client data via the
SET-CLIENT-VALUE
action is that there is
no intermediate conversion to alphanumeric (A253) format involved, as is the
case if the CLIENT-VALUE
attribute
is used. This is shown in the following diagram, where format X is used to
imply any particular data type, and format An
represents an alphanumeric value stripped of any trailing blanks:
Here we see that the storage and retrieval of client data via the
CLIENT-VALUE
attribute
is a two-step process (as is indeed the case for all attributes in Natural)
depicted by the arrows (1) and (2) above, involving an attribute buffer
corresponding to the defined format for the attribute - in this case A253. In
contrast, the use of the
SET-CLIENT-VALUE
and
GET-CLIENT-VALUE
actions is a single step
process that is effectively equivalent to performing step (2) alone, by-passing
the conversion between the attribute buffer and the source or target field.
This offers the following advantages (aside from being somewhat faster):
Alphanumeric data longer than 253 characters may be stored without truncation, due to not having to pass through the attribute buffer.
Handle values may be stored. These are incompatible with the use of an alphanumeric attribute buffer, because conversions between handles and alphanumeric fields are not allowed.
If the data is being sourced from a dynamic alphanumeric variable, any trailing blanks are preserved. If the attribute buffer is used, trailing blanks become indistinguishable from (and are assumed to be) buffer filler characters and are thus stripped from the value when it is stored.
Because the data is stored without conversion to and from alphanumeric format, non-alphanumeric data may be stored without any loss of information. For example, date information and tenths of a second are not lost when time values are stored, and century information is not lost when dates are stored.
In addition, there are other advantages to using the action-based approach for client data storage:
Alphanumeric values consisting entirely of blanks may be stored.
This is not possible via the
CLIENT-VALUE
attribute,
as this would imply a delete operation.
Error codes (e.g., in the case where an invalid control handle
is passed) are returned in the GIVING
field (if specified),
without standard error handling necessarily being invoked (although this can be
achieved, if desired, by the use of GIVING *ERROR
).
To query client data for a dialog element using the action-based technique
Call the
GET-CLIENT-VALUE
action, passing the
handle of the dialog element, the (client) key for which the value is to be
retrieved, and a field to receive the value itself. Alternatively, the key
parameter can be omitted, in which case the current value of the dialog
element's CLIENT-KEY
attribute is
implicitly used as the key.
Example:
DEFINE DATA LOCAL 01 #VALUE (A253) ... END-DEFINE PROCESS GUI ACTION GET-CLIENT-VALUE WITH #LB-1 #VALUE 'ANYKEY' GIVING *ERROR IF #VALUE <> ' ' /* Value found ... ELSE /* Value not found ... END-IF
Note that the format of the field specified to receive the value
must be MOVE
-compatible
with the format of the stored value.
If the specified key is not found for the specified dialog element,
the value field is RESET
. For example, an
alphanumeric receiving field is filled with blanks, and a numeric receiving
field is set to zero.
However, if such values can be explicitly stored for this key by the program, the value alone cannot be used to determine whether the requested client data was found.
To query client data if reset values are being explicitly stored
Call the
GET-CLIENT-VALUE
action, also passing (in
addition to the standard parameters mentioned above) a field of type L to
receive the found/not found status.
Example:
DEFINE DATA LOCAL 01 #VALUE (A253) 01 #FOUND (L) ... END-DEFINE * PROCESS GUI ACTION GET-CLIENT-VALUE WITH #LB-1 #VALUE 'ANYKEY' #FOUND GIVING *ERROR * IF #FOUND ... END-IF
The main advantage of reading client data via the
GET-CLIENT-VALUE
action is again the
avoidance of going via an attribute buffer (see earlier diagram), implying that
no intermediate conversion to or from alphanumeric (A253) format involved, as
is the case if the CLIENT-VALUE
attribute
is used. Instead, the stored data is converted directly to the format of the
receiving field for the value. This offers the following advantages:
Alphanumeric data longer than 253 characters may be retrieved,
without being truncated to the length of the (not used)
CLIENT-VALUE
attribute
buffer.
Handle values may be retrieved, which are not
MOVE
-compatible with the
alphanumeric format of the
CLIENT-VALUE
attribute
buffer.
If the data is being read into a dynamic alphanumeric variable,
any trailing blanks in stored alphanumeric data are preserved. If the
CLIENT-VALUE
attribute
is used, the dynamic variable would receive the buffer's filler characters and
be unable to distinguish them from any trailing blanks in the original
data.
In addition, Stored alphanumeric values consisting entirely of
blanks may be recognized. This is not possible via the
CLIENT-VALUE
attribute,
as there is no way to distinguish them from the implicit "not
found" value.
To delete client data for a dialog element using the action-based technique
Call the
SET-CLIENT-VALUE
action, passing the
handle of the dialog element and the (client) key for which the value is to be
deleted, but omitting the value itself. Alternatively, the key parameter can be
omitted, in which case the current value of the dialog element's
CLIENT-KEY
attribute is
implicitly used as the key.
Example:
/* No value supplied => delete any existing value for specified key PROCESS GUI ACTION SET-CLIENT-VALUE WITH #LB-1 1X 'ANYKEY' GIVING *ERROR /* Alternatively, a mixed attribute/action approach can be used: #LB-1.CLIENT-KEY := 'ANYKEY' PROCESS GUI ACTION SET-CLIENT-VALUE WITH #LB-1 GIVING *ERROR
The above sections have dealt with the creation, updating, querying and deletion of client key and client value data. In most cases this is enough. However, in some situations, the keys that are being used by a dialog element are either not known to the code that reads them, or it is necessary to be able to verify that the expected keys are present for debugging or testing purposes. The iterative process of retrieving the keys currently being used by a particular dialog element is known as key enumeration.
To enumerate the client keys for a dialog element
Call the
ENUM-CLIENT-KEYS
action, passing the
handle of the dialog element for which the client keys should be enumerated,
but omitting the key parameter. This has the effect of resetting the dialog
element's enumeration cursor (i.e., position) back to the beginning of its
internal key list. Since the enumeration cursor is initially reset when a
dialog element is created, this step is strictly not required for the first key
enumeration for a particular dialog element. However, it is good practice to
explicitly reset the cursor in this manner, in order to make the enumeration
context-insensitive.
Call the
ENUM-CLIENT-KEYS
action again, passing
the handle of the dialog element and the key parameter, into which the first
key (if any) will be returned.
If the key field was internally
RESET
to blanks by the
above call, this indicates that no (more) keys remain, and the program should
terminate the enumeration process.
Otherwise, go back to step 2 in order to retrieve the next key (if any).
Example:
/* Enumerate and output all client keys in use by control #LB-1: /* (1) Reset enumeration cursor: PROCESS GUI ACTION ENUM-CLIENT-KEYS WITH #LB-1 GIVING *ERROR /* (2) Enumerate and delete the keys one-by-one: REPEAT PROCESS GUI ACTION ENUM-CLIENT-KEYS WITH #LB-1 #LB-1.CLIENT-KEY GIVING *ERROR IF #LB-1.CLIENT-KEY <> ' ' RESET #LB-1.CLIENT-VALUE /* delete the key END-IF WHILE #LB-1.CLIENT-KEY <> ' ' END-REPEAT
This example illustrates that the
ENUM-CLIENT-KEYS
action is tolerant of
keys being deleted during the enumeration process. If (as shown here) the last
enumerated (i.e., "current") key is deleted, Natural automatically
moves the internal enumeration cursor to its predecessor in th enumeration
sequence, or resets it if there no predecessor. In either case, the next key
returned by ENUM-CLIENT-KEYS
is the one that would
have been returned had the previous key not been deleted.
Note:
The sequence in which the keys are enumerated is
implementation-dependent and is not guaranteed to remain the same in future
Natural versions. Therefore, do not code your programs such that they are
dependent on any particular enumeration sequence.